diff --git a/data/strings/strings.json b/data/strings/strings.json index 1253c042..fc2c9a0c 100644 --- a/data/strings/strings.json +++ b/data/strings/strings.json @@ -274,6 +274,17 @@ "mb_filters_Location_Distance": "Filter by distance to target", "mb_filters_Location_FuelRange": "Lock distance filter to your fuel range", "mb_filters_Location_SliderTitle": "Distance (LY)", + "mb_sort": "Sort", + "mb_sort_Ascending": "Ascending", + "mb_sort_Descending": "Descending", + "mb_sort_Alphabetical": "Alphabetical", + "mb_sort_Distance": "Distance", + "mb_sort_Credits": "Credits", + "mb_sort_FirstCreated": "Time Posted", + "mb_sort_DropNonHostiles": "Drop non-hostiles in populated areas", + "mb_sort_DropNonHostilesTooltip": "When enabled, non-hostile bounty targets located in systems with a size 4 or larger friendly market will be moved to the bottom of the list and colored red.\n\nE.G: If killing a bounty target would unavoidably trigger a war with a faction.", + + "mb_intelTutorial": "Select a bounty on the left to see more information.\nFilter the list by clicking \"Filters\" at the top of the list.\nIf the job terms are acceptable, click Accept in the bottom-right of the info panel.", # MagicAchievements @@ -331,6 +342,6 @@ "subsystemState_Cooldown": "", "subsystemState_OutOfRange": "OUT OF RANGE", "subsystemState_NoTarget": "NO TARGET", - "subsystemState_FluxCapped": "FLUX TOO HIGH", + "subsystemState_FluxCapped": "FLUX TOO HIGH" } } diff --git a/src/org/magiclib/bounty/MagicBountyIntel.java b/src/org/magiclib/bounty/MagicBountyIntel.java index 3e944d3b..8f2e6d0f 100644 --- a/src/org/magiclib/bounty/MagicBountyIntel.java +++ b/src/org/magiclib/bounty/MagicBountyIntel.java @@ -426,6 +426,13 @@ public void createSmallDescription(TooltipMakerAPI info, float width, float heig Misc.ucFirst(MagicBountyUtilsInternal.getPronoun(bounty.getCaptain())), bounty.getFleetSpawnLocation().getStarSystem().getNameWithLowercaseType()); break; + case Distance: + info.addPara(MagicTxt.getString("mb_distance"), + 10f, + Misc.getTextColor(), + Misc.getHighlightColor(), + Math.round(Misc.getDistanceLY(Global.getSector().getPlayerFleet(), bounty.getFleetSpawnLocation())) + ""); + break; default: info.addPara(MagicBountyUtilsInternal.createLocationEstimateText(bounty), 10f); break; @@ -459,8 +466,10 @@ public SectorEntityToken getMapLocation(SectorMapAPI map) { case Exact: case System: return hideoutLocation; -// case None: -// return null; NOPE, the icon should always be placed somewhere otherwise there is no way to get the location information again. + //case Vague: + case Distance: + case None: + return Global.getSector().getPlayerFleet(); default: // From PersonBountyIntel.getMapLocation Constellation c = hideoutLocation.getConstellation(); diff --git a/src/org/magiclib/bounty/intel/AssassinationMagicBountyInfo.kt b/src/org/magiclib/bounty/intel/AssassinationMagicBountyInfo.kt deleted file mode 100644 index c215da3d..00000000 --- a/src/org/magiclib/bounty/intel/AssassinationMagicBountyInfo.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.magiclib.bounty.intel - -import com.fs.starfarer.api.Global -import com.fs.starfarer.api.campaign.StarSystemAPI -import com.fs.starfarer.api.campaign.comm.IntelInfoPlugin -import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BreadcrumbSpecial -import com.fs.starfarer.api.ui.CustomPanelAPI -import com.fs.starfarer.api.ui.MapParams -import com.fs.starfarer.api.ui.TooltipMakerAPI -import com.fs.starfarer.api.util.Misc -import org.magiclib.bounty.MagicBountyLoader -import org.magiclib.bounty.MagicBountySpec -import org.magiclib.kotlin.ucFirst -import org.magiclib.util.MagicTxt -import java.awt.Color -import kotlin.math.roundToInt - -class AssassinationMagicBountyInfo(bountyKey: String, bountySpec: MagicBountySpec) : - MagicBountyInfo(bountyKey, bountySpec) { - override fun showTargetInfo(panel: CustomPanelAPI, width: Float, height: Float): TooltipMakerAPI { - val targetInfoTooltip = panel.createUIElement(width, height, true) - val childPanelWidth = width - 16f - val activeBountyLocal = activeBounty ?: return targetInfoTooltip - - if (bountySpec.job_show_captain) { - val portrait = targetInfoTooltip.beginImageWithText(getJobIcon(), 64f) - var displayName = activeBountyLocal.fleet.commander.nameString - val targetFirstName = activeBountyLocal.captain.name.first - val targetLastName = activeBountyLocal.captain.name.last - if (targetFirstName != null || targetLastName != null) { - displayName = "$targetFirstName $targetLastName" - if (targetFirstName == null || targetFirstName.isEmpty()) - displayName = targetLastName - else if (targetLastName == null || targetLastName.isEmpty()) - displayName = targetFirstName - } - portrait.addPara(displayName, activeBountyLocal.targetFactionTextColor, 0f) - portrait.addPara(activeBountyLocal.fleet.commander.rank.ucFirst(), 2f) - targetInfoTooltip.addImageWithText(0f) - } - - val location = getLocationIfBountyIsActive() - if (location is StarSystemAPI) { - val params = MapParams() - params.showSystem(location) - val w = targetInfoTooltip.widthSoFar - val h = (w / 1.6f).roundToInt().toFloat() - params.positionToShowAllMarkersAndSystems(false, w.coerceAtMost(h)) - params.filterData.fuel = true - params.arrows.add(IntelInfoPlugin.ArrowData(Global.getSector().playerFleet, location.center)) - - val map = targetInfoTooltip.createSectorMap(childPanelWidth, 200f, params, null) - targetInfoTooltip.addCustom(map, 4f) - - if (bountySpec.job_show_distance != MagicBountyLoader.ShowDistance.None) { - when (bountySpec.job_show_distance) { - MagicBountyLoader.ShowDistance.Exact -> targetInfoTooltip.addPara( - createLocationPreciseText(activeBounty!!), - 10f, - location.lightColor, - activeBounty!!.fleetSpawnLocation.starSystem.nameWithLowercaseType - ) - - MagicBountyLoader.ShowDistance.System -> targetInfoTooltip.addPara( - MagicTxt.getString("mb_distance_system"), - 10f, - arrayOf(Misc.getTextColor(), location.lightColor), - MagicTxt.getString("mb_distance_they"), - activeBounty!!.fleetSpawnLocation.starSystem.nameWithLowercaseType - ) - - else -> targetInfoTooltip.addPara( - createLocationEstimateText(activeBounty!!), - 10f, - location.lightColor, - BreadcrumbSpecial.getLocationDescription(activeBounty!!.fleetSpawnLocation, false) - ) - } - } - } else { - targetInfoTooltip.setButtonFontOrbitron20Bold() - targetInfoTooltip.addPara(MagicTxt.getString("mb_descLocationUnknown"), 3f, Color.RED).position.inTMid(2f) - } - - activeBounty?.let { - showFleet(targetInfoTooltip, childPanelWidth, it) - - if (it.spec.job_show_captain) { - targetInfoTooltip.addPara(MagicTxt.getString("mb_hvb_skillsHeader"), 8f) - targetInfoTooltip.addSkillPanel(it.captain, 2f) - } - } - - panel.addUIElement(targetInfoTooltip).inTL(0f, 0f) - - return targetInfoTooltip - } -} \ No newline at end of file diff --git a/src/org/magiclib/bounty/intel/BountyInfo.kt b/src/org/magiclib/bounty/intel/BountyInfo.kt index 98299e4c..72e02cbe 100644 --- a/src/org/magiclib/bounty/intel/BountyInfo.kt +++ b/src/org/magiclib/bounty/intel/BountyInfo.kt @@ -8,19 +8,23 @@ import com.fs.starfarer.api.impl.campaign.ids.Tags import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BreadcrumbSpecial import com.fs.starfarer.api.ui.TooltipMakerAPI import org.magiclib.bounty.ActiveBounty -import org.magiclib.bounty.intel.filters.LocationParam -import org.magiclib.bounty.ui.lists.filtered.Filterable -import org.magiclib.bounty.ui.lists.filtered.FilterableParam +import org.magiclib.bounty.ui.lists.sorted.Sortable +import org.magiclib.bounty.ui.lists.sorted.SortableParam import org.magiclib.util.MagicTxt +import java.awt.Color -interface BountyInfo : Filterable { +interface BountyInfo : Sortable { fun getBountyId(): String fun getBountyName(): String fun getBountyType(): String fun getBountyPayout(): Int fun getJobIcon(): String? fun getLocationIfBountyIsActive(): LocationAPI? - fun getSortIndex(): Int = 1 + fun getPlayerKnownDistanceIfBountyIsActive(): Float? + + fun getCustomPanelColor(): Color? + fun setCustomPanelColor(value: Color?) + fun addNotificationBulletpoints(info: TooltipMakerAPI) { } @@ -49,9 +53,9 @@ interface BountyInfo : Filterable { fun layoutPanel(tooltip: TooltipMakerAPI, width: Float, height: Float) - override fun getFilterData(): List> { + override fun getSorterData(): List> { return mutableListOf( - LocationParam(this) + //LocatioParam(this) ) } diff --git a/src/org/magiclib/bounty/intel/BountyListPanelPlugin.kt b/src/org/magiclib/bounty/intel/BountyListPanelPlugin.kt index 35496273..09434cff 100644 --- a/src/org/magiclib/bounty/intel/BountyListPanelPlugin.kt +++ b/src/org/magiclib/bounty/intel/BountyListPanelPlugin.kt @@ -7,16 +7,16 @@ import com.fs.starfarer.api.ui.TooltipMakerAPI import com.fs.starfarer.api.ui.UIPanelAPI import com.fs.starfarer.api.util.Misc import org.lwjgl.opengl.GL11 -import org.magiclib.bounty.intel.filters.LocationFilter +import org.magiclib.bounty.intel.sorters.TogglePrimarySorter import org.magiclib.bounty.ui.BaseUIPanelPlugin import org.magiclib.bounty.ui.lists.ListItemUIPanelPlugin -import org.magiclib.bounty.ui.lists.filtered.FilteredListPanelPlugin -import org.magiclib.bounty.ui.lists.filtered.ListFilter +import org.magiclib.bounty.ui.lists.sorted.ListSorter +import org.magiclib.bounty.ui.lists.sorted.SortedListPanelPlugin import org.magiclib.kotlin.setAlpha import org.magiclib.util.MagicTxt import java.awt.Color -class BountyListPanelPlugin(parentPanel: CustomPanelAPI) : FilteredListPanelPlugin(parentPanel) { +class BountyListPanelPlugin(parentPanel: CustomPanelAPI) : SortedListPanelPlugin(parentPanel) { override val rowWidth get() = panelWidth - 4f override val rowHeight = 68f @@ -27,11 +27,11 @@ class BountyListPanelPlugin(parentPanel: CustomPanelAPI) : FilteredListPanelPlug private var selectedItem: BountyInfo? = null private var finalItem: BountyInfo? = null - override fun getApplicableFilters(): List> { - return listOf(LocationFilter()) + override fun getApplicableSorters(): List> { + return listOf(TogglePrimarySorter()) } - override fun getFiltersFromItem(item: BountyInfo): List { + override fun getSortersFromItem(item: BountyInfo): List { return listOf(item.getBountyType()) } @@ -55,7 +55,7 @@ class BountyListPanelPlugin(parentPanel: CustomPanelAPI) : FilteredListPanelPlug } override fun layoutPanels(members: List): CustomPanelAPI { - finalItem = members.lastOrNull() + finalItem = members.maxByOrNull { it.getSortIndex() } val outerPanelLocal = super.layoutPanels(members) @@ -142,9 +142,12 @@ class BountyListPanelPlugin(parentPanel: CustomPanelAPI) : FilteredListPanelPlug ListItemUIPanelPlugin(item) { private var wasHovered: Boolean = false private var wasSelected: Boolean = false - var baseBgColor: Color = Color(255, 255, 255, 0) - var hoveredColor: Color = Misc.getBasePlayerColor().setAlpha(75) - var selectedColor: Color = Misc.getBasePlayerColor().setAlpha(125) + val defaultBgColor: Color = Color(255, 255, 255, 0) + val defaultHoveredColor: Color = Misc.getBasePlayerColor().setAlpha(75) + val defaultSelectedColor: Color = Misc.getBasePlayerColor().setAlpha(125) + var baseBgColor: Color = defaultBgColor + var hoveredColor: Color = defaultHoveredColor + var selectedColor: Color = defaultSelectedColor override var bgColor: Color = baseBgColor override fun layoutPanel(tooltip: TooltipMakerAPI): CustomPanelAPI { diff --git a/src/org/magiclib/bounty/intel/MagicBountyBoardProvider.kt b/src/org/magiclib/bounty/intel/MagicBountyBoardProvider.kt index e3a1f90f..e479964a 100644 --- a/src/org/magiclib/bounty/intel/MagicBountyBoardProvider.kt +++ b/src/org/magiclib/bounty/intel/MagicBountyBoardProvider.kt @@ -9,9 +9,6 @@ class MagicBountyBoardProvider: BountyBoardProvider { .entries .distinctBy { it.key } .map { (key, spec) -> - if (spec.job_type == MagicBountyLoader.JobType.Assassination) - AssassinationMagicBountyInfo(key, spec) - else MagicBountyInfo(key, spec) } } diff --git a/src/org/magiclib/bounty/intel/MagicBountyInfo.kt b/src/org/magiclib/bounty/intel/MagicBountyInfo.kt index c5a4d29e..0213db58 100644 --- a/src/org/magiclib/bounty/intel/MagicBountyInfo.kt +++ b/src/org/magiclib/bounty/intel/MagicBountyInfo.kt @@ -2,29 +2,31 @@ package org.magiclib.bounty.intel import com.fs.starfarer.api.Global import com.fs.starfarer.api.campaign.LocationAPI +import com.fs.starfarer.api.campaign.SectorEntityToken import com.fs.starfarer.api.campaign.StarSystemAPI import com.fs.starfarer.api.campaign.comm.IntelInfoPlugin -import com.fs.starfarer.api.campaign.rules.MemoryAPI import com.fs.starfarer.api.fleet.FleetMemberAPI import com.fs.starfarer.api.impl.campaign.ids.Factions +import com.fs.starfarer.api.impl.campaign.procgen.Constellation import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BreadcrumbSpecial -import com.fs.starfarer.api.ui.CustomPanelAPI -import com.fs.starfarer.api.ui.LabelAPI -import com.fs.starfarer.api.ui.MapParams -import com.fs.starfarer.api.ui.TooltipMakerAPI +import com.fs.starfarer.api.ui.* import com.fs.starfarer.api.util.Misc +import org.lwjgl.util.vector.Vector2f import org.magiclib.bounty.ActiveBounty import org.magiclib.bounty.MagicBountyCoordinator import org.magiclib.bounty.MagicBountyLoader.* import org.magiclib.bounty.MagicBountySpec import org.magiclib.bounty.MagicBountyUtilsInternal import org.magiclib.bounty.ui.InteractiveUIPanelPlugin +import org.magiclib.kotlin.interpolateColor import org.magiclib.kotlin.setAlpha +import org.magiclib.kotlin.ucFirst import org.magiclib.util.MagicCampaign import org.magiclib.util.MagicTxt import java.awt.Color import kotlin.math.ceil import kotlin.math.roundToInt +import kotlin.text.isEmpty open class MagicBountyInfo(val bountyKey: String, val bountySpec: MagicBountySpec) : BountyInfo { val activeBounty: ActiveBounty? @@ -32,6 +34,12 @@ open class MagicBountyInfo(val bountyKey: String, val bountySpec: MagicBountySpe var holdingPanel: CustomPanelAPI? = null var panelThatCanBeRemoved: CustomPanelAPI? = null + var customColor: Color? = Color(255, 255, 255, 0) + override fun getCustomPanelColor(): Color? = customColor + override fun setCustomPanelColor(value: Color?) { + customColor = value + } + override fun getBountyId(): String { return bountyKey } @@ -60,18 +68,47 @@ open class MagicBountyInfo(val bountyKey: String, val bountySpec: MagicBountySpe return activeBounty?.fleetSpawnLocation?.containingLocation } + override fun getPlayerKnownDistanceIfBountyIsActive(): Float? { + getLocationIfBountyIsActive() ?: return null + val bounty = activeBounty ?: return null + val playerFleet = Global.getSector().playerFleet + + return when (bountySpec.job_show_distance) { + //ShowDistance.Vague, + ShowDistance.None-> null + + ShowDistance.Distance, + ShowDistance.Exact, + ShowDistance.System -> + Misc.getDistanceLY(playerFleet, bounty.fleetSpawnLocation) + + else -> { + val token = bounty.fleet.constellation?.let { createConstellationCenterToken(it) } ?: bounty.fleetSpawnLocation + Misc.getDistanceLY(playerFleet, token) + } + } + } + + private var sortIndexOffset: Int = 0 + override fun getSortIndexOffset(): Int = sortIndexOffset + override fun setSortIndexOffset(value: Int) { + sortIndexOffset = value + } + override fun getSortIndex(): Int { - return when (activeBounty?.stage) { + val baseIndex = when (activeBounty?.stage) { ActiveBounty.Stage.Accepted -> 0 - ActiveBounty.Stage.NotAccepted -> 1 - ActiveBounty.Stage.Succeeded -> 3 - ActiveBounty.Stage.ExpiredAfterAccepting -> 4 - ActiveBounty.Stage.ExpiredWithoutAccepting -> 4 - ActiveBounty.Stage.FailedSalvagedFlagship -> 4 - ActiveBounty.Stage.EndedWithoutPlayerInvolvement -> 4 - ActiveBounty.Stage.Dismissed -> 4 - else -> 1 + ActiveBounty.Stage.NotAccepted -> 100000 + ActiveBounty.Stage.Succeeded -> 300000 + ActiveBounty.Stage.ExpiredAfterAccepting -> 400000 + ActiveBounty.Stage.ExpiredWithoutAccepting -> 400000 + ActiveBounty.Stage.FailedSalvagedFlagship -> 400000 + ActiveBounty.Stage.EndedWithoutPlayerInvolvement -> 400000 + ActiveBounty.Stage.Dismissed -> 400000 + else -> 100000 } + + return baseIndex + sortIndexOffset } override fun notifyWhenAvailable(): Boolean { @@ -226,6 +263,15 @@ open class MagicBountyInfo(val bountyKey: String, val bountySpec: MagicBountySpe plugin.baseBgColor = Misc.getDarkHighlightColor().setAlpha(45) plugin.hoveredColor = Misc.getDarkHighlightColor().setAlpha(75) plugin.selectedColor = Misc.getDarkHighlightColor().setAlpha(125) + } else if (customColor != null) { + val t = customColor!!.alpha / 255f + plugin.baseBgColor = customColor!!.setAlpha(customColor!!.alpha / 2) + plugin.hoveredColor = plugin.defaultHoveredColor.interpolateColor(customColor!!, t) + plugin.selectedColor = plugin.defaultSelectedColor.interpolateColor(customColor!!, t) + } else { + plugin.baseBgColor = plugin.defaultBgColor + plugin.hoveredColor = plugin.defaultHoveredColor + plugin.selectedColor = plugin.defaultSelectedColor } } @@ -278,13 +324,33 @@ open class MagicBountyInfo(val bountyKey: String, val bountySpec: MagicBountySpe BountyBoardIntelPlugin.refreshPanel(this) } } else if (activeBountyLocal.stage == ActiveBounty.Stage.Accepted) { - val courseButton = - actionTooltip.addButton(MagicTxt.getString("mb_plot_course"), null, rightPanelWidth, 24f, 0f) - rightPanelPlugin.addButton(courseButton) { - courseButton.isChecked = false - Global.getSector().layInCourseFor( - Misc.getDistressJumpPoint(activeBountyLocal.fleet.containingLocation as StarSystemAPI) - ) + + //Add plot course button if there is a clear destination + val dis = activeBountyLocal.spec.job_show_distance + if(dis != ShowDistance.Distance && dis != ShowDistance.None// && dis != ShowDistance.Vague + ) { + + var location: SectorEntityToken? = null + if(dis == ShowDistance.Exact || dis == ShowDistance.System) { + val system = activeBountyLocal.fleet.containingLocation as? StarSystemAPI + if(system != null) + location = Misc.getDistressJumpPoint(system) + } else { + val constellation = activeBountyLocal.fleet.constellation + if(constellation != null) + location = createConstellationCenterToken(constellation) + } + + if(location != null) { + val courseButton = + actionTooltip.addButton(MagicTxt.getString("mb_plot_course"), null, rightPanelWidth, 24f, 0f) + rightPanelPlugin.addButton(courseButton) { + courseButton.isChecked = false + Global.getSector().layInCourseFor( + location + ) + } + } } } @@ -455,38 +521,115 @@ open class MagicBountyInfo(val bountyKey: String, val bountySpec: MagicBountySpe val targetInfoTooltip = panel.createUIElement(width, height, true) val childPanelWidth = width - 16f + val bounty = activeBounty!! + + if (bountySpec.job_type == JobType.Assassination && bountySpec.job_show_captain) { + val portrait = targetInfoTooltip.beginImageWithText(getJobIcon(), 64f) + var displayName = bounty.fleet.commander.nameString + val targetFirstName = bounty.captain.name.first + val targetLastName = bounty.captain.name.last + if (targetFirstName != null || targetLastName != null) { + displayName = "$targetFirstName $targetLastName" + if (targetFirstName == null || targetFirstName.isEmpty()) + displayName = targetLastName + else if (targetLastName == null || targetLastName.isEmpty()) + displayName = targetFirstName + } + portrait.addPara(displayName, bounty.targetFactionTextColor, 0f) + portrait.addPara(bounty.fleet.commander.rank.ucFirst(), 2f) + targetInfoTooltip.addImageWithText(0f) + } + val location = getLocationIfBountyIsActive() - if (location is StarSystemAPI) { + if (location is StarSystemAPI + && bountySpec.job_show_distance != ShowDistance.None) { + + fun setupSystemParams(params: MapParams, location: StarSystemAPI) { + params.showSystem(location) + params.arrows.add(IntelInfoPlugin.ArrowData(Global.getSector().playerFleet, location.center)) + } + val params = MapParams() - params.showSystem(location) val w = targetInfoTooltip.widthSoFar val h = (w / 1.6f).roundToInt().toFloat() - params.positionToShowAllMarkersAndSystems(false, w.coerceAtMost(h)) - params.filterData.fuel = true - params.arrows.add(IntelInfoPlugin.ArrowData(Global.getSector().playerFleet, location.center)) - val map = targetInfoTooltip.createSectorMap(childPanelWidth, 200f, params, null) - targetInfoTooltip.addCustom(map, 2f) + when (bountySpec.job_show_distance) { + ShowDistance.Exact -> { + setupSystemParams(params, location) - if (bountySpec.job_show_distance != ShowDistance.None) { - val bounty = activeBounty!! - when (bountySpec.job_show_distance) { - ShowDistance.Exact -> targetInfoTooltip.addPara( + targetInfoTooltip.addPara( createLocationPreciseText(bounty), 10f, location.lightColor, bounty.fleetSpawnLocation.starSystem.nameWithLowercaseType ) + } - ShowDistance.System -> targetInfoTooltip.addPara( + ShowDistance.System -> { + setupSystemParams(params, location) + + targetInfoTooltip.addPara( MagicTxt.getString("mb_distance_system"), 10f, arrayOf(Misc.getTextColor(), location.lightColor), MagicTxt.getString("mb_distance_they"), bounty.fleetSpawnLocation.starSystem.nameWithLowercaseType ) + } + + /*ShowDistance.Vague -> { + params.filterData.names = false + + val distance = bounty.fleetSpawnLocation.containingLocation.location.length() + var vague = MagicTxt.getString("mb_distance_core") + if (distance > MagicVariables.getSectorSize() * 0.6f) { + vague = MagicTxt.getString("mb_distance_far") + } else if (distance > MagicVariables.getSectorSize() * 0.33f) { + vague = MagicTxt.getString("mb_distance_close") + } + targetInfoTooltip.addPara( + MagicTxt.getString("mb_distance_vague"), + 10f, + Misc.getTextColor(), + Misc.getHighlightColor(), + vague + ) + }*///Commented out due to seeming to be a bad mechanic with the current implementation of the bounty board. Given pre-existing use of it in some mods (such as Seeker), enabling this on an update may cause issues for some users. + + ShowDistance.Distance -> { + params.filterData.names = false + + val distanceLY = Misc.getDistanceLY(Global.getSector().playerFleet, bounty.fleetSpawnLocation).roundToInt() + targetInfoTooltip.addPara( + MagicTxt.getString("mb_distance"), + 10f, + Misc.getTextColor(), + Misc.getHighlightColor(), + distanceLY.toString() + ) + } + + else -> { + val constellation = location.constellation + if(constellation != null) { + //Show constellation + params.filterData.constellations = true + params.filterData.names = false + params.showConsellations = setOf(constellation) + params.smallConstellations = true + + //Point towards center of constellation + val token = createConstellationCenterToken(constellation) + if (token != null) { + params.arrows.add(IntelInfoPlugin.ArrowData(Global.getSector().playerFleet, token)) + params.markers = listOf(MarkerData(token.location, Global.getSector().hyperspace)) + } + + } else { + setupSystemParams(params, location) + } - else -> targetInfoTooltip.addPara( + targetInfoTooltip.addPara( createLocationEstimateText(bounty), 10f, location.lightColor, @@ -494,6 +637,15 @@ open class MagicBountyInfo(val bountyKey: String, val bountySpec: MagicBountySpe ) } } + + params.positionToShowAllMarkersAndSystems(false, w.coerceAtMost(h)) + params.markers = null//Markers are purely for positioning the camera. Don't render them. + + params.filterData.fuel = true + + val map = targetInfoTooltip.createSectorMap(childPanelWidth, 200f, params, null) + targetInfoTooltip.addCustom(map, 2f) + } else { targetInfoTooltip.setButtonFontOrbitron20Bold() targetInfoTooltip.addPara(MagicTxt.getString("mb_descLocationUnknown"), 3f, Color.RED).position.inTMid(2f) @@ -793,4 +945,25 @@ fun ActiveBounty.calculateCreditReward(): Float? { MagicBountyCoordinator.getInstance().preScalingCreditRewardMultiplier, MagicBountyCoordinator.getInstance().postScalingCreditRewardMultiplier ) -} \ No newline at end of file +} + +fun getConstellationSystemsCenter(constellation: Constellation): Vector2f? { + val systems = constellation.systems + if (systems.isEmpty()) return null + + val total = Vector2f(0f, 0f) + for (system in systems) { + Vector2f.add(total, system.location, total) + } + + total.scale(1f / systems.size) + return total +} + +fun createConstellationCenterToken(constellation: Constellation): SectorEntityToken? { + val center = getConstellationSystemsCenter(constellation) ?: return null + val hyperspace = Global.getSector().hyperspace + + // Create a temporary token at the calculated position + return hyperspace.createToken(center.x, center.y) +} diff --git a/src/org/magiclib/bounty/intel/filters/LocationParam.kt b/src/org/magiclib/bounty/intel/filters/LocationParam.kt index e3eab555..f5563fc4 100644 --- a/src/org/magiclib/bounty/intel/filters/LocationParam.kt +++ b/src/org/magiclib/bounty/intel/filters/LocationParam.kt @@ -1,4 +1,4 @@ -package org.magiclib.bounty.intel.filters +/*package org.magiclib.bounty.intel.filters import com.fs.starfarer.api.Global import com.fs.starfarer.api.campaign.LocationAPI @@ -191,4 +191,4 @@ class LocationFilter : ListFilter { if (ly < 0) ly = 0f return ly } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/org/magiclib/bounty/intel/filters/PayoutParam.kt b/src/org/magiclib/bounty/intel/filters/PayoutParam.kt index ba9bd54c..30231af0 100644 --- a/src/org/magiclib/bounty/intel/filters/PayoutParam.kt +++ b/src/org/magiclib/bounty/intel/filters/PayoutParam.kt @@ -1,4 +1,4 @@ -package org.magiclib.bounty.intel.filters +/*package org.magiclib.bounty.intel.filters import com.fs.starfarer.api.Global import com.fs.starfarer.api.ui.CustomPanelAPI @@ -106,4 +106,4 @@ class PayoutFilter : ListFilter { } override fun isActive(): Boolean = payoutMinimum > -1 -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/org/magiclib/bounty/intel/sorters/TogglePrimarySorter.kt b/src/org/magiclib/bounty/intel/sorters/TogglePrimarySorter.kt new file mode 100644 index 00000000..5f6c9cf3 --- /dev/null +++ b/src/org/magiclib/bounty/intel/sorters/TogglePrimarySorter.kt @@ -0,0 +1,205 @@ +package org.magiclib.bounty.intel.sorters + +import com.fs.starfarer.api.Global +import com.fs.starfarer.api.campaign.LocationAPI +import com.fs.starfarer.api.ui.ButtonAPI +import com.fs.starfarer.api.ui.CustomPanelAPI +import com.fs.starfarer.api.ui.TooltipMakerAPI +import org.magiclib.bounty.intel.BountyInfo +import org.magiclib.bounty.intel.MagicBountyInfo +import org.magiclib.bounty.ui.InteractiveUIPanelPlugin +import org.magiclib.bounty.ui.lists.sorted.ListSorter +import org.magiclib.bounty.ui.lists.sorted.Sortable +import org.magiclib.internalextensions.addTooltip +import org.magiclib.kotlin.getMarketsInLocation +import org.magiclib.util.MagicTxt +import java.awt.Color + +class TogglePrimarySorter : ListSorter { + + private var nonEnemyToBottom = true + + enum class Order { + ASCENDING, + DESCENDING, + } + private var orderBy = Order.ASCENDING + fun getOrderBy(): Order = orderBy + fun setOrderBy(value: Order) { + orderBy = value + } + + enum class SortingMethod { + ALPHABETICAL, + CREDITS, + KNOWNDISTANCE, + FIRSTCREATED, + } + private var sortBy = SortingMethod.FIRSTCREATED + fun getSortBy(): SortingMethod = sortBy + fun setSortBy(value: SortingMethod) { + sortBy = value + } + + override fun createPanel( + tooltip: TooltipMakerAPI, + width: Float, + lastItems: List> + ): CustomPanelAPI { + val sorterPlugin = InteractiveUIPanelPlugin() + val sorterPanel = Global.getSettings().createCustom(width, tooltip.heightSoFar, sorterPlugin) + + //checkbox tooltip + val toggleGroupTooltip = sorterPanel.createUIElement(width, sorterPanel.position.height, false) + + + val orderTogglesData = listOf( + MagicTxt.getString("mb_sort_Ascending") to Order.ASCENDING, + MagicTxt.getString("mb_sort_Descending") to Order.DESCENDING + ) + + var currentOrderSelected: ButtonAPI? = null + + orderTogglesData.forEachIndexed { index, (label, order) -> + val checkbox = toggleGroupTooltip.addCheckbox(20f, 16f, label, null, ButtonAPI.UICheckboxSize.SMALL, if(index == 0) 0f else 4f) + + // Check the current order by default + if (orderBy == order) { + checkbox.isChecked = true + currentOrderSelected = checkbox + } + + sorterPlugin.addCheckbox(checkbox) { checked -> + if (checked) { + currentOrderSelected?.let { if (it != checkbox) it.isChecked = false } + currentOrderSelected = checkbox + orderBy = order + } else { + currentOrderSelected?.isChecked = true + } + } + } + + toggleGroupTooltip.addSpacer(12f) + + val togglesData = listOf( + MagicTxt.getString("mb_sort_Alphabetical") to SortingMethod.ALPHABETICAL, + MagicTxt.getString("mb_sort_Distance") to SortingMethod.KNOWNDISTANCE, + MagicTxt.getString("mb_sort_Credits") to SortingMethod.CREDITS, + MagicTxt.getString("mb_sort_FirstCreated") to SortingMethod.FIRSTCREATED, + ) + + var currentSelected: ButtonAPI? = null + + togglesData.forEachIndexed { index, (label, method) -> + val checkbox = toggleGroupTooltip.addCheckbox(20f, 16f, label, null, ButtonAPI.UICheckboxSize.SMALL, if(index == 0) 0f else 4f) + if (sortBy == method) { + checkbox.isChecked = true + currentSelected = checkbox + } + sorterPlugin.addCheckbox(checkbox) { checked -> + if (checked) { + currentSelected?.let { if (it != checkbox) it.isChecked = false } + currentSelected = checkbox + sortBy = method + } else { + currentSelected?.isChecked = true + } + } + } + + + val nonEnemyToBottomButton = toggleGroupTooltip.addCheckbox(20f, 16f, MagicTxt.getString("mb_sort_DropNonHostiles"), null, ButtonAPI.UICheckboxSize.SMALL, toggleGroupTooltip.heightSoFar - toggleGroupTooltip.position.height - 16f - 4f) + toggleGroupTooltip.addTooltip(nonEnemyToBottomButton, TooltipMakerAPI.TooltipLocation.BELOW, 600f) { + it.addPara(MagicTxt.getString("mb_sort_DropNonHostilesTooltip"), 0f) + } + nonEnemyToBottomButton.isChecked = nonEnemyToBottom + sorterPlugin.addCheckbox(nonEnemyToBottomButton) { checked -> + nonEnemyToBottom = checked + } + + sorterPanel.addUIElement(toggleGroupTooltip).inTMid(2f) + tooltip.addCustomDoNotSetPosition(sorterPanel) + + return sorterPanel + } + + override fun saveToPersistentData() { + Global.getSector().persistentData["MagicLib.LocationSorter.sortBy"] = sortBy + Global.getSector().persistentData["MagicLib.LocationSorter.orderBy"] = orderBy + Global.getSector().persistentData["MagicLib.LocationSorter.nonEnemyToBottom"] = nonEnemyToBottom + } + + override fun loadFromPersistentData(members: List) { + if (Global.getSector().persistentData.containsKey("MagicLib.LocationSorter.sortBy")) + sortBy = Global.getSector().persistentData["MagicLib.LocationSorter.sortBy"] as SortingMethod + if (Global.getSector().persistentData.containsKey("MagicLib.LocationSorter.orderBy")) + orderBy = Global.getSector().persistentData["MagicLib.LocationSorter.orderBy"] as Order + if (Global.getSector().persistentData.containsKey("MagicLib.LocationSorter.nonEnemyToBottom")) + nonEnemyToBottom = Global.getSector().persistentData["MagicLib.LocationSorter.nonEnemyToBottom"] as Boolean + + sortMembers(members) + } + + fun sortMembers(items: List) { + items.forEach { it.setSortIndexOffset(0) } + + val sorted = when (getSortBy()) { + SortingMethod.CREDITS -> + items.sortedBy { it.getBountyPayout() } + + SortingMethod.KNOWNDISTANCE -> + items.sortedBy { it.getPlayerKnownDistanceIfBountyIsActive() ?: Float.MAX_VALUE } + + SortingMethod.FIRSTCREATED -> + items.sortedBy { (it as? MagicBountyInfo)?.activeBounty?.bountyCreatedTimestamp }.reversed() + + SortingMethod.ALPHABETICAL -> + items.sortedBy { it.getBountyName() } + }.toMutableList() + + // Reverse if descending + if (getOrderBy() == Order.DESCENDING) { + sorted.reverse() + } + + sorted.forEach { it.setCustomPanelColor(null) } + if (nonEnemyToBottom) { + val sector = Global.getSector() + val playerFaction = sector.playerFaction + + val (keep, moveToBottom) = sorted.partition { entry -> + val bounty = (entry as? MagicBountyInfo)?.activeBounty + ?: return@partition true + + val faction = bounty.targetFaction ?: return@partition true + val system = bounty.fleetSpawnLocation.starSystem ?: return@partition true + + val isCoreFaction = faction.isShowInIntelTab + val isNotEnemy = !faction.isHostileTo(playerFaction) + + val hasLargeMarketInSystem = system.getMarketsInLocation(faction.id)?.any { market -> + market.factionId == faction.id && market.size >= 4 + } == true + + // keep everything that does NOT match all conditions + !(isCoreFaction && isNotEnemy && hasLargeMarketInSystem) + } + + sorted.clear() + sorted.addAll(keep) + sorted.addAll(moveToBottom) + moveToBottom.forEach { it.setCustomPanelColor(Color(255, 0, 0, 102)) } + } + + // Assign sortIndexOffset + sorted.forEachIndexed { index, item -> + item.setSortIndexOffset(index) + } + + } + + override fun isActive(): Boolean { + return true + } +} \ No newline at end of file diff --git a/src/org/magiclib/bounty/ui/lists/sorted/ListSort.kt b/src/org/magiclib/bounty/ui/lists/sorted/ListSort.kt new file mode 100644 index 00000000..87d2abf6 --- /dev/null +++ b/src/org/magiclib/bounty/ui/lists/sorted/ListSort.kt @@ -0,0 +1,25 @@ +package org.magiclib.bounty.ui.lists.sorted + +import com.fs.starfarer.api.ui.CustomPanelAPI +import com.fs.starfarer.api.ui.TooltipMakerAPI + +interface ListSorter, V> { + fun createPanel(tooltip: TooltipMakerAPI, width: Float, lastItems: List>): CustomPanelAPI + + fun saveToPersistentData() + fun loadFromPersistentData(members: List) + + fun isActive(): Boolean +} + +abstract class SortableParam(val item: T) { + abstract fun getData(): V? +} + +interface Sortable { + fun getSorterData(): List> + + fun getSortIndex(): Int = 1 + fun getSortIndexOffset(): Int + fun setSortIndexOffset(value: Int) +} \ No newline at end of file diff --git a/src/org/magiclib/bounty/ui/lists/sorted/SortedListPanelPlugin.kt b/src/org/magiclib/bounty/ui/lists/sorted/SortedListPanelPlugin.kt new file mode 100644 index 00000000..36c4dd2d --- /dev/null +++ b/src/org/magiclib/bounty/ui/lists/sorted/SortedListPanelPlugin.kt @@ -0,0 +1,134 @@ +package org.magiclib.bounty.ui.lists.sorted + +import com.fs.starfarer.api.ui.* +import org.magiclib.bounty.ui.ButtonHandler +import org.magiclib.bounty.ui.InteractiveUIPanelPlugin +import org.magiclib.bounty.ui.lists.ListUIPanelPlugin +import org.magiclib.util.MagicTxt + +abstract class SortedListPanelPlugin>(parentPanel: CustomPanelAPI) : + ListUIPanelPlugin(parentPanel) { + var sorterButton: ButtonAPI? = null + var sorterContainerPanel: CustomPanelAPI? = null + var sortersForItems: List> = getApplicableSorters() + + protected abstract fun getApplicableSorters(): List> + + override fun layoutPanels(members: List): CustomPanelAPI { + if (outerPanel != null) { + outerTooltip!!.removeComponent(innerPanel) + outerPanel!!.removeComponent(outerTooltip) + clearItems() + } + + val outerPanelLocal = outerPanel ?: parentPanel.createCustomPanel(panelWidth, panelHeight, this) + outerPanel = outerPanelLocal + + sortersForItems.forEach { it.loadFromPersistentData(members) } + var validMembers = members.filter { shouldMakePanelForItem(it) } + lastMembers = validMembers + //validMembers = sortMembers(validMembers) + validMembers = validMembers.sortedBy { it.getSortIndex() } + + val outerTooltipLocal = outerPanelLocal.createUIElement(panelWidth, panelHeight, false) + outerTooltip = outerTooltipLocal + + createListHeader(outerTooltipLocal) + + val buttonHeight = 20f + val sorterButtonLocal = outerTooltipLocal.addButton( + sorterButtonText(), + null, + panelWidth - 4f, + buttonHeight, + 2f + ) + sorterButton = sorterButtonLocal + this.buttons[sorterButtonLocal] = SorterButtonHandler() + sorterButtonLocal.position.inTMid(22f) + + val listHeight = panelHeight - buttonHeight - 22f + val holdingPanel = outerPanelLocal.createCustomPanel(panelWidth, listHeight, null) + innerPanel = holdingPanel + + val scrollerTooltip: TooltipMakerAPI = holdingPanel.createUIElement(panelWidth, listHeight, true) + val scrollingPanel: CustomPanelAPI = + holdingPanel.createCustomPanel(panelWidth, getListHeight(validMembers.size) + buttonHeight + 22f, null) + val tooltip: TooltipMakerAPI = + scrollingPanel.createUIElement(panelWidth, getListHeight(validMembers.size) + buttonHeight + 22f, false) + + var lastItem: UIPanelAPI? = null + validMembers + .map { it to createPanelForItem(tooltip, it) } + .filter { (_, rowPlugin) -> rowPlugin != null } + .forEach { (item, rowPlugin) -> + lastItem = placeItem(tooltip, rowPlugin!!, lastItem) + } + + scrollingPanel.addUIElement(tooltip).inTL(0f, 0f) + scrollerTooltip.addCustom(scrollingPanel, 0f).position.inTL(0f, 0f) + holdingPanel.addUIElement(scrollerTooltip).inTL(0f, 0f) + outerTooltipLocal.addCustom(holdingPanel, 0f).position.belowMid(sorterButtonLocal, 2f) + outerPanelLocal.addUIElement(outerTooltipLocal).inTL(0f, 0f) + this.parentPanel.addComponent(outerPanelLocal).inTL(0f, 0f) + scroller = scrollerTooltip.externalScroller + + return outerPanelLocal + } + + protected fun createSorterPanel() { + val sorterContainerPanelPlugin = InteractiveUIPanelPlugin() + sorterContainerPanelPlugin.renderBackground = true + sorterContainerPanelPlugin.eatAllClicks = true + + val sorterContainerPanelLocal = + outerPanel!!.createCustomPanel(panelWidth, panelHeight * 0.33f, sorterContainerPanelPlugin) + sorterContainerPanel = sorterContainerPanelLocal + + val sorterContainerTooltip = sorterContainerPanelLocal.createUIElement(panelWidth, panelHeight * 0.33f, true) + sorterContainerTooltip.addSpacer(panelHeight * 0.33f) // sorterContainerTooltip's height is always forced to 0f for some reason which makes all contents invisible. Calling this function allows the contents to be seen and keeps track of height. + + var lastItem: UIComponentAPI? = null + + sortersForItems.forEach { + val sorterPanel = it.createPanel(sorterContainerTooltip, panelWidth - 4f, lastMembers!!) + if (lastItem != null) { + sorterPanel.position.belowMid(lastItem, 4f).setXAlignOffset(-3f) + } else { + sorterPanel.position.inTMid(4f).setXAlignOffset(-3f) + } + lastItem = sorterPanel + } + + sorterContainerPanelLocal.addUIElement(sorterContainerTooltip).inBMid(4f) + + outerPanel!!.addComponent(sorterContainerPanelLocal).inTMid(46f) + } + + fun closeSorterPanel() { + sortersForItems.forEach { it.saveToPersistentData() } + outerPanel!!.removeComponent(sorterContainerPanel) + sorterContainerPanel = null + + layoutPanels() + } + + protected abstract fun getSortersFromItem(item: T): List + + inner class SorterButtonHandler : ButtonHandler() { + override fun onClicked() { + sorterButton!!.isChecked = false + if (this@SortedListPanelPlugin.sorterContainerPanel == null) { + sorterButton!!.text = MagicTxt.getString("mb_confirm") + createSorterPanel() + } else { + sorterButton!!.text = + sorterButtonText() + closeSorterPanel() + } + } + } + + private fun sorterButtonText() = + MagicTxt.getString("mb_sort") +} \ No newline at end of file