diff --git a/1.1.0 Changelog.txt b/1.1.0 Changelog.txt new file mode 100644 index 0000000..87bb350 --- /dev/null +++ b/1.1.0 Changelog.txt @@ -0,0 +1,31 @@ +Changes and additions: + +1. Re-designed and rewritten mod internals - this update is most certainly guaranteed to break your saves. + +2. Reshuffled and removed some superficial settings. + +3. Production report message icon no longer clickable and doesn't take you to intel tab anymore. + +4. Production now also has a chance to trigger fatal incidents which result in a loss of crew, similar to breakdowns. + +5. Redone pretty much every tooltip - hullmods, ability, report. + +6. Removed civgrade and cargo restrictions, replaced with CR malus. + +7. Added control panel, which allows for toggling of individual production capacities. No more setting repairs suspension! + +8. Added hull parts production capacity - can produce some derelict and low tech frigates at a steep cost, requires having Salvage Rigs. + +9. Added plausible way of obtaining hullmods - salvage from derelict Motherships. Hullmods are now unobtainable via normal drop or markets. + +10. Included interactive production values spreadsheet in release package - might be a help in making settings adjustments. + +Tracked issues: + +1. Mouseover events in nested scrollable areas glitch - fixed by Alex in next release. + +2. Ship icon perma-mods widget rendering glitch - fixed by Alex in next release. + +3. Unsatisfactory hull parts icon - work pending due to lack of artistic skill. + +4. Unsatisfactory sound effects qualtiy - work pending due to lack of sound design skill. \ No newline at end of file diff --git a/Conversion Calculations.xlsx b/Conversion Calculations.xlsx new file mode 100644 index 0000000..296dd6d Binary files /dev/null and b/Conversion Calculations.xlsx differ diff --git a/data/campaign/abilities.csv b/data/campaign/abilities.csv new file mode 100644 index 0000000..3167eef --- /dev/null +++ b/data/campaign/abilities.csv @@ -0,0 +1,2 @@ +name,id,type,tags,activationDays,activationCooldown,durationDays,deactivationDays,deactivationCooldown,unlockedAtStart,defaultForAIFleet,musicSuppression,uiOn,uiOff,uiLoop,worldOn,worldOff,worldLoop,icon,plugin,ai,desc,sortOrder +"Forge Production",forge_production,TOGGLE,,0.5,,,0.25,1,FALSE,,,forge_ability_enable,forge_ability_disable,,,,forge_ability_loop,graphics/forgprod/icons/abilities/forge_production.png,forgprod.abilities.ForgeProductionAbility,,"Control forge production of your fleet.",868 diff --git a/data/campaign/rules.csv b/data/campaign/rules.csv new file mode 100644 index 0000000..c12a3da --- /dev/null +++ b/data/campaign/rules.csv @@ -0,0 +1,35 @@ +id,trigger,conditions,script,text,options,notes +"#Mothership special salvage interaction",,,,,, +forgprod_salvage_start,PopulateSalvageOptions2,"$customType == derelict_mothership +!$mothershipInvestigated +!$hardwareRecovered",,"Exploration crews also report of several chambers left relatively intact deep within mothership hull, which may warrant further investigation.","20:forgprodInvestigate:Investigate the compartments", +forgprod_salvage_recurrent,PopulateSalvageOptions2,"$customType == derelict_mothership +$mothershipInvestigated +!$hardwareRecovered",,,"20:forgprodConsiderRecovery:Consider recovery options", +forgprod_salvage_consider,DialogOptionSelected,"$option == forgprodConsiderRecovery +!$hardwareRecovered","ForgprodShowRetrievalCost +FireBest ForgprodTryEquipmentCheck +SetShortcut forgprodReconsider ESCAPE","It appears that sizable portions of the original autoforge compartment are still operational, rusted and worn out by time as they may be. + +Given necessary preparations, it might be possible to recover workable production modules as-is instead of scuttling them for machinery.","1:forgprodProceedRecovery:Commence recovery operation +2:forgprodReconsider:Reconsider", +forgprod_salvage_start_selected,DialogOptionSelected,"$option == forgprodInvestigate +!$hardwareRecovered","ForgprodShowRetrievalCost +FireBest ForgprodTryEquipmentCheck +SetShortcut forgprodReconsider ESCAPE +$mothershipInvestigated = true","Upon thorough examination of undamaged sections, exploration team's officer concludes that most of the hardware in sight is decayed beyond any practical hope of recovery. + +However, there's one important exception: it appears that sizable portions of the original autoforge compartment are still operational, rusted and worn out by time as they may be. Given necessary preparations, it might be possible to recover workable production modules as-is instead of scuttling them for machinery.","1:forgprodProceedRecovery:Commence recovery operation +2:forgprodReconsider:Reconsider", +forgprod_equipment_check,ForgprodTryEquipmentCheck,"!ForgprodCheckEquipment playerHasEquipment","SetTooltip forgprodProceedRecovery ""Commencing operation without sufficient equipment present would be a pointless endeavour."" +SetEnabled forgprodProceedRecovery false",,, +forgprod_proceed_recovery_selected,DialogOptionSelected,"$option == forgprodProceedRecovery",ForgprodOperationSuccessSFX,"After a rather lengthy wait, head of recovery operation reports back to the bridge. The operation can be considered a success: salvage crews were able to decouple autoforge installations and are now in the process of packaging the hardware. + +Nevertheless, close examinations of forge machinery revealed that these modules are decidedly unsuitable for use without external facilities such as those native to the mothership's hull - at least, in their current form. Your chief engineer notes that the modules, arcane as they might be, can theoretically be disassembled and reassembled in a form more accomodating to whatever ship happened to be available - given, of course, inevitable size constraints and the fact that performance of makeshift versions is likely to be abysmally low.",0:forgprodRecoveryRewards:Continue, +forgprod_recovery_rewards,DialogOptionSelected,"$option == forgprodRecoveryRewards","ForgprodAbilityUnlock +ForgprodRecoveryRewards +$hardwareRecovered = true",,0:forgprodRecoveryContinue:Continue, +forgprod_recovery_continue_selected,DialogOptionSelected,"$option == forgprodRecoveryContinue","FireAll PopulateSalvageOptions2",,, +forgprod_reconsider_selected,DialogOptionSelected,"$option == forgprodReconsider +","FireAll PopulateSalvageOptions2",,, +"#END Mothership special salvage interaction",,,,,, diff --git a/data/config/settings.json b/data/config/settings.json new file mode 100644 index 0000000..a932e7b --- /dev/null +++ b/data/config/settings.json @@ -0,0 +1,55 @@ +{ + + "graphics": { + + "forgprod_intel": { + + "forge_production_report": "graphics/forgprod/icons/intel/forge_production_report.png", + "forge_production_report_big": "graphics/forgprod/icons/intel/forge_production_report_big.png", + + }, + + "forgprod_hullmods": { + + "forgprod_refining_module": "graphics/forgprod/hullmods/refining_module.png", + "forgprod_fuel_production_module": "graphics/forgprod/hullmods/fuel_production_module.png", + "forgprod_heavy_industry_module": "graphics/forgprod/hullmods/heavy_industry_module.png", + + "forgprod_refining_module_big": "graphics/forgprod/hullmods/refining_module_big.png", + "forgprod_fuel_production_module_big": "graphics/forgprod/hullmods/fuel_production_module_big.png", + "forgprod_heavy_industry_module_big": "graphics/forgprod/hullmods/heavy_industry_module_big.png", + + }, + + "forgprod_ui": { + + "frame": "graphics/forgprod/ui/frame.png", + "frame_bright": "graphics/forgprod/ui/frame_bright.png", + "frame_small": "graphics/forgprod/ui/frame_small.png", + + "effect_tooltip_anchor": "graphics/forgprod/ui/effect_tooltip_anchor.png", + "income_tooltip_anchor": "graphics/forgprod/ui/income_tooltip_anchor.png", + "cost_tooltip_anchor": "graphics/forgprod/ui/cost_tooltip_anchor.png", + + "hull_parts_symbolic": "graphics/forgprod/icons/tooltip/hull_parts_symbolic.png", + "credits": "graphics/forgprod/icons/tooltip/credits.png", + + "production_small": "graphics/forgprod/icons/tooltip/production_small.png", + "production_satisfied": "graphics/forgprod/icons/tooltip/production_satisfied.png", + "production_shortage": "graphics/forgprod/icons/tooltip/production_shortage.png", + "production_breakdowns": "graphics/forgprod/icons/tooltip/production_breakdowns.png", + "production_casualties": "graphics/forgprod/icons/tooltip/production_casualties.png", + + "forge_production_illustration": "graphics/forgprod/icons/abilities/forge_production_big.png", + + }, + + "forgprod_cargo": { + + "hull_parts": "graphics/forgprod/icons/cargo/hull_parts.png", + + } + + } + +} \ No newline at end of file diff --git a/data/config/sounds.json b/data/config/sounds.json new file mode 100644 index 0000000..8f38cf2 --- /dev/null +++ b/data/config/sounds.json @@ -0,0 +1,17 @@ +{ + + # UI sounds must be in stereo, enable/disable sounds are of this sort. Their methods don't have Vector2f location. + + "forge_ability_enable":[ + {"file":"sounds/forgprod/forge_ability_enable.ogg","pitch":1.0,"volume":1.0}, + ], + "forge_ability_disable":[ + {"file":"sounds/forgprod/forge_ability_disable.ogg","pitch":1.0,"volume":1.0}, + ], + + # This is a world sound, has mono channel and is played on fleet's location. + + "forge_ability_loop":[ + {"file":"sounds/forgprod/forge_ability_loop.ogg","pitch":1.0,"volume":1.0}, + ], +} \ No newline at end of file diff --git a/data/hullmods/hull_mods.csv b/data/hullmods/hull_mods.csv new file mode 100644 index 0000000..b6d883f --- /dev/null +++ b/data/hullmods/hull_mods.csv @@ -0,0 +1,16 @@ +name,id,tier,rarity,tech/manufacturer,tags,uiTags,"base value",unlocked,hidden,hiddenEverywhere,cost_frigate,cost_dest,cost_cruiser,cost_capital,script,desc,short,sprite +"Makeshift Production Forge",forgprod_refining_module,,,"Domain Restricted","req_spaceport, special, no_build_in, no_drop, no_drop_salvage","Special, Requires Dock",120000,FALSE,FALSE,FALSE,20,20,20,40,forgprod.hullmods.RefiningModule,"This ship is outfitted with a jury-rigged version of Domain-era production autoforge, downscaled for installation on regular spaceship hulls. + +Increases the minimum crew required by %s and monthly maintenance supply cost by %s to account for production logistics. Unintended use of hull facilities decreases maximum combat readiness by %s. + +Can only be installed on cruiser or capital hulls.","Produces refined materials.",graphics/forgprod/hullmods/refining_module.png +"Makeshift Production Forge",forgprod_fuel_production_module,,,"Domain Restricted","req_spaceport, special, no_build_in, no_drop, no_drop_salvage","Special, Requires Dock",140000,FALSE,FALSE,FALSE,20,20,20,40,forgprod.hullmods.FuelProductionModule,"This ship is outfitted with a jury-rigged version of Domain-era production autoforge, downscaled for installation on regular spaceship hulls. + +Increases the minimum crew required by %s and monthly maintenance supply cost by %s to account for production logistics. Unintended use of hull facilities decreases maximum combat readiness by %s. + +Can only be installed on cruiser or capital hulls.","Produces fuel.",graphics/forgprod/hullmods/fuel_production_module.png +"Makeshift Production Forge",forgprod_heavy_industry_module,,,"Domain Restricted","req_spaceport, special, no_build_in, no_drop, no_drop_salvage","Special, Requires Dock",180000,FALSE,FALSE,FALSE,20,20,20,40,forgprod.hullmods.HeavyIndustryModule,"This ship is outfitted with a jury-rigged version of Domain-era production autoforge, downscaled for installation on regular spaceship hulls. + +Increases the minimum crew required by %s and monthly maintenance supply cost by %s to account for production logistics. Unintended use of hull facilities decreases maximum combat readiness by %s. + +Can only be installed on cruiser or capital hulls.","Produces industrial equipment.",graphics/forgprod/hullmods/heavy_industry_module.png diff --git a/forge_production_settings.ini b/forge_production_settings.ini new file mode 100644 index 0000000..439efef --- /dev/null +++ b/forge_production_settings.ini @@ -0,0 +1,121 @@ +{ + + ####### Installation Settings ####### + + "crew_requirement_per_capacity": 15, + "supply_increase_per_capacity": 1, + + ####### Miscellaneous Settings ####### + + # Hold this button before clicking on ability button to bring up production control menu. + # Ability does not activate or deactivate when clicking on a button with this key down. + # UI will not reflect changes to this field, apologies for that. + + "interaction_call_hotkey": LCONTROL, + + "enable_slow_move_penalty": true, + "enable_burn_abilities_incompatibility": true, + "enable_go_dark_incompatibility": true, + "enable_sensor_penalty": true, + + "sensor_profile_increase": 100, + + # How many ships are displayed in ability tooltip. + + "ship_list_size": 5, + + # In days. Production is still calculated daily. + # Results are gathered and cleared when intel is created. + + "notification_interval": 3, + + ####### Ship Capacity Settings ####### + + # These values are multipliers of processing cycle. + # Ship's final production is one cycle multiplied by ship's capacity. + # Frigate and destroyer hullsizes do not have code implementations. + + "base_capacity_frigate": 0, + "base_capacity_destroyer": 0, + "base_capacity_cruiser": 2, + "base_capacity_capital": 4, + + # A key variable that serves as a divisor for commodity values and multiplier for ship capacity. + # Determines size of each production cycle unit, the bigger granularity the lower the size. + # Sufficiently big granularity is needed to deal with cargo leftovers. + + "granularity": 20, + + ####### Metals Production ####### + + "base_ore_input": 16, + "base_metals_output": 10, + "base_machinery_use_metals": 6, + + ####### Transplutonics Production ####### + + "base_transplutonic_ore_input": 8, + "base_transplutonics_output": 4, + "base_machinery_use_transplutonics": 8, + + ####### Fuel Production ####### + + "base_volatiles_input": 2, + "base_fuel_output": 8, + "base_machinery_use_fuel": 12, + + ####### Supplies Production ####### + + "base_metals_input_supplies": 8, + "base_transplutonics_input_supplies": 2, + "base_supplies_output": 6, + "base_machinery_use_supplies": 10, + + ####### Machinery Production ####### + + "base_metals_input_machinery": 10, + "base_transplutonics_input_machinery": 1, + "base_machinery_output": 4, + "base_machinery_use_machinery": 14, + + ####### Hull Parts Production ####### + + # Base hull cost formula before modifiers: + # Initial base buy value assigned in ship_data.csv / Base value of all inputs of single part = total parts cost. + + "base_metals_input_hull_parts": 10, + "base_transplutonics_input_hull_parts": 1, + "base_supplies_input_hull_parts": 2, + "base_machinery_input_hull_parts": 2, + "base_hull_parts_output": 1, + "base_machinery_use_hull_parts": 18, + + # Final hull cost in parts mutliplier; applies after all other modificators. + + "hull_cost_multiplier": 0.9, + + ####### Machinery Breakdown Settings ####### + + # Number 0.3 translates to 30% base breakdown chance. + + "machinery_breakdown_chance": 0.3, + + # Determines size of breakdown. Exact size of breakdown is semi-randomized. + # Has minimal and maximal range of severity / 2, so for 0.05 the range is 0.025 - 0.075. + + "machinery_breakdown_severity": 0.05, + + ####### CR Settings ####### + + # Number 0.02 translates to 2% daily CR decrease. + + "daily_cr_decrease": 0.02, + + ####### Special Items Settings ####### + + "catalytic_core_output_bonus": 2, + "synchrotron_core_output_bonus": 4, + "corrupted_nanoforge_output_bonus": 1, + "pristine_nanoforge_output_bonus": 2, + +} diff --git a/graphics/forgprod/hullmods/fuel_production_module.png b/graphics/forgprod/hullmods/fuel_production_module.png new file mode 100644 index 0000000..2b7961b Binary files /dev/null and b/graphics/forgprod/hullmods/fuel_production_module.png differ diff --git a/graphics/forgprod/hullmods/fuel_production_module_big.png b/graphics/forgprod/hullmods/fuel_production_module_big.png new file mode 100644 index 0000000..6fe2074 Binary files /dev/null and b/graphics/forgprod/hullmods/fuel_production_module_big.png differ diff --git a/graphics/forgprod/hullmods/heavy_industry_module.png b/graphics/forgprod/hullmods/heavy_industry_module.png new file mode 100644 index 0000000..658d349 Binary files /dev/null and b/graphics/forgprod/hullmods/heavy_industry_module.png differ diff --git a/graphics/forgprod/hullmods/heavy_industry_module_big.png b/graphics/forgprod/hullmods/heavy_industry_module_big.png new file mode 100644 index 0000000..7e476d8 Binary files /dev/null and b/graphics/forgprod/hullmods/heavy_industry_module_big.png differ diff --git a/graphics/forgprod/hullmods/refining_module.png b/graphics/forgprod/hullmods/refining_module.png new file mode 100644 index 0000000..d43ec91 Binary files /dev/null and b/graphics/forgprod/hullmods/refining_module.png differ diff --git a/graphics/forgprod/hullmods/refining_module_big.png b/graphics/forgprod/hullmods/refining_module_big.png new file mode 100644 index 0000000..c231bf2 Binary files /dev/null and b/graphics/forgprod/hullmods/refining_module_big.png differ diff --git a/graphics/forgprod/icons/abilities/forge_production.png b/graphics/forgprod/icons/abilities/forge_production.png new file mode 100644 index 0000000..976e498 Binary files /dev/null and b/graphics/forgprod/icons/abilities/forge_production.png differ diff --git a/graphics/forgprod/icons/abilities/forge_production_big.png b/graphics/forgprod/icons/abilities/forge_production_big.png new file mode 100644 index 0000000..48d0092 Binary files /dev/null and b/graphics/forgprod/icons/abilities/forge_production_big.png differ diff --git a/graphics/forgprod/icons/cargo/hull_parts.png b/graphics/forgprod/icons/cargo/hull_parts.png new file mode 100644 index 0000000..6c7c272 Binary files /dev/null and b/graphics/forgprod/icons/cargo/hull_parts.png differ diff --git a/graphics/forgprod/icons/intel/forge_production_report.png b/graphics/forgprod/icons/intel/forge_production_report.png new file mode 100644 index 0000000..e758654 Binary files /dev/null and b/graphics/forgprod/icons/intel/forge_production_report.png differ diff --git a/graphics/forgprod/icons/intel/forge_production_report_big.png b/graphics/forgprod/icons/intel/forge_production_report_big.png new file mode 100644 index 0000000..6f4f541 Binary files /dev/null and b/graphics/forgprod/icons/intel/forge_production_report_big.png differ diff --git a/graphics/forgprod/icons/tooltip/credits.png b/graphics/forgprod/icons/tooltip/credits.png new file mode 100644 index 0000000..7e01f84 Binary files /dev/null and b/graphics/forgprod/icons/tooltip/credits.png differ diff --git a/graphics/forgprod/icons/tooltip/hull_parts_symbolic.png b/graphics/forgprod/icons/tooltip/hull_parts_symbolic.png new file mode 100644 index 0000000..8ea8526 Binary files /dev/null and b/graphics/forgprod/icons/tooltip/hull_parts_symbolic.png differ diff --git a/graphics/forgprod/icons/tooltip/production_breakdowns.png b/graphics/forgprod/icons/tooltip/production_breakdowns.png new file mode 100644 index 0000000..5998a93 Binary files /dev/null and b/graphics/forgprod/icons/tooltip/production_breakdowns.png differ diff --git a/graphics/forgprod/icons/tooltip/production_casualties.png b/graphics/forgprod/icons/tooltip/production_casualties.png new file mode 100644 index 0000000..eb6291a Binary files /dev/null and b/graphics/forgprod/icons/tooltip/production_casualties.png differ diff --git a/graphics/forgprod/icons/tooltip/production_satisfied.png b/graphics/forgprod/icons/tooltip/production_satisfied.png new file mode 100644 index 0000000..aa5cc4f Binary files /dev/null and b/graphics/forgprod/icons/tooltip/production_satisfied.png differ diff --git a/graphics/forgprod/icons/tooltip/production_shortage.png b/graphics/forgprod/icons/tooltip/production_shortage.png new file mode 100644 index 0000000..87d004a Binary files /dev/null and b/graphics/forgprod/icons/tooltip/production_shortage.png differ diff --git a/graphics/forgprod/icons/tooltip/production_small.png b/graphics/forgprod/icons/tooltip/production_small.png new file mode 100644 index 0000000..974d4c8 Binary files /dev/null and b/graphics/forgprod/icons/tooltip/production_small.png differ diff --git a/graphics/forgprod/ui/cost_tooltip_anchor.png b/graphics/forgprod/ui/cost_tooltip_anchor.png new file mode 100644 index 0000000..150b7dd Binary files /dev/null and b/graphics/forgprod/ui/cost_tooltip_anchor.png differ diff --git a/graphics/forgprod/ui/effect_tooltip_anchor.png b/graphics/forgprod/ui/effect_tooltip_anchor.png new file mode 100644 index 0000000..3492544 Binary files /dev/null and b/graphics/forgprod/ui/effect_tooltip_anchor.png differ diff --git a/graphics/forgprod/ui/frame.png b/graphics/forgprod/ui/frame.png new file mode 100644 index 0000000..e6f2083 Binary files /dev/null and b/graphics/forgprod/ui/frame.png differ diff --git a/graphics/forgprod/ui/frame_bright.png b/graphics/forgprod/ui/frame_bright.png new file mode 100644 index 0000000..7e96ae7 Binary files /dev/null and b/graphics/forgprod/ui/frame_bright.png differ diff --git a/graphics/forgprod/ui/frame_small.png b/graphics/forgprod/ui/frame_small.png new file mode 100644 index 0000000..233fd12 Binary files /dev/null and b/graphics/forgprod/ui/frame_small.png differ diff --git a/graphics/forgprod/ui/income_tooltip_anchor.png b/graphics/forgprod/ui/income_tooltip_anchor.png new file mode 100644 index 0000000..3a63820 Binary files /dev/null and b/graphics/forgprod/ui/income_tooltip_anchor.png differ diff --git a/jars/forge_production.jar b/jars/forge_production.jar new file mode 100644 index 0000000..0ce1098 Binary files /dev/null and b/jars/forge_production.jar differ diff --git a/mod_info.json b/mod_info.json new file mode 100644 index 0000000..9c552d3 --- /dev/null +++ b/mod_info.json @@ -0,0 +1,12 @@ +{ + "id":"forge_production", + "name":"Forge Production", + "author":"Ontheheavens", + "version":"1.1.0", + "description":"Hullmods that allow ships to produce commodities with ability toggle.", + "gameVersion":"0.95.1a-RC6", + "jars":["jars/forge_production.jar"], + "modPlugin":"forgprod.ForgprodModPlugin", + "totalConversion": "false", + "utility": "false", +} \ No newline at end of file diff --git a/sounds/forgprod/forge_ability_disable.ogg b/sounds/forgprod/forge_ability_disable.ogg new file mode 100644 index 0000000..35b3614 Binary files /dev/null and b/sounds/forgprod/forge_ability_disable.ogg differ diff --git a/sounds/forgprod/forge_ability_enable.ogg b/sounds/forgprod/forge_ability_enable.ogg new file mode 100644 index 0000000..7dd8811 Binary files /dev/null and b/sounds/forgprod/forge_ability_enable.ogg differ diff --git a/sounds/forgprod/forge_ability_loop.ogg b/sounds/forgprod/forge_ability_loop.ogg new file mode 100644 index 0000000..fda03df Binary files /dev/null and b/sounds/forgprod/forge_ability_loop.ogg differ diff --git a/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodAbilityUnlock.java b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodAbilityUnlock.java new file mode 100644 index 0000000..b5f45e1 --- /dev/null +++ b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodAbilityUnlock.java @@ -0,0 +1,78 @@ +package com.fs.starfarer.api.impl.campaign.rulecmd; + +import java.util.List; +import java.util.Map; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.InteractionDialogAPI; +import com.fs.starfarer.api.campaign.PersistentUIDataAPI; +import com.fs.starfarer.api.campaign.TextPanelAPI; +import com.fs.starfarer.api.campaign.rules.MemoryAPI; +import com.fs.starfarer.api.loading.AbilitySpecAPI; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.conversion.support.ProductionConstants; + +/** + * @author Ontheheavens + * @since 15.01.2023 + */ + +@SuppressWarnings("unused") +public class ForgprodAbilityUnlock extends BaseCommandPlugin { + + @Override + public boolean execute(String ruleId, InteractionDialogAPI dialog, List params, Map memoryMap) { + if (dialog == null) return false; + TextPanelAPI text = dialog.getTextPanel(); + ForgprodAbilityUnlock.unlockAbility(text); + ForgprodAbilityUnlock.unlockHullmods(text); + return true; + } + + private static void unlockAbility(TextPanelAPI text) { + String abilityId = "forge_production"; + AbilitySpecAPI spec = Global.getSettings().getAbilitySpec(abilityId); + boolean hadAbilityAlready = Global.getSector().getPlayerFleet().hasAbility(abilityId); + if (hadAbilityAlready) { + return; + } + Global.getSector().getCharacterData().addAbility(abilityId); + PersistentUIDataAPI.AbilitySlotsAPI slots = Global.getSector().getUIData().getAbilitySlotsAPI(); + int currBarIndex = slots.getCurrBarIndex(); + OUTER: + for (int i = 0; i < 5; i++) { + slots.setCurrBarIndex(i); + for (int j = 0; j < 10; j++) { + PersistentUIDataAPI.AbilitySlotAPI slot = slots.getCurrSlotsCopy().get(j); + if (slot.getAbilityId() == null) { + slot.setAbilityId(abilityId); + break OUTER; + } + } + } + slots.setCurrBarIndex(currBarIndex); + text.addPara("You now possess %s ability - technical know-how needed to operate " + + "Pre-Collapse space manufacturing tech.", + Misc.getHighlightColor(), + "\"" + spec.getName() + "\""); + text.addPara("These know-how, however, do not go as far as " + + "producing brand-new autoforge units yourself - the only practical option right now is to " + + "reassemble makeshift production modules using stock of crucial Domain-made components " + + "salvaged from derelict mothership."); + AddRemoveCommodity.addAbilityGainText(abilityId, text); + } + + private static void unlockHullmods(TextPanelAPI text) { + Global.getSector().getCharacterData().getHullMods().add(ProductionConstants.REFINING_MODULE); + Global.getSector().getCharacterData().getHullMods().add(ProductionConstants.FUEL_PRODUCTION_MODULE); + Global.getSector().getCharacterData().getHullMods().add(ProductionConstants.HEAVY_INDUSTRY_MODULE); + String name = Global.getSettings().getHullModSpec(ProductionConstants.HEAVY_INDUSTRY_MODULE).getDisplayName(); + text.setFontSmallInsignia(); + text.addParagraph("Gained modspec: " + name + "", Misc.getPositiveHighlightColor()); + text.highlightInLastPara(Misc.getHighlightColor(), name); + text.setFontInsignia(); + Global.getSoundPlayer().playUISound("ui_acquired_hullmod", 1f, 1f); + } + +} diff --git a/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodCheckEquipment.java b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodCheckEquipment.java new file mode 100644 index 0000000..7c7dfd7 --- /dev/null +++ b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodCheckEquipment.java @@ -0,0 +1,63 @@ +package com.fs.starfarer.api.impl.campaign.rulecmd; + +import java.util.List; +import java.util.Map; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.*; +import com.fs.starfarer.api.campaign.rules.MemoryAPI; +import com.fs.starfarer.api.impl.campaign.ids.Commodities; +import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.SalvageEntity; +import com.fs.starfarer.api.util.Misc; + +/** + * @author Ontheheavens + * @since 14.01.2023 + */ + +@SuppressWarnings("unused") +public class ForgprodCheckEquipment extends BaseCommandPlugin { + + private static SectorEntityToken mothership; + + @Override + public boolean execute(final String ruleId, final InteractionDialogAPI dialog, + final List params, final Map memoryMap) { + if (dialog == null) { + return false; + } + mothership = dialog.getInteractionTarget(); + final String command = params.get(0).getString(memoryMap); + if (command == null) { + return false; + } + if ("playerHasEquipment".equals(command)) { + return playerHasEquipment(); + } + return false; + } + + protected boolean playerHasEquipment() { + CargoAPI cargo = Global.getSector().getPlayerFleet().getCargo(); + Map requiredRes = SalvageEntity.computeRequiredToSalvage(mothership); + for (String commodityId : requiredRes.keySet()) { + int cost = requiredRes.get(commodityId); + if (commodityId.equals(Commodities.CREW)) { + cost = Math.round(cost * 1.4f); + } else { + cost = Math.round(cost * 4.8f); + } + requiredRes.put(commodityId, cost); + } + requiredRes.put(Commodities.GAMMA_CORE, 3); + boolean hasCrew = hasCommodity(requiredRes, cargo, Commodities.CREW); + boolean hasMachinery = hasCommodity(requiredRes, cargo, Commodities.HEAVY_MACHINERY); + boolean hasCores = hasCommodity(requiredRes, cargo, Commodities.GAMMA_CORE); + return hasCrew && hasMachinery && hasCores; + } + + private static boolean hasCommodity(Map requiredRes, CargoAPI cargo, String commodity) { + return cargo.getCommodityQuantity(commodity) >= requiredRes.get(commodity); + } + +} diff --git a/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodOperationSuccessSFX.java b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodOperationSuccessSFX.java new file mode 100644 index 0000000..bda8df6 --- /dev/null +++ b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodOperationSuccessSFX.java @@ -0,0 +1,26 @@ +package com.fs.starfarer.api.impl.campaign.rulecmd; + +import java.util.List; +import java.util.Map; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.InteractionDialogAPI; +import com.fs.starfarer.api.campaign.rules.MemoryAPI; +import com.fs.starfarer.api.util.Misc; + +/** + * @author Ontheheavens + * @since 15.01.2023 + */ + +@SuppressWarnings("unused") +public class ForgprodOperationSuccessSFX extends BaseCommandPlugin { + + @Override + public boolean execute(String ruleId, InteractionDialogAPI dialog, List params, Map memoryMap) { + if (dialog == null) return false; + Global.getSoundPlayer().playUISound("ui_scavenge_on", 1f, 1f); + return true; + } + +} diff --git a/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodRecoveryRewards.java b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodRecoveryRewards.java new file mode 100644 index 0000000..b6f5613 --- /dev/null +++ b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodRecoveryRewards.java @@ -0,0 +1,61 @@ +package com.fs.starfarer.api.impl.campaign.rulecmd; + +import java.util.*; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.*; +import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType; +import com.fs.starfarer.api.campaign.rules.MemoryAPI; +import com.fs.starfarer.api.combat.ShipVariantAPI; +import com.fs.starfarer.api.impl.campaign.ids.Commodities; +import com.fs.starfarer.api.impl.campaign.ids.Items; +import com.fs.starfarer.api.impl.campaign.ids.Strings; +import com.fs.starfarer.api.util.Misc; +import com.fs.starfarer.api.util.Pair; + +/** + * @author Ontheheavens + * @since 15.01.2023 + */ + +@SuppressWarnings("unused") +public class ForgprodRecoveryRewards extends BaseCommandPlugin { + + @Override + public boolean execute(String ruleId, InteractionDialogAPI dialog, List params, Map memoryMap) { + if (dialog == null) return false; + ForgprodRecoveryRewards.addItemRewards(dialog); + return true; + } + + private static void addItemRewards(InteractionDialogAPI dialog) { + CargoAPI cargo = Global.getSector().getPlayerFleet().getCargo(); + Map> rewards = new HashMap<>(); + rewards.put(Commodities.HEAVY_MACHINERY, new Pair<>(CargoItemType.RESOURCES, 120)); + rewards.put(Commodities.METALS, new Pair<>(CargoItemType.RESOURCES, 260)); + rewards.put(Commodities.RARE_METALS, new Pair<>(CargoItemType.RESOURCES, 80)); + rewards.put(Items.CORRUPTED_NANOFORGE, new Pair<>(CargoItemType.SPECIAL, 1)); + for (String item : rewards.keySet()) { + CargoItemType type = rewards.get(item).one; + int quantity = rewards.get(item).two; + if (type == CargoAPI.CargoItemType.RESOURCES) { + cargo.addItems(type, item, quantity); + AddRemoveCommodity.addCommodityGainText(item, quantity, dialog.getTextPanel()); + } else if (type == CargoAPI.CargoItemType.SPECIAL) { + cargo.addItems(type, new SpecialItemData(item, null), quantity); + ForgprodRecoveryRewards.addSpecialGainText(item, quantity, dialog.getTextPanel()); + } + } + } + + private static void addSpecialGainText(String itemId, int quantity, TextPanelAPI text) { + SpecialItemSpecAPI spec = Global.getSettings().getSpecialItemSpec(itemId); + text.setFontSmallInsignia(); + String name = spec.getName(); + text.addParagraph("Gained " + Misc.getWithDGS(quantity) + Strings.X + " " + name + "", Misc.getPositiveHighlightColor()); + text.highlightInLastPara(Misc.getHighlightColor(), Misc.getWithDGS(quantity) + Strings.X); + text.setFontInsignia(); + } + + +} diff --git a/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodShowRetrievalCost.java b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodShowRetrievalCost.java new file mode 100644 index 0000000..e28629a --- /dev/null +++ b/source/com/fs/starfarer/api/impl/campaign/rulecmd/ForgprodShowRetrievalCost.java @@ -0,0 +1,70 @@ +package com.fs.starfarer.api.impl.campaign.rulecmd; + +import java.awt.*; +import java.util.List; +import java.util.Map; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.*; +import com.fs.starfarer.api.campaign.rules.MemoryAPI; +import com.fs.starfarer.api.impl.campaign.ids.Commodities; +import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.SalvageEntity; +import com.fs.starfarer.api.ui.Alignment; +import com.fs.starfarer.api.util.Misc; + +@SuppressWarnings("unused") +public class ForgprodShowRetrievalCost extends BaseCommandPlugin { + + @Override + public boolean execute(final String ruleId, final InteractionDialogAPI dialog, + final List params, final Map memoryMap) { + if (dialog == null) { + return false; + } + TextPanelAPI textPanel = dialog.getTextPanel(); + SectorEntityToken mothership = dialog.getInteractionTarget(); + this.showCost(textPanel, mothership); + return true; + } + + public void showCost(TextPanelAPI textPanel, SectorEntityToken mothership) { + FactionAPI playerFaction = Global.getSector().getPlayerFaction(); + CargoAPI cargo = Global.getSector().getPlayerFleet().getCargo(); + + Color color = playerFaction.getColor(); + Color bad = Misc.getNegativeHighlightColor(); + Map requiredRes = SalvageEntity.computeRequiredToSalvage(mothership); + for (String commodityId : requiredRes.keySet()) { + int cost = requiredRes.get(commodityId); + if (commodityId.equals(Commodities.CREW)) { + cost = Math.round(cost * 1.4f); + } else { + cost = Math.round(cost * 4.8f); + } + requiredRes.put(commodityId, cost); + } + requiredRes.put(Commodities.GAMMA_CORE, 3); + String format = "Reporting officer notes that that engineering operation of this kind" + + " will likely demand considerable %s in order to plan smooth uninstall of autoforging complexes " + + "without causing irreparable damage to them in the process. The operation will also " + + "require significant amounts of specialized %s."; + textPanel.addPara(format, Misc.getTextColor(), Misc.getHighlightColor(), + "processing power", "industrial equipment"); + ResourceCostPanelAPI cost = textPanel.addCostPanel("Crew, machinery & AI cores: required (available)", + 67f, color, playerFaction.getDarkUIColor()); + cost.setNumberOnlyMode(true); + cost.setWithBorder(false); + cost.setAlignment(Alignment.LMID); + for (String commodityId : requiredRes.keySet()) { + int required = requiredRes.get(commodityId); + int available = (int) cargo.getCommodityQuantity(commodityId); + Color curr = color; + if (required > cargo.getQuantity(CargoAPI.CargoItemType.RESOURCES, commodityId)) { + curr = bad; + } + cost.addCost(commodityId, "" + required + " (" + available + ")", curr); + } + cost.update(); + } + +} \ No newline at end of file diff --git a/source/forgprod/ForgprodModPlugin.java b/source/forgprod/ForgprodModPlugin.java new file mode 100644 index 0000000..0dcfe86 --- /dev/null +++ b/source/forgprod/ForgprodModPlugin.java @@ -0,0 +1,21 @@ +package forgprod; + +import org.json.JSONException; + +import java.io.IOException; + +import com.fs.starfarer.api.BaseModPlugin; + +import forgprod.settings.SettingsDataSupplier; + +@SuppressWarnings("unused") +public final class ForgprodModPlugin extends BaseModPlugin { + + public static final String FORGE_SETTINGS = "forge_production_settings.ini"; + + @Override + public void onApplicationLoad() throws JSONException, IOException { + SettingsDataSupplier.loadSettings(FORGE_SETTINGS); + } + +} diff --git a/source/forgprod/abilities/ForgeProductionAbility.java b/source/forgprod/abilities/ForgeProductionAbility.java new file mode 100644 index 0000000..adc7cb7 --- /dev/null +++ b/source/forgprod/abilities/ForgeProductionAbility.java @@ -0,0 +1,177 @@ +package forgprod.abilities; + +import org.lwjgl.input.Keyboard; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.characters.AbilityPlugin; +import com.fs.starfarer.api.impl.campaign.abilities.BaseToggleAbility; +import com.fs.starfarer.api.impl.campaign.ids.Abilities; +import com.fs.starfarer.api.ui.TooltipMakerAPI; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.conversion.ProductionEventManager; +import forgprod.abilities.conversion.support.checks.FleetwideProductionChecks; +import forgprod.abilities.effects.AbilityEffects; +import forgprod.abilities.interaction.wrappers.ControlInteractionPlugin; +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.abilities.tooltip.TooltipAssembly; +import forgprod.settings.SettingsHolder; + +@SuppressWarnings("unused") +public class ForgeProductionAbility extends BaseToggleAbility { + + private ProductionEventManager productionManager; + + protected void activateImpl() { + productionManager = ProductionEventManager.getInstance(); + FleetwideModuleManager.getInstance().refreshIndexes(getFleet()); + interruptIncompatible(); + } + + @Override + protected String getActivationText() { + return "Commencing forge production"; + } + + protected void applyEffect(float amount, float level) { + CampaignFleetAPI fleet = getFleet(); + if (fleet == null) return; + if (!isActive()) return; + if (!checkUsableCapacities()) { + deactivate(); + return; + } + disableIncompatible(); + if (SettingsHolder.ENABLE_DETECT_AT_RANGE_PENALTY) { + fleet.getStats().getDetectedRangeMod().modifyPercent(getModId(), SettingsHolder.SENSOR_PROFILE_INCREASE, "Forge production"); + } + if (SettingsHolder.ENABLE_SLOW_MOVE_PENALTY) { + fleet.goSlowOneFrame(); + } + AbilityEffects.shiftFleetContrails(fleet, getModId(), level); + productionManager.advance(fleet, amount); + } + + @Override + protected void deactivateImpl() { + cleanupImpl(); + } + + @Override + protected void cleanupImpl() { + CampaignFleetAPI fleet = getFleet(); + if (fleet == null) return; + if (SettingsHolder.ENABLE_DETECT_AT_RANGE_PENALTY) { + fleet.getStats().getDetectedRangeMod().unmodify(getModId()); + } + productionManager.clearTracker(); + productionManager = null; + } + + private boolean checkUsableCapacities() { + FleetwideModuleManager.getInstance().refreshIndexes(getFleet()); + boolean hasShips = FleetwideProductionChecks.hasActiveModules(); + boolean hasMachinery = FleetwideProductionChecks.hasMinimumMachinery(this.getFleet()); + return hasShips && hasMachinery; + } + + public boolean checkUsableAbilities() { + for (AbilityPlugin other : this.getFleet().getAbilities().values()) { + if (other == this) continue; + if (!isCompatible(other) && other.isActiveOrInProgress()) { + return false; + } + } + return true; + } + + @Override + public boolean isUsable() { + if (Keyboard.isKeyDown(Keyboard.getKeyIndex(SettingsHolder.INTERACTION_CALL_HOTKEY))) { + return true; + } + if (!isActivateCooldown && + getProgressFraction() > 0 && getProgressFraction() < 1 && + getDeactivationDays() > 0) return false; + + if (!checkUsableCapacities() || !checkUsableAbilities()) + return false; + + return super.isUsable(); + } + + @Override + public void pressButton() { + if (Keyboard.isKeyDown(Keyboard.getKeyIndex(SettingsHolder.INTERACTION_CALL_HOTKEY))) { + FleetwideModuleManager.getInstance().refreshIndexes(this.getFleet()); + Global.getSector().getCampaignUI().showInteractionDialog(new ControlInteractionPlugin(), null); + return; + } + super.pressButton(); + } + + @Override + public boolean showActiveIndicator() { return isActive(); } + + @Override + public boolean hasTooltip() { return true; } + + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) { + TooltipAssembly.assembleTooltip(this.getFleet(), tooltip, this.getSpec().getName(), + this.isActive(), this.turnedOn, expanded, this); + addIncompatibleToTooltip(tooltip, expanded); + tooltip.addPara("Ctrl+left-click to open control panel", Misc.getGrayColor(), 4f); + } + + @Override + protected void addIncompatibleToTooltip(TooltipMakerAPI tooltip, boolean expanded) { + addIncompatibleToTooltip(tooltip, "Incompatible with the following abilities:", + "Expand tooltip to view production and conflicting abilities", + expanded); + } + + @Override + public boolean isCompatible(AbilityPlugin other) { + boolean burnIncompat = SettingsHolder.ENABLE_BURN_ABILITIES_INCOMPATIBILITY; + boolean darkIncompat = SettingsHolder.ENABLE_GO_DARK_INCOMPATIBILITY; + if (burnIncompat && darkIncompat) { + return !other.getId().equals(Abilities.SUSTAINED_BURN) && + !other.getId().equals(Abilities.EMERGENCY_BURN) && + !other.getId().equals(Abilities.GO_DARK); + } + if (burnIncompat) { + return !other.getId().equals(Abilities.SUSTAINED_BURN) && !other.getId().equals(Abilities.EMERGENCY_BURN); + } + if (darkIncompat) { + return !other.getId().equals(Abilities.GO_DARK); + } + return true; + } + + @Override + public List getInterruptedList() { + List result = new ArrayList<>(); + CampaignFleetAPI fleet = getFleet(); + if (fleet == null) return result; + for (AbilityPlugin curr : fleet.getAbilities().values()) { + if (curr == this) continue; + if (!isCompatible(curr)) { + result.add(curr); + } + } + Collections.sort(result, new Comparator() { + public int compare(AbilityPlugin o1, AbilityPlugin o2) { + return o1.getSpec().getSortOrder() - o2.getSpec().getSortOrder(); + } + }); + return result; + } + +} \ No newline at end of file diff --git a/source/forgprod/abilities/conversion/ProductionEventManager.java b/source/forgprod/abilities/conversion/ProductionEventManager.java new file mode 100644 index 0000000..0f6064e --- /dev/null +++ b/source/forgprod/abilities/conversion/ProductionEventManager.java @@ -0,0 +1,120 @@ +package forgprod.abilities.conversion; + +import forgprod.abilities.conversion.logic.*; +import forgprod.abilities.conversion.logic.base.ProductionLogic; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.effects.AbilityEffects; +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.abilities.report.ForgeProductionReport; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignClockAPI; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.util.IntervalUtil; + +import forgprod.abilities.conversion.support.ConversionVariables; +import forgprod.settings.SettingsHolder; + +/** + * @author Ontheheavens + * @since 23.11.2022 + */ + +public class ProductionEventManager { + + private static ProductionEventManager managerInstance; + private Integer firstDay = null; + private boolean firstDayDone = false; + private int notificationCounter = 0; + + // Exactly one day, having a setting for this tracker is pointless, + // since entire logic is based on the assumption of daily production cycle. + private final IntervalUtil productionTracker = new IntervalUtil(1f, 1f); + + + public static ProductionEventManager getInstance() { + if (managerInstance == null) { + managerInstance = new ProductionEventManager(); + } + return managerInstance; + } + + public int getNotificationCounter() { + return notificationCounter; + } + + // The idea here is to have production event happen precisely when the new day comes. + // Since it is unreasonable to wait almost two full days if the ability button was pushed at the start of the day, + // or, on the other hand, receive production after half a day if ability was toggled late, + // the logic had to be made a bit more complicated. + public void advance(CampaignFleetAPI fleet, float amount) { + CampaignClockAPI clock = Global.getSector().getClock(); + int presentDay = clock.getDay(); + int hour = clock.getHour(); + // Four hours is the approximate time when day progress bar grows for one increment. + // Therefore, ability user has about four hours window after start of the day to commence forging, + // if he wants the day to be productive. + if (firstDay == null && hour >= 4) { + firstDay = presentDay; + firstDayDone = true; + productionTracker.setElapsed(0); + } else if (firstDay == null) { + firstDay = presentDay; + productionTracker.setElapsed(0); + firstDayDone = false; + } + // This is extra production cycle for the first productive day. + if ((firstDay == presentDay) && !firstDayDone && (hour == 23)) { + firstDayDone = true; + startProductionEvent(fleet); + } + // Normal production cycle advancement, starts as soon as the next day after ability toggle comes, + // continues until ability toggle-off. + if (!(firstDay == presentDay)) { + productionTracker.advance(Global.getSector().getClock().convertToDays(amount)); + } + if (productionTracker.getElapsed() >= productionTracker.getIntervalDuration()) { + productionTracker.setElapsed(0); + startProductionEvent(fleet); + } + } + + private void startProductionEvent(CampaignFleetAPI fleet) { + FleetwideModuleManager.getInstance().refreshIndexes(fleet); + for (ProductionType type : ProductionType.values()) { + ProductionLogic logic = LogicFactory.getLogic(type); + logic.initializeProduction(fleet); + } + BreakdownLogic.getInstance().calculateBreakdowns(fleet); + AbilityEffects.applyCRDecrease(); + if (ConversionVariables.hasGoodsDailyFlags()) { + AbilityEffects.playProductionEffects(fleet); + ConversionVariables.clearGoodsDailyFlags(); + } + if (ConversionVariables.goodsProducedReport()) { + notificationCounter += 1; + } + boolean notificationCounterElapsed = (notificationCounter >= SettingsHolder.NOTIFICATION_INTERVAL); + if (notificationCounterElapsed) { + addReportMessage(); + notificationCounter = 0; + } + } + + public void clearTracker() { + productionTracker.setElapsed(0); + firstDay = null; + firstDayDone = false; + // Immediate notification firing and cleanup so that variables do not carry over to the next ability usage. + if (notificationCounter >= 1) { + addReportMessage(); + notificationCounter = 0; + } + } + + private static void addReportMessage() { + ForgeProductionReport intel = new ForgeProductionReport(); + Global.getSector().getCampaignUI().addMessage(intel); + } + +} diff --git a/source/forgprod/abilities/conversion/logic/BreakdownLogic.java b/source/forgprod/abilities/conversion/logic/BreakdownLogic.java new file mode 100644 index 0000000..5602429 --- /dev/null +++ b/source/forgprod/abilities/conversion/logic/BreakdownLogic.java @@ -0,0 +1,81 @@ +package forgprod.abilities.conversion.logic; + +import java.util.Map; + +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.fleet.FleetMemberAPI; +import com.fs.starfarer.api.impl.campaign.ids.Commodities; + +import forgprod.abilities.conversion.support.ConversionVariables; +import forgprod.abilities.conversion.support.checks.FleetwideProductionChecks; +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.abilities.modules.dataholders.ProductionModule; +import forgprod.settings.SettingsHolder; + +import static forgprod.abilities.conversion.support.checks.ItemBonusesChecks.getNanoforgeBreakdownDecrease; + +/** + * Collects amount of daily used machinery from all production types and handles breakdowns. + * Singleton-pattern class. + * @author Ontheheavens + * @since 03.12.2022 + */ + +public class BreakdownLogic { + + private static BreakdownLogic logicInstance; + private float totalDailyMachineryUsed; + + public static BreakdownLogic getInstance() { + if (logicInstance == null) { + logicInstance = new BreakdownLogic(); + } + return logicInstance; + } + + public void addToTotalDailyMachineryUsed(float dailyMachineryUsedByType) { + this.totalDailyMachineryUsed += dailyMachineryUsedByType; + } + + // Needs to be called after every other production type has been finished. + // Two types of breakdowns are intended: the usual machinery breakdown, and fatal accident. + // First means lost machinery, second means you also lose crew. + // Second can't happen without the first. + public void calculateBreakdowns(CampaignFleetAPI fleet) { + float baseBreakdownChance = FleetwideProductionChecks.getBreakdownChance(fleet); + boolean breakdownHappened = Math.random() < baseBreakdownChance; + if (breakdownHappened && totalDailyMachineryUsed > 10) { + float minimalBreakdown = FleetwideProductionChecks.getMinimalBreakdownSeverity(fleet); + float maximalBreakdown = FleetwideProductionChecks.getMaximalBreakdownSeverity(fleet); + float finalBreakdownSeverity = (float) (minimalBreakdown + Math.random() * (maximalBreakdown - minimalBreakdown)); + float dailyMachineryBroken = totalDailyMachineryUsed * finalBreakdownSeverity; + + boolean fatalAccidentHappened = Math.random() < (FleetwideProductionChecks.getFatalAccidentChance(fleet)); + if (fatalAccidentHappened && totalDailyMachineryUsed > 50) { + calculateFatalAccidents(fleet); + } + fleet.getCargo().removeCommodity(Commodities.HEAVY_MACHINERY, dailyMachineryBroken); + ConversionVariables.totalMachineryBroke += dailyMachineryBroken; + } + totalDailyMachineryUsed = 0f; + } + + private void calculateFatalAccidents(CampaignFleetAPI fleet) { + float totalCrewEmployed = 0; + Map moduleIndex = FleetwideModuleManager.getInstance().getModuleIndex(); + for (ProductionModule module : moduleIndex.values()) { + if (module.hadProducedToday()) { + totalCrewEmployed += module.getParentFleetMember().getCrewComposition().getCrew(); + } + } + float baseAccidentSeverity = (SettingsHolder.BREAKDOWN_SEVERITY * getNanoforgeBreakdownDecrease(fleet)) / 2; + float finalAccidentSeverity = (float) (baseAccidentSeverity * Math.random()); + float dailyCrewmenLost = totalCrewEmployed * finalAccidentSeverity; + if (dailyCrewmenLost < 1f) { + dailyCrewmenLost = 1f; + } + fleet.getCargo().removeCommodity(Commodities.CREW, dailyCrewmenLost); + ConversionVariables.totalCrewmenLost += dailyCrewmenLost; + } + +} diff --git a/source/forgprod/abilities/conversion/logic/LogicFactory.java b/source/forgprod/abilities/conversion/logic/LogicFactory.java new file mode 100644 index 0000000..00cd173 --- /dev/null +++ b/source/forgprod/abilities/conversion/logic/LogicFactory.java @@ -0,0 +1,33 @@ +package forgprod.abilities.conversion.logic; + +import forgprod.abilities.conversion.logic.base.ProductionLogic; +import forgprod.abilities.conversion.logic.production.*; +import forgprod.abilities.conversion.support.ProductionType; + +/** + * @author Ontheheavens + * @since 26.12.2022 + */ + +public class LogicFactory { + + public static ProductionLogic getLogic(ProductionType type) { + switch (type) { + case METALS_PRODUCTION: + return MetalsProductionLogic.getInstance(); + case TRANSPLUTONICS_PRODUCTION: + return TransplutonicsProductionLogic.getInstance(); + case FUEL_PRODUCTION: + return FuelProductionLogic.getInstance(); + case SUPPLIES_PRODUCTION: + return SuppliesProductionLogic.getInstance(); + case MACHINERY_PRODUCTION: + return MachineryProductionLogic.getInstance(); + case HULL_PARTS_PRODUCTION: + return HullPartsProductionLogic.getInstance(); + default: + return new ProductionLogic() {}; + } + } + +} diff --git a/source/forgprod/abilities/conversion/logic/base/ProductionLogic.java b/source/forgprod/abilities/conversion/logic/base/ProductionLogic.java new file mode 100644 index 0000000..9302dd0 --- /dev/null +++ b/source/forgprod/abilities/conversion/logic/base/ProductionLogic.java @@ -0,0 +1,172 @@ +package forgprod.abilities.conversion.logic.base; + +import java.util.HashSet; +import java.util.Set; + +import com.fs.starfarer.api.campaign.CampaignFleetAPI; + +import forgprod.abilities.conversion.logic.*; +import forgprod.abilities.conversion.support.checks.FleetwideProductionChecks; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.conversion.support.checks.ItemBonusesChecks; +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.abilities.modules.dataholders.ProductionModule; + +/** + * New iteration of commodity production logic. + * Singleton-pattern class. + * @author Ontheheavens + * @since 03.12.2022 + */ + +public abstract class ProductionLogic { + + protected ProductionType productionType; + protected boolean hasSecondaryInput; + protected float cyclePrimaryInputAmount; + protected float cycleSecondaryInputAmount; + protected float cycleOutputAmount; + protected float cycleMachineryUse; + protected String primaryInputType; + protected String secondaryInputType; + protected String outputType; + + // To be overridden by all inheritors. + protected ProductionLogic() { + productionType = null; + hasSecondaryInput = false; + cyclePrimaryInputAmount = 0f; + cycleSecondaryInputAmount = 0f; + cycleOutputAmount = 0f; + cycleMachineryUse = 0f; + primaryInputType = ""; + secondaryInputType = ""; + outputType = ""; + } + + public boolean hasSecondaryInput() { + return hasSecondaryInput; + } + + public String getPrimaryInputType() { + return primaryInputType; + } + + public String getSecondaryInputType() { + return secondaryInputType; + } + + public String getOutputType() { + return outputType; + } + + public float getCyclePrimaryInputAmount() { + return cyclePrimaryInputAmount; + } + + public float getCycleSecondaryInputAmount() { + return cycleSecondaryInputAmount; + } + + public float getCycleOutputAmount() { + return cycleOutputAmount; + } + + public float getCycleMachineryUse() { + return cycleMachineryUse; + } + + public void initializeProduction(CampaignFleetAPI fleet) { + if (fleet == null) return; + if (this.productionType == null) return; + if (FleetwideProductionChecks.getFleetCapacityForType(fleet, productionType) < 1) return; + if (!FleetwideProductionChecks.hasMinimumMachinery(fleet)) return; + if (hasMinimumCargo(fleet)) { + commenceProduction(fleet); + } + } + + protected boolean hasMinimumCargo(CampaignFleetAPI fleet) { + if (hasSecondaryInput) { + return (getAvailablePrimaryInputs(fleet) > cyclePrimaryInputAmount && + getAvailableSecondaryInputs(fleet) > cycleSecondaryInputAmount); + } else { + return (getAvailablePrimaryInputs(fleet) > cyclePrimaryInputAmount); + } + } + + protected float getAvailablePrimaryInputs(CampaignFleetAPI fleet) { + return fleet.getCargo().getCommodityQuantity(primaryInputType); + } + + protected float getAvailableSecondaryInputs(CampaignFleetAPI fleet) { + return fleet.getCargo().getCommodityQuantity(secondaryInputType); + } + + // Takes inputs actually available in cargo holds into account. + protected int getMinimalCapacity(CampaignFleetAPI fleet) { + int possibleCapacityFleet = FleetwideProductionChecks.getFleetCapacityForType(fleet, productionType); + int possibleCapacityCargo = getPossibleCapacityByCargo(fleet); + return Math.min(possibleCapacityFleet, possibleCapacityCargo); + } + + protected int getPossibleCapacityByCargo(CampaignFleetAPI fleet) { + if (hasSecondaryInput) { + return (int) Math.min((getAvailablePrimaryInputs(fleet) / cyclePrimaryInputAmount), + (getAvailableSecondaryInputs(fleet) / cycleSecondaryInputAmount)); + } else { + return (int) (getAvailablePrimaryInputs(fleet) / cyclePrimaryInputAmount); + } + } + + public float getCycleOutputBonus(CampaignFleetAPI fleet) { + switch (productionType) { + case METALS_PRODUCTION: + case TRANSPLUTONICS_PRODUCTION: + return ItemBonusesChecks.getRefiningBonus(fleet); + case FUEL_PRODUCTION: + return ItemBonusesChecks.getFuelProductionBonus(fleet); + case SUPPLIES_PRODUCTION: + case MACHINERY_PRODUCTION: + return ItemBonusesChecks.getHeavyIndustryBonus(fleet); + } + return 0f; + } + + protected void commenceProduction(CampaignFleetAPI fleet) { + int cycles = getMinimalCapacity(fleet); + float dailyPrimaryInputsSpent = cyclePrimaryInputAmount * cycles; + float dailySecondaryInputsSpent = 0; + if (hasSecondaryInput) { + dailySecondaryInputsSpent = cycleSecondaryInputAmount * cycles; + } + float dailyOutputProduced = (cycleOutputAmount + getCycleOutputBonus(fleet)) * cycles; + float dailyMachineryUsed = cycleMachineryUse * cycles; + + fleet.getCargo().removeCommodity(primaryInputType, dailyPrimaryInputsSpent); + if (hasSecondaryInput) { + fleet.getCargo().removeCommodity(secondaryInputType, dailySecondaryInputsSpent); + } + fleet.getCargo().addCommodity(outputType, dailyOutputProduced); + this.setReportFlags(dailyOutputProduced); + this.addMachineryUsed(dailyMachineryUsed); + this.setModuleFlags(); + } + + // Needs to be overridden to set specific production report flags. + protected void setReportFlags(float amountProduced) {} + + protected void addMachineryUsed(float amountUsed) { + BreakdownLogic.getInstance().addToTotalDailyMachineryUsed(amountUsed); + } + + protected void setModuleFlags() { + FleetwideModuleManager managerInstance = FleetwideModuleManager.getInstance(); + Set fleetModules = new HashSet<>(managerInstance.getModuleIndex().values()); + for (ProductionModule module : fleetModules) { + if (module.hasSpecificActiveCapacity(productionType)) { + module.setProducedToday(true); + } + } + } +} diff --git a/source/forgprod/abilities/conversion/logic/production/FuelProductionLogic.java b/source/forgprod/abilities/conversion/logic/production/FuelProductionLogic.java new file mode 100644 index 0000000..1617fe1 --- /dev/null +++ b/source/forgprod/abilities/conversion/logic/production/FuelProductionLogic.java @@ -0,0 +1,56 @@ +package forgprod.abilities.conversion.logic.production; + +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.impl.campaign.ids.Commodities; + +import forgprod.abilities.conversion.logic.base.ProductionLogic; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.conversion.support.ConversionVariables; + +import static forgprod.abilities.conversion.support.ProductionConstants.*; + +/** + * @author Ontheheavens + * @since 04.12.2022 + */ + +public class FuelProductionLogic extends ProductionLogic { + + private static FuelProductionLogic logicInstance; + + protected FuelProductionLogic() { + productionType = ProductionType.FUEL_PRODUCTION; + hasSecondaryInput = false; + cyclePrimaryInputAmount = VOLATILES_INPUT; + cycleOutputAmount = FUEL_OUTPUT; + cycleMachineryUse = MACHINERY_USAGE.get(productionType); + primaryInputType = Commodities.VOLATILES; + outputType = Commodities.FUEL; + } + + public static FuelProductionLogic getInstance() { + if (logicInstance == null) { + logicInstance = new FuelProductionLogic(); + } + return logicInstance; + } + + @Override + public void initializeProduction(CampaignFleetAPI fleet) { + if (areFuelTanksFull(fleet)) { + return; + } + super.initializeProduction(fleet); + } + + public boolean areFuelTanksFull(CampaignFleetAPI fleet) { + return fleet.getCargo().getFreeFuelSpace() < (cycleOutputAmount + getCycleOutputBonus(fleet)); + } + + @Override + protected void setReportFlags(float amountProduced) { + ConversionVariables.fuelDailyFlag = true; + ConversionVariables.totalFuelProduced += amountProduced; + } + +} diff --git a/source/forgprod/abilities/conversion/logic/production/HullPartsProductionLogic.java b/source/forgprod/abilities/conversion/logic/production/HullPartsProductionLogic.java new file mode 100644 index 0000000..fc672b0 --- /dev/null +++ b/source/forgprod/abilities/conversion/logic/production/HullPartsProductionLogic.java @@ -0,0 +1,270 @@ +package forgprod.abilities.conversion.logic.production; + +import java.util.List; +import java.util.Random; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.campaign.CargoAPI; +import com.fs.starfarer.api.campaign.FleetDataAPI; +import com.fs.starfarer.api.combat.ShieldAPI; +import com.fs.starfarer.api.combat.ShipVariantAPI; +import com.fs.starfarer.api.fleet.FleetMemberAPI; +import com.fs.starfarer.api.fleet.FleetMemberType; +import com.fs.starfarer.api.impl.campaign.DModManager; +import com.fs.starfarer.api.impl.campaign.FleetEncounterContext; +import com.fs.starfarer.api.impl.campaign.fleets.DefaultFleetInflater; +import com.fs.starfarer.api.impl.campaign.ids.Commodities; +import com.fs.starfarer.api.impl.campaign.ids.Factions; +import com.fs.starfarer.api.impl.campaign.ids.Items; + +import forgprod.abilities.conversion.logic.base.ProductionLogic; +import forgprod.abilities.conversion.support.ConversionVariables; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.settings.SettingsHolder; + +import static forgprod.abilities.conversion.support.ProductionConstants.*; +import static forgprod.abilities.conversion.support.checks.ItemBonusesChecks.hasSpecialItem; +import static forgprod.settings.SettingsHolder.*; + +/** + * Modified version of production logic, supports four input types. + * @author Ontheheavens + * @since 16.01.2023 + */ + +public class HullPartsProductionLogic extends ProductionLogic { + + private static HullPartsProductionLogic logicInstance; + protected float cycleTertiaryInputAmount; + protected float cycleQuaternaryInputAmount; + protected String tertiaryInputType; + protected String quaternaryInputType; + + protected HullPartsProductionLogic() { + productionType = ProductionType.HULL_PARTS_PRODUCTION; + hasSecondaryInput = true; + cyclePrimaryInputAmount = METALS_INPUT_HULL_PARTS; + cycleSecondaryInputAmount = TRANSPLUTONICS_INPUT_HULL_PARTS; + cycleTertiaryInputAmount = SUPPLIES_INPUT_HULL_PARTS; + cycleQuaternaryInputAmount = MACHINERY_INPUT_HULL_PARTS; + cycleOutputAmount = HULL_PARTS_OUTPUT; + cycleMachineryUse = MACHINERY_USAGE.get(productionType); + primaryInputType = Commodities.METALS; + secondaryInputType = Commodities.RARE_METALS; + tertiaryInputType = Commodities.SUPPLIES; + quaternaryInputType = Commodities.HEAVY_MACHINERY; + outputType = null; + } + + public static HullPartsProductionLogic getInstance() { + if (logicInstance == null) { + logicInstance = new HullPartsProductionLogic(); + } + return logicInstance; + } + + public String getTertiaryInputType() { + return tertiaryInputType; + } + + public String getQuaternaryInputType() { + return quaternaryInputType; + } + + public float getCycleTertiaryInputAmount() { + return cycleTertiaryInputAmount; + } + + public float getCycleQuaternaryInputAmount() { + return cycleQuaternaryInputAmount; + } + + @Override + public void initializeProduction(CampaignFleetAPI fleet) { + if (isPlayerFleetRosterFull() || !hasOperationalSalvageRigs()) { + return; + } + super.initializeProduction(fleet); + } + + public boolean isPlayerFleetRosterFull() { + CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); + return playerFleet.getFleetData().getNumMembers() >= Global.getSettings().getMaxShipsInFleet(); + } + + public boolean hasOperationalSalvageRigs() { + return getActiveSalvageRigsNumber() >= 1; + } + + public int getActiveSalvageRigsNumber() { + CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); + List membersList = playerFleet.getFleetData().getMembersListCopy(); + int count = 0; + for (FleetMemberAPI member : membersList) { + if (!member.getHullSpec().getBaseHullId().equals("crig")) { + continue; + } + boolean mothballed = member.getRepairTracker().isMothballed(); + boolean hasMinCR = member.getRepairTracker().getCR() >= 0.2f; + if (!mothballed || hasMinCR) { + count += 1; + } + } + return count; + } + + @Override + protected boolean hasMinimumCargo(CampaignFleetAPI fleet) { + boolean hasPrimaryAndSecondaryInputs = (getAvailablePrimaryInputs(fleet) > cyclePrimaryInputAmount && + getAvailableSecondaryInputs(fleet) > cycleSecondaryInputAmount); + boolean hasTertiaryAndQuaternaryInputs = (getAvailableTertiaryInputs(fleet) > cycleTertiaryInputAmount && + getAvailableQuaternaryInputs(fleet) > cycleQuaternaryInputAmount); + return hasPrimaryAndSecondaryInputs && hasTertiaryAndQuaternaryInputs; + } + + protected float getAvailableTertiaryInputs(CampaignFleetAPI fleet) { + return fleet.getCargo().getCommodityQuantity(tertiaryInputType); + } + + protected float getAvailableQuaternaryInputs(CampaignFleetAPI fleet) { + return fleet.getCargo().getCommodityQuantity(quaternaryInputType); + } + + @Override + protected int getPossibleCapacityByCargo(CampaignFleetAPI fleet) { + float primaryAndSecondaryInputs = Math.min((getAvailablePrimaryInputs(fleet) / cyclePrimaryInputAmount), + (getAvailableSecondaryInputs(fleet) / cycleSecondaryInputAmount)); + float tertiaryAndQuaternaryInputs = Math.min((getAvailableTertiaryInputs(fleet) / cycleTertiaryInputAmount), + (getAvailableQuaternaryInputs(fleet) / cycleQuaternaryInputAmount)); + return (int) Math.min(primaryAndSecondaryInputs, tertiaryAndQuaternaryInputs); + } + + public int getPossibleAssemblingCapacity() { + float capacityFromOneRig = SettingsHolder.HULL_ASSEMBLING_CAPACITY_PER_RIG * SettingsHolder.GRANULARITY; + float totalRigsCapacity = this.getActiveSalvageRigsNumber() * capacityFromOneRig; + return (int) totalRigsCapacity; + } + + // Hull Parts Production does not receive item bonuses. + @Override + public float getCycleOutputBonus(CampaignFleetAPI fleet) { + return 0f; + } + + // Should be maximum of 0.8f, so you still get one d-mod and planetary production has more quality. + public float getHullQuality() { + CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); + float quality = 0.2f; // Base quality for autoforge parts production. + if (hasOperationalSalvageRigs()) { + quality += 0.1f; // Quality level of assembling hulls with Rigs. + } + if (hasSpecialItem(playerFleet, Items.PRISTINE_NANOFORGE)) { + quality += PRISTINE_NANOFORGE_BREAKDOWN_DECREASE; + } else if (hasSpecialItem(playerFleet, Items.CORRUPTED_NANOFORGE)) { + quality += CORRUPTED_NANOFORGE_BREAKDOWN_DECREASE; + } + return quality; + } + + public float computeDesignatedHullCostInParts() { + ShipVariantAPI designatedVariant = FleetwideModuleManager.getInstance().getDesignatedVariant(); + return computeHullCostInParts(designatedVariant); + } + + public float computeHullCostInParts(ShipVariantAPI variant) { + float designValue = getHullPrice(variant); + float singlePartValue = getSingleHullPartValue(); + if (variant.getHullSpec().hasTag(Factions.DERELICT)) { + designValue = designValue * 0.6f; + } else { + designValue = designValue * 1.8f; + } + if (variant.getHullSpec().getShieldType() != ShieldAPI.ShieldType.NONE) { + designValue = designValue * 1.2f; + } + designValue = designValue * SettingsHolder.HULL_COST_MULTIPLIER; + return (float) Math.ceil(designValue / singlePartValue); + } + + public float getHullPrice(ShipVariantAPI designatedVariant) { + FleetMemberAPI dummyMember = Global.getFactory().createFleetMember(FleetMemberType.SHIP, designatedVariant); + return dummyMember.getBaseBuyValue(); + } + + private float getSingleHullPartValue() { + float metalsValue = BASE_METAL_INPUT_HULL_PARTS * getCommodityBaseValue(Commodities.METALS); + float transplutonicsValue = BASE_TRANSPLUTONICS_INPUT_HULL_PARTS * getCommodityBaseValue(Commodities.RARE_METALS); + float suppliesValue = BASE_SUPPLIES_INPUT_HULL_PARTS * getCommodityBaseValue(Commodities.SUPPLIES); + float machineryValue = BASE_MACHINERY_INPUT_HULL_PARTS * getCommodityBaseValue(Commodities.HEAVY_MACHINERY); + return metalsValue + transplutonicsValue + suppliesValue + machineryValue; + } + + private float getCommodityBaseValue(String commodityId) { + return Global.getSettings().getCommoditySpec(commodityId).getBasePrice(); + } + + private boolean attemptHullAssembly() { + FleetwideModuleManager manager = FleetwideModuleManager.getInstance(); + float hullCost = computeDesignatedHullCostInParts(); + if (hullCost <= manager.getAccumulatedHullParts()) { + manager.updateAccumulatedHullParts(-hullCost); + FleetDataAPI fleetData = Global.getSector().getPlayerFleet().getFleetData(); + FleetMemberAPI newHull = Global.getFactory().createFleetMember(FleetMemberType.SHIP, manager.getDesignatedVariant()); + this.addAssembledHullToFleet(newHull, fleetData); + return true; + } + return false; + } + + private void addAssembledHullToFleet(FleetMemberAPI newHull, FleetDataAPI fleetData) { + FleetEncounterContext.prepareShipForRecovery(newHull, true, true, + true, 1f, 1f, new Random()); + newHull.getRepairTracker().setCR(0.4f); + float averageDMods = DefaultFleetInflater.getAverageDmodsForQuality(getHullQuality()); + Random itemGenRandom = new Random(); + int addDMods = DefaultFleetInflater.getNumDModsToAdd(newHull.getVariant(), averageDMods, itemGenRandom); + if (addDMods > 0) { + DModManager.setDHull(newHull.getVariant()); + DModManager.addDMods(newHull, true, addDMods, itemGenRandom); + } + fleetData.addFleetMember(newHull); + } + + @Override + protected void commenceProduction(CampaignFleetAPI fleet) { + int cycles = Math.min(getMinimalCapacity(fleet), getPossibleAssemblingCapacity()); + float dailyPrimaryInputsSpent = cyclePrimaryInputAmount * cycles; + float dailySecondaryInputsSpent = cycleSecondaryInputAmount * cycles; + float dailyTertiaryInputsSpent = cycleTertiaryInputAmount * cycles; + float dailyQuaternaryInputsSpent = cycleQuaternaryInputAmount * cycles; + float dailyOutputProduced = cycleOutputAmount * cycles; + float dailyMachineryUsed = cycleMachineryUse * cycles; + CargoAPI cargo = fleet.getCargo(); + cargo.removeCommodity(primaryInputType, dailyPrimaryInputsSpent); + cargo.removeCommodity(secondaryInputType, dailySecondaryInputsSpent); + cargo.removeCommodity(tertiaryInputType, dailyTertiaryInputsSpent); + cargo.removeCommodity(quaternaryInputType, dailyQuaternaryInputsSpent); + FleetwideModuleManager manager = FleetwideModuleManager.getInstance(); + manager.updateAccumulatedHullParts(dailyOutputProduced); + float partsUpdatedAmount; + int hullsAssembled = 0; + do { + if (attemptHullAssembly()) { + hullsAssembled++; + } + partsUpdatedAmount = manager.getAccumulatedHullParts(); + } while (partsUpdatedAmount > computeDesignatedHullCostInParts()); + this.setReportFlags(hullsAssembled); + this.addMachineryUsed(dailyMachineryUsed); + this.setModuleFlags(); + } + + @Override + protected void setReportFlags(float amountProduced) { + ConversionVariables.hullPartsDailyFlag = true; + ConversionVariables.totalHullsProduced += amountProduced; + } + +} diff --git a/source/forgprod/abilities/conversion/logic/production/MachineryProductionLogic.java b/source/forgprod/abilities/conversion/logic/production/MachineryProductionLogic.java new file mode 100644 index 0000000..a81a8d6 --- /dev/null +++ b/source/forgprod/abilities/conversion/logic/production/MachineryProductionLogic.java @@ -0,0 +1,45 @@ +package forgprod.abilities.conversion.logic.production; + +import com.fs.starfarer.api.impl.campaign.ids.Commodities; + +import forgprod.abilities.conversion.logic.base.ProductionLogic; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.conversion.support.ConversionVariables; + +import static forgprod.abilities.conversion.support.ProductionConstants.*; + +/** + * @author Ontheheavens + * @since 04.12.2022 + */ + +public class MachineryProductionLogic extends ProductionLogic { + + private static MachineryProductionLogic logicInstance; + + protected MachineryProductionLogic() { + productionType = ProductionType.MACHINERY_PRODUCTION; + hasSecondaryInput = true; + cyclePrimaryInputAmount = METALS_INPUT_MACHINERY; + cycleSecondaryInputAmount = TRANSPLUTONICS_INPUT_MACHINERY; + cycleOutputAmount = MACHINERY_OUTPUT; + cycleMachineryUse = MACHINERY_USAGE.get(productionType); + primaryInputType = Commodities.METALS; + secondaryInputType = Commodities.RARE_METALS; + outputType = Commodities.HEAVY_MACHINERY; + } + + public static MachineryProductionLogic getInstance() { + if (logicInstance == null) { + logicInstance = new MachineryProductionLogic(); + } + return logicInstance; + } + + @Override + protected void setReportFlags(float amountProduced) { + ConversionVariables.machineryDailyFlag = true; + ConversionVariables.totalMachineryProduced += amountProduced; + } + +} diff --git a/source/forgprod/abilities/conversion/logic/production/MetalsProductionLogic.java b/source/forgprod/abilities/conversion/logic/production/MetalsProductionLogic.java new file mode 100644 index 0000000..c694b5e --- /dev/null +++ b/source/forgprod/abilities/conversion/logic/production/MetalsProductionLogic.java @@ -0,0 +1,43 @@ +package forgprod.abilities.conversion.logic.production; + +import com.fs.starfarer.api.impl.campaign.ids.Commodities; + +import forgprod.abilities.conversion.logic.base.ProductionLogic; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.conversion.support.ConversionVariables; + +import static forgprod.abilities.conversion.support.ProductionConstants.*; + +/** + * @author Ontheheavens + * @since 04.12.2022 + */ + +public class MetalsProductionLogic extends ProductionLogic { + + private static MetalsProductionLogic logicInstance; + + protected MetalsProductionLogic() { + productionType = ProductionType.METALS_PRODUCTION; + hasSecondaryInput = false; + cyclePrimaryInputAmount = ORE_INPUT; + cycleOutputAmount = METAL_OUTPUT; + cycleMachineryUse = MACHINERY_USAGE.get(productionType); + primaryInputType = Commodities.ORE; + outputType = Commodities.METALS; + } + + public static MetalsProductionLogic getInstance() { + if (logicInstance == null) { + logicInstance = new MetalsProductionLogic(); + } + return logicInstance; + } + + @Override + protected void setReportFlags(float amountProduced) { + ConversionVariables.metalsDailyFlag = true; + ConversionVariables.totalMetalsProduced += amountProduced; + } + +} diff --git a/source/forgprod/abilities/conversion/logic/production/SuppliesProductionLogic.java b/source/forgprod/abilities/conversion/logic/production/SuppliesProductionLogic.java new file mode 100644 index 0000000..7240f11 --- /dev/null +++ b/source/forgprod/abilities/conversion/logic/production/SuppliesProductionLogic.java @@ -0,0 +1,45 @@ +package forgprod.abilities.conversion.logic.production; + +import com.fs.starfarer.api.impl.campaign.ids.Commodities; + +import forgprod.abilities.conversion.logic.base.ProductionLogic; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.conversion.support.ConversionVariables; + +import static forgprod.abilities.conversion.support.ProductionConstants.*; + +/** + * @author Ontheheavens + * @since 04.12.2022 + */ + +public class SuppliesProductionLogic extends ProductionLogic { + + private static SuppliesProductionLogic logicInstance; + + protected SuppliesProductionLogic() { + productionType = ProductionType.SUPPLIES_PRODUCTION; + hasSecondaryInput = true; + cyclePrimaryInputAmount = METALS_INPUT_SUPPLIES; + cycleSecondaryInputAmount = TRANSPLUTONICS_INPUT_SUPPLIES; + cycleOutputAmount = SUPPLIES_OUTPUT; + cycleMachineryUse = MACHINERY_USAGE.get(productionType); + primaryInputType = Commodities.METALS; + secondaryInputType = Commodities.RARE_METALS; + outputType = Commodities.SUPPLIES; + } + + public static SuppliesProductionLogic getInstance() { + if (logicInstance == null) { + logicInstance = new SuppliesProductionLogic(); + } + return logicInstance; + } + + @Override + protected void setReportFlags(float amountProduced) { + ConversionVariables.suppliesDailyFlag = true; + ConversionVariables.totalSuppliesProduced += amountProduced; + } + +} diff --git a/source/forgprod/abilities/conversion/logic/production/TransplutonicsProductionLogic.java b/source/forgprod/abilities/conversion/logic/production/TransplutonicsProductionLogic.java new file mode 100644 index 0000000..e89e714 --- /dev/null +++ b/source/forgprod/abilities/conversion/logic/production/TransplutonicsProductionLogic.java @@ -0,0 +1,47 @@ +package forgprod.abilities.conversion.logic.production; + +import com.fs.starfarer.api.impl.campaign.ids.Commodities; + +import forgprod.abilities.conversion.logic.base.ProductionLogic; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.conversion.support.ConversionVariables; + +import static forgprod.abilities.conversion.support.ProductionConstants.*; + +/** + * New iteration of commodity production logic. + * Singleton-pattern class. + *

+ * + * @author Ontheheavens + * @since 03.12.2022 + */ + +public class TransplutonicsProductionLogic extends ProductionLogic { + + private static TransplutonicsProductionLogic logicInstance; + + protected TransplutonicsProductionLogic() { + productionType = ProductionType.TRANSPLUTONICS_PRODUCTION; + hasSecondaryInput = false; + cyclePrimaryInputAmount = TRANSPLUTONIC_ORE_INPUT; + cycleOutputAmount = TRANSPLUTONICS_OUTPUT; + cycleMachineryUse = MACHINERY_USAGE.get(productionType); + primaryInputType = Commodities.RARE_ORE; + outputType = Commodities.RARE_METALS; + } + + public static TransplutonicsProductionLogic getInstance() { + if (logicInstance == null) { + logicInstance = new TransplutonicsProductionLogic(); + } + return logicInstance; + } + + @Override + protected void setReportFlags(float amountProduced) { + ConversionVariables.transplutonicsDailyFlag = true; + ConversionVariables.totalTransplutonicsProduced += amountProduced; + } + +} diff --git a/source/forgprod/abilities/conversion/support/ConversionVariables.java b/source/forgprod/abilities/conversion/support/ConversionVariables.java new file mode 100644 index 0000000..f0a3ecd --- /dev/null +++ b/source/forgprod/abilities/conversion/support/ConversionVariables.java @@ -0,0 +1,91 @@ +package forgprod.abilities.conversion.support; + +import java.util.HashSet; +import java.util.Set; + +/** + * Storage of temporary production flags and values. + *

+ * + * @author Ontheheavens + * @since 30.11.2021 + */ + +public class ConversionVariables { + + // Here: Daily flags, used and cleared each time production event happens. + // Set to true only if their specific production happened, otherwise stay false. + // Used for applying CR malus to ships and displaying float notes/ playing sounds. + + public static boolean metalsDailyFlag = false; + public static boolean transplutonicsDailyFlag = false; + public static boolean fuelDailyFlag = false; + public static boolean suppliesDailyFlag = false; + public static boolean machineryDailyFlag = false; + public static boolean hullPartsDailyFlag = false; + + public static boolean hasGoodsDailyFlags() { + Set goodsDailyFlag = new HashSet<>(); + goodsDailyFlag.add(metalsDailyFlag); + goodsDailyFlag.add(transplutonicsDailyFlag); + goodsDailyFlag.add(fuelDailyFlag); + goodsDailyFlag.add(suppliesDailyFlag); + goodsDailyFlag.add(machineryDailyFlag); + goodsDailyFlag.add(hullPartsDailyFlag); + for (boolean produced : goodsDailyFlag) { + if (produced) { return true; } + } + return false; + } + + public static void clearGoodsDailyFlags() { + metalsDailyFlag = false; + transplutonicsDailyFlag = false; + fuelDailyFlag = false; + suppliesDailyFlag = false; + machineryDailyFlag = false; + hullPartsDailyFlag = false; + } + + // Here: Production report variables, accrue each time production event happens. + // Used for production report, cleared after report is displayed. + + public static float totalMetalsProduced = 0f; + public static float totalTransplutonicsProduced = 0f; + public static float totalFuelProduced = 0f; + public static float totalSuppliesProduced = 0f; + public static float totalMachineryProduced = 0f; + public static float totalHullsProduced = 0f; + public static float totalMachineryBroke = 0f; + public static float totalCrewmenLost = 0f; + + public static boolean goodsProducedReport() { + Set totalGoodsTally = new HashSet<>(); + totalGoodsTally.add(totalMetalsProduced); + totalGoodsTally.add(totalTransplutonicsProduced); + totalGoodsTally.add(totalFuelProduced); + totalGoodsTally.add(totalSuppliesProduced); + totalGoodsTally.add(totalMachineryProduced); + totalGoodsTally.add(totalHullsProduced); + totalGoodsTally.add(totalMachineryBroke); + totalGoodsTally.add(totalCrewmenLost); + for (float produced : totalGoodsTally) { + if (produced >= 1f) { + return true; + } + } + return false; + } + + public static void clearReportVariables() { + totalMetalsProduced = 0f; + totalTransplutonicsProduced = 0f; + totalFuelProduced = 0f; + totalSuppliesProduced = 0f; + totalMachineryProduced = 0f; + totalHullsProduced = 0f; + totalMachineryBroke = 0f; + totalCrewmenLost = 0f; + } + +} diff --git a/source/forgprod/abilities/conversion/support/ProductionConstants.java b/source/forgprod/abilities/conversion/support/ProductionConstants.java new file mode 100644 index 0000000..b5a9d36 --- /dev/null +++ b/source/forgprod/abilities/conversion/support/ProductionConstants.java @@ -0,0 +1,111 @@ +package forgprod.abilities.conversion.support; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.fs.starfarer.api.combat.ShipAPI; + +import forgprod.settings.SettingsHolder; + +import static forgprod.settings.SettingsHolder.*; + +public class ProductionConstants { + + public static final String REFINING_MODULE = "forgprod_refining_module"; + public static final String FUEL_PRODUCTION_MODULE = "forgprod_fuel_production_module"; + public static final String HEAVY_INDUSTRY_MODULE = "forgprod_heavy_industry_module"; + + public static float ORE_INPUT = ((float) BASE_ORE_INPUT / GRANULARITY); + public static float METAL_OUTPUT = ((float) BASE_METAL_OUTPUT / GRANULARITY); + + public static float TRANSPLUTONIC_ORE_INPUT = ((float) BASE_TRANSPLUTONIC_ORE_INPUT / GRANULARITY); + public static float TRANSPLUTONICS_OUTPUT = ((float) BASE_TRANSPLUTONICS_OUTPUT / GRANULARITY); + + public static float VOLATILES_INPUT = ((float) BASE_VOLATILES_INPUT / GRANULARITY); + public static float FUEL_OUTPUT = ((float) BASE_FUEL_OUTPUT / GRANULARITY); + + public static float METALS_INPUT_SUPPLIES = ((float) BASE_METALS_INPUT_SUPPLIES / GRANULARITY); + public static float TRANSPLUTONICS_INPUT_SUPPLIES = ((float) BASE_TRANSPLUTONICS_INPUT_SUPPLIES / GRANULARITY); + public static float SUPPLIES_OUTPUT = ((float) BASE_SUPPLIES_OUTPUT / GRANULARITY); + + public static float METALS_INPUT_MACHINERY = ((float) BASE_METAL_INPUT_MACHINERY / GRANULARITY); + public static float TRANSPLUTONICS_INPUT_MACHINERY = ((float) BASE_TRANSPLUTONICS_INPUT_MACHINERY / GRANULARITY); + public static float MACHINERY_OUTPUT = ((float) BASE_MACHINERY_OUTPUT / GRANULARITY); + + public static float METALS_INPUT_HULL_PARTS = ((float) BASE_METAL_INPUT_HULL_PARTS / GRANULARITY); + public static float TRANSPLUTONICS_INPUT_HULL_PARTS = ((float) BASE_TRANSPLUTONICS_INPUT_HULL_PARTS / GRANULARITY); + public static float SUPPLIES_INPUT_HULL_PARTS = ((float) BASE_SUPPLIES_INPUT_HULL_PARTS / GRANULARITY); + public static float MACHINERY_INPUT_HULL_PARTS = ((float) BASE_MACHINERY_INPUT_HULL_PARTS / GRANULARITY); + public static float HULL_PARTS_OUTPUT = ((float) BASE_HULL_PARTS_OUTPUT / GRANULARITY); + + public static final String REFINING_MODULE_DESC = "Machinery and infrastructure needed for refining processes, " + + "including dedicated purification complex and universal auto-smelter."; + public static final String FUEL_PRODUCTION_MODULE_DESC = "High-tech isotope processing facility " + + "and particle deceleration plant for small-scale starship fuel synthesis."; + public static final String HEAVY_INDUSTRY_MODULE_DESC = "Multi-purpose autoforge array capable of " + + "producing spaceship supplies, hull parts and heavy industrial equipment."; + + public static final Map HULLMOD_DESCRIPTIONS = new HashMap<>(); + static { + HULLMOD_DESCRIPTIONS.put(ProductionConstants.REFINING_MODULE, REFINING_MODULE_DESC); + HULLMOD_DESCRIPTIONS.put(ProductionConstants.FUEL_PRODUCTION_MODULE, FUEL_PRODUCTION_MODULE_DESC); + HULLMOD_DESCRIPTIONS.put(ProductionConstants.HEAVY_INDUSTRY_MODULE, HEAVY_INDUSTRY_MODULE_DESC); + } + + public static final Map HULLMOD_BY_TYPE = new HashMap<>(); + static { + HULLMOD_BY_TYPE.put(ProductionType.METALS_PRODUCTION, REFINING_MODULE); + HULLMOD_BY_TYPE.put(ProductionType.TRANSPLUTONICS_PRODUCTION, REFINING_MODULE); + HULLMOD_BY_TYPE.put(ProductionType.FUEL_PRODUCTION, FUEL_PRODUCTION_MODULE); + HULLMOD_BY_TYPE.put(ProductionType.SUPPLIES_PRODUCTION, HEAVY_INDUSTRY_MODULE); + HULLMOD_BY_TYPE.put(ProductionType.MACHINERY_PRODUCTION, HEAVY_INDUSTRY_MODULE); + HULLMOD_BY_TYPE.put(ProductionType.HULL_PARTS_PRODUCTION, HEAVY_INDUSTRY_MODULE); + } + + public static final Map MACHINERY_USAGE = new HashMap<>(); + static { + MACHINERY_USAGE.put(ProductionType.METALS_PRODUCTION, ((float)BASE_MACHINERY_USE_METALS / GRANULARITY)); + MACHINERY_USAGE.put(ProductionType.TRANSPLUTONICS_PRODUCTION, ((float)BASE_MACHINERY_USE_TRANSPLUTONICS / GRANULARITY)); + MACHINERY_USAGE.put(ProductionType.FUEL_PRODUCTION, ((float)BASE_MACHINERY_USE_FUEL / GRANULARITY)); + MACHINERY_USAGE.put(ProductionType.SUPPLIES_PRODUCTION, ((float)BASE_MACHINERY_USE_SUPPLIES / GRANULARITY)); + MACHINERY_USAGE.put(ProductionType.MACHINERY_PRODUCTION, ((float)BASE_MACHINERY_USE_MACHINERY / GRANULARITY)); + MACHINERY_USAGE.put(ProductionType.HULL_PARTS_PRODUCTION, ((float) BASE_MACHINERY_USE_HULL_PARTS / GRANULARITY)); + } + + public static final Set ALL_HULLMODS = new HashSet<>(); + static { + ALL_HULLMODS.add(ProductionConstants.REFINING_MODULE); + ALL_HULLMODS.add(ProductionConstants.FUEL_PRODUCTION_MODULE); + ALL_HULLMODS.add(ProductionConstants.HEAVY_INDUSTRY_MODULE); + } + + public static final Map HULLMOD_NAMES = new HashMap<>(); + static { + HULLMOD_NAMES.put(ProductionConstants.REFINING_MODULE, "Refining Module"); + HULLMOD_NAMES.put(ProductionConstants.FUEL_PRODUCTION_MODULE, "Fuel Production Module"); + HULLMOD_NAMES.put(ProductionConstants.HEAVY_INDUSTRY_MODULE, "Heavy Industry Module"); + } + + public static final Map PRODUCTION_NAMES = new HashMap<>(); + static { + PRODUCTION_NAMES.put(ProductionType.METALS_PRODUCTION, "Metals"); + PRODUCTION_NAMES.put(ProductionType.TRANSPLUTONICS_PRODUCTION, "Transplutonics"); + PRODUCTION_NAMES.put(ProductionType.FUEL_PRODUCTION, "Fuel"); + PRODUCTION_NAMES.put(ProductionType.SUPPLIES_PRODUCTION, "Supplies"); + PRODUCTION_NAMES.put(ProductionType.MACHINERY_PRODUCTION, "Machinery"); + PRODUCTION_NAMES.put(ProductionType.HULL_PARTS_PRODUCTION, "Hull Parts"); + } + + public static Map SHIPSIZE_CAPACITY = new HashMap<>(); + static { + + SHIPSIZE_CAPACITY.put(ShipAPI.HullSize.FRIGATE, SettingsHolder.BASE_CAPACITY_FRIGATE); + SHIPSIZE_CAPACITY.put(ShipAPI.HullSize.DESTROYER, SettingsHolder.BASE_CAPACITY_DESTROYER); + SHIPSIZE_CAPACITY.put(ShipAPI.HullSize.CRUISER, SettingsHolder.BASE_CAPACITY_CRUISER); + SHIPSIZE_CAPACITY.put(ShipAPI.HullSize.CAPITAL_SHIP, SettingsHolder.BASE_CAPACITY_CAPITAL); + + } + +} diff --git a/source/forgprod/abilities/conversion/support/ProductionType.java b/source/forgprod/abilities/conversion/support/ProductionType.java new file mode 100644 index 0000000..e201676 --- /dev/null +++ b/source/forgprod/abilities/conversion/support/ProductionType.java @@ -0,0 +1,17 @@ +package forgprod.abilities.conversion.support; + +/** + * @author Ontheheavens + * @since 03.12.2022 + */ + +public enum ProductionType { + + METALS_PRODUCTION, + TRANSPLUTONICS_PRODUCTION, + FUEL_PRODUCTION, + SUPPLIES_PRODUCTION, + MACHINERY_PRODUCTION, + HULL_PARTS_PRODUCTION + +} diff --git a/source/forgprod/abilities/conversion/support/checks/FleetwideProductionChecks.java b/source/forgprod/abilities/conversion/support/checks/FleetwideProductionChecks.java new file mode 100644 index 0000000..8065750 --- /dev/null +++ b/source/forgprod/abilities/conversion/support/checks/FleetwideProductionChecks.java @@ -0,0 +1,142 @@ +package forgprod.abilities.conversion.support.checks; + +import java.util.*; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.combat.ShipAPI; +import com.fs.starfarer.api.fleet.FleetMemberAPI; +import com.fs.starfarer.api.impl.campaign.ids.Commodities; + +import forgprod.abilities.conversion.support.ProductionConstants; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.abilities.modules.dataholders.ProductionCapacity; +import forgprod.abilities.modules.dataholders.ProductionModule; +import forgprod.settings.SettingsHolder; + +import static forgprod.abilities.conversion.support.checks.ItemBonusesChecks.getNanoforgeBreakdownDecrease; + +public class FleetwideProductionChecks { + + public static int getFleetCapacityForType(CampaignFleetAPI fleet, ProductionType type) { + int fleetCapacity = 0; + Set fleetCapacities = FleetwideModuleManager.getInstance().getCapacitiesIndex(); + if (fleetCapacities.isEmpty()) { + return 0; + } + for (ProductionCapacity capacity : fleetCapacities) { + if (capacity.getProductionType() == type) { + if (!capacity.isActive()) { continue; } + if (capacity.getCurrentThroughput() <= 0f) { continue; } + fleetCapacity += capacity.getModifiedCapacity(fleet); + } + } + return fleetCapacity; + } + + public static int getTotalModuleVolume() { + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + int result = 0; + List hullmods = new ArrayList<>(ProductionConstants.ALL_HULLMODS); + for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) { + if (!Collections.disjoint(member.getVariant().getHullMods(), hullmods)) { + ShipAPI.HullSize memberSize = member.getHullSpec().getHullSize(); + int moduleSize = ProductionConstants.SHIPSIZE_CAPACITY.get(memberSize); + result += moduleSize; + } + } + return result; + } + + public static int getTotalModuleCount() { + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + int result = 0; + List hullmods = new ArrayList<>(ProductionConstants.ALL_HULLMODS); + for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) { + if (!Collections.disjoint(member.getVariant().getHullMods(), hullmods)) { + result += 1; + } + } + return result; + } + + public static float getFleetMachineryRequirement() { + float totalRequirement = 0; + Set fleetCapacities = FleetwideModuleManager.getInstance().getCapacitiesIndex(); + for (ProductionCapacity capacity : fleetCapacities) { + if (!capacity.isActive()) { continue; } + totalRequirement += capacity.getMachineryUse(); + } + return totalRequirement; + } + + public static float getMachineryAvailability(CampaignFleetAPI fleet) { + int machineryInCargo = (int) Math.floor(fleet.getCargo().getCommodityQuantity(Commodities.HEAVY_MACHINERY)); + float machineryRequiredTotal = getFleetMachineryRequirement(); + float machineryAvailability = machineryInCargo / machineryRequiredTotal; + if (machineryAvailability > 1f) { machineryAvailability = 1f; } + return machineryAvailability; + } + + public static float getBreakdownChance(CampaignFleetAPI fleet) { + return SettingsHolder.BASE_BREAKDOWN_CHANCE * getNanoforgeBreakdownDecrease(fleet); + } + + // Final accident chance is (getBreakdownChance * getFatalAccidentChance), or 0.045 by default. + // This is because accidents chances are only calculated when breakdown happens. + public static float getFatalAccidentChance(CampaignFleetAPI fleet) { + return getBreakdownChance(fleet) / 2; + } + + public static float getBaseBreakdownSeverity(CampaignFleetAPI fleet) { + return SettingsHolder.BREAKDOWN_SEVERITY * getNanoforgeBreakdownDecrease(fleet); + } + + private static float getBreakdownSeverityRange(float baseSeverity) { + return baseSeverity / 2; + } + + public static float getMinimalBreakdownSeverity(CampaignFleetAPI fleet) { + float baseSeverity = getBaseBreakdownSeverity(fleet); + float severityRange = getBreakdownSeverityRange(baseSeverity); + return baseSeverity - severityRange; + } + + public static float getMaximalBreakdownSeverity(CampaignFleetAPI fleet) { + float baseSeverity = getBaseBreakdownSeverity(fleet); + float severityRange = getBreakdownSeverityRange(baseSeverity); + return baseSeverity + severityRange; + } + + public static boolean hasMinimumMachinery(CampaignFleetAPI fleet) { + return getMachineryAvailability(fleet) >= SettingsHolder.MINIMUM_MACHINERY_PERCENT; + } + + public static boolean hasInstalledModules(CampaignFleetAPI fleet) { + Map moduleIndex = FleetwideModuleManager.getInstance().getModuleIndex(); + if (moduleIndex.isEmpty()) { + return false; + } + for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) { + if (moduleIndex.get(member) != null) { + return true; + } + } + return false; + } + + public static boolean hasActiveModules() { + Map moduleIndex = FleetwideModuleManager.getInstance().getModuleIndex(); + if (moduleIndex.isEmpty()) { + return false; + } + for (ProductionModule module : moduleIndex.values()) { + if (module.hasActiveCapacities()) { + return true; + } + } + return false; + } + +} diff --git a/source/forgprod/abilities/conversion/support/checks/ItemBonusesChecks.java b/source/forgprod/abilities/conversion/support/checks/ItemBonusesChecks.java new file mode 100644 index 0000000..595f274 --- /dev/null +++ b/source/forgprod/abilities/conversion/support/checks/ItemBonusesChecks.java @@ -0,0 +1,91 @@ +package forgprod.abilities.conversion.support.checks; + +import java.util.HashSet; +import java.util.Set; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.campaign.CargoAPI; +import com.fs.starfarer.api.campaign.SpecialItemData; +import com.fs.starfarer.api.campaign.SpecialItemSpecAPI; +import com.fs.starfarer.api.impl.campaign.ids.Items; + +import forgprod.settings.SettingsHolder; + +/** + * @author Ontheheavens + * @since 04.12.2022 + */ + +public class ItemBonusesChecks { + + public static boolean hasSpecialItem(CampaignFleetAPI fleet, String specialItemType) { + SpecialItemData specialItem = new SpecialItemData(specialItemType, null); + float specialItemQuantity = fleet.getCargo().getQuantity(CargoAPI.CargoItemType.SPECIAL, specialItem); + return ((int) Math.ceil(specialItemQuantity) >= 1); + } + + public static boolean hasAnySpecialItem(CampaignFleetAPI fleet) { + Set items = new HashSet<>(); + items.add(Items.CATALYTIC_CORE); + items.add(Items.SYNCHROTRON); + items.add(Items.CORRUPTED_NANOFORGE); + items.add(Items.PRISTINE_NANOFORGE); + for (String item : items) { + if (hasSpecialItem(fleet, item)) { + return true; + } + } + return false; + } + + // Acts as a multiplier for base breakdown chance and breakdown severity. + public static float getNanoforgeBreakdownDecrease(CampaignFleetAPI fleet) { + if (hasSpecialItem(fleet, Items.PRISTINE_NANOFORGE)) { + return (1 - SettingsHolder.PRISTINE_NANOFORGE_BREAKDOWN_DECREASE); + } + if (hasSpecialItem(fleet, Items.CORRUPTED_NANOFORGE)) { + return (1 - SettingsHolder.CORRUPTED_NANOFORGE_BREAKDOWN_DECREASE); + } + return 1f; + } + + private static SpecialItemSpecAPI getSpecialItemSpec(String itemId) { + return Global.getSettings().getSpecialItemSpec(itemId); + } + + public static SpecialItemSpecAPI getBestNanoforgeInCargoSpec(CampaignFleetAPI fleet) { + if (hasSpecialItem(fleet, Items.PRISTINE_NANOFORGE)) { + return getSpecialItemSpec(Items.PRISTINE_NANOFORGE); + } + if (hasSpecialItem(fleet, Items.CORRUPTED_NANOFORGE)) { + return getSpecialItemSpec(Items.CORRUPTED_NANOFORGE); + } + return null; + } + + public static float getRefiningBonus(CampaignFleetAPI fleet) { + if (hasSpecialItem(fleet, Items.CATALYTIC_CORE)) { + return ((float)SettingsHolder.CATALYTIC_CORE_OUTPUT_BONUS / SettingsHolder.GRANULARITY); + } + return 0f; + } + + public static float getFuelProductionBonus(CampaignFleetAPI fleet) { + if (hasSpecialItem(fleet, Items.SYNCHROTRON)) { + return ((float) SettingsHolder.SYNCHROTRON_CORE_OUTPUT_BONUS / SettingsHolder.GRANULARITY); + } + return 0f; + } + + public static float getHeavyIndustryBonus(CampaignFleetAPI fleet) { + if (hasSpecialItem(fleet, Items.PRISTINE_NANOFORGE)) { + return ((float) SettingsHolder.PRISTINE_NANOFORGE_OUTPUT_BONUS / SettingsHolder.GRANULARITY); + } + if (hasSpecialItem(fleet, Items.CORRUPTED_NANOFORGE)) { + return ((float) SettingsHolder.CORRUPTED_NANOFORGE_OUTPUT_BONUS / SettingsHolder.GRANULARITY); + } + return 0f; + } + +} diff --git a/source/forgprod/abilities/conversion/support/checks/ModuleConditionChecks.java b/source/forgprod/abilities/conversion/support/checks/ModuleConditionChecks.java new file mode 100644 index 0000000..f0e886c --- /dev/null +++ b/source/forgprod/abilities/conversion/support/checks/ModuleConditionChecks.java @@ -0,0 +1,28 @@ +package forgprod.abilities.conversion.support.checks; + +import com.fs.starfarer.api.fleet.FleetMemberAPI; + +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.abilities.modules.dataholders.ProductionModule; +import forgprod.settings.SettingsHolder; + +public class ModuleConditionChecks { + + public static boolean isOperational(FleetMemberAPI member) { + return !member.getRepairTracker().isMothballed() && + !member.getRepairTracker().isSuspendRepairs() && + hasMinimumCR(member); + } + + public static boolean hasMinimumCR(FleetMemberAPI member) { + return member.getRepairTracker().getCR() > SettingsHolder.MINIMUM_CR_PERCENT; + } + + public static boolean hasActiveModule(FleetMemberAPI member) { + ProductionModule module = FleetwideModuleManager.getInstance().getModuleIndex().get(member); + if (module != null) { + return module.hasActiveCapacities(); + } else return false; + } + +} diff --git a/source/forgprod/abilities/effects/AbilityEffects.java b/source/forgprod/abilities/effects/AbilityEffects.java new file mode 100644 index 0000000..1de88e2 --- /dev/null +++ b/source/forgprod/abilities/effects/AbilityEffects.java @@ -0,0 +1,100 @@ +package forgprod.abilities.effects; + +import org.lwjgl.util.vector.Vector2f; + +import java.awt.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.fleet.FleetMemberAPI; +import com.fs.starfarer.api.fleet.FleetMemberViewAPI; +import com.fs.starfarer.api.util.Misc; +import com.fs.starfarer.api.util.Pair; + +import forgprod.abilities.conversion.support.ConversionVariables; +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.abilities.modules.dataholders.ProductionModule; +import forgprod.abilities.conversion.support.checks.ModuleConditionChecks; +import forgprod.settings.SettingsHolder; + +/** + * @author Ontheheavens + * @since 23.11.2022 + */ +public class AbilityEffects { + + private static final Color CONTRAIL_COLOR = new Color(65, 45, 30, 255); + + public static void applyCRDecrease() { + FleetwideModuleManager managerInstance = FleetwideModuleManager.getInstance(); + Set fleetModules = new HashSet<>(managerInstance.getModuleIndex().values()); + for (ProductionModule module : fleetModules) { + if (module == null) { + continue; + } + if (module.hadProducedToday()) { + module.getParentFleetMember().getRepairTracker().applyCREvent(-SettingsHolder.DAILY_CR_DECREASE, "Forged goods"); + module.setProducedToday(false); + } + } + } + + public static void shiftFleetContrails(CampaignFleetAPI fleet, String modId, float level) { + for (FleetMemberViewAPI view : fleet.getViews()) { + FleetMemberAPI member = view.getMember(); + if ((ModuleConditionChecks.hasActiveModule(member) && ModuleConditionChecks.isOperational(member))) { + view.getContrailColor().shift(modId, CONTRAIL_COLOR, 1f, 1f, 0.5f * level); + view.getContrailWidthMult().shift(modId, 2f, 1f, 1f, level); + view.getContrailDurMult().shift(modId, 1.2f, 1f, 1f, level); + view.getEngineGlowSizeMult().shift(modId, 1.5f, 1f, 1f, level); + view.getEngineHeightMult().shift(modId, 1.5f, 1f, 1f, level); + view.getEngineWidthMult().shift(modId, 2f, 1f, 1f, level); + } + } + } + + public static void playProductionEffects (CampaignFleetAPI fleet) { + + List> refinedGoods = new ArrayList<>(); + refinedGoods.add(0, new Pair<>(ConversionVariables.metalsDailyFlag, "Produced metals")); + refinedGoods.add(1,new Pair<>(ConversionVariables.transplutonicsDailyFlag, "Produced transplutonics")); + refinedGoods.add(2,new Pair<>(ConversionVariables.fuelDailyFlag, "Produced fuel")); + refinedGoods.add(3,new Pair<>(ConversionVariables.suppliesDailyFlag, "Produced supplies")); + refinedGoods.add(4,new Pair<>(ConversionVariables.machineryDailyFlag, "Produced machinery")); + refinedGoods.add(5, new Pair<>(ConversionVariables.hullPartsDailyFlag, "Produced hull parts")); + + int refinedCount = 0; + String validMessage = ""; + for (Pair goodRefined : refinedGoods) { + if (goodRefined.one) { + ++refinedCount; + validMessage = goodRefined.two; + } + } + String resultMessage = "Produced goods"; + if (refinedCount <= 1) { + resultMessage = validMessage; + } + fleet.addFloatingText(resultMessage, Misc.setAlpha(Misc.getTextColor(), 255), 0.5f); + + refinedGoods.set(0, new Pair<>(ConversionVariables.metalsDailyFlag, "ui_cargo_metals_drop")); + refinedGoods.set(1, new Pair<>(ConversionVariables.transplutonicsDailyFlag, "ui_cargo_raremetals_drop")); + refinedGoods.set(2, new Pair<>(ConversionVariables.fuelDailyFlag, "ui_cargo_fuel_drop")); + refinedGoods.set(3, new Pair<>(ConversionVariables.suppliesDailyFlag, "ui_cargo_supplies_drop")); + refinedGoods.set(4, new Pair<>(ConversionVariables.machineryDailyFlag, "ui_cargo_machinery_drop")); + refinedGoods.set(5, new Pair<>(ConversionVariables.hullPartsDailyFlag, "ui_cargo_machinery_drop")); + + for (int index = 5; index >= 0; --index ) { + Pair producedGood = refinedGoods.get(index); + if (producedGood.one) { + Global.getSoundPlayer().playSound(producedGood.two, 1f, 1f, fleet.getLocation(), new Vector2f()); + break; + } + } + } + +} diff --git a/source/forgprod/abilities/interaction/ControlPanelPlugin.java b/source/forgprod/abilities/interaction/ControlPanelPlugin.java new file mode 100644 index 0000000..8d136f5 --- /dev/null +++ b/source/forgprod/abilities/interaction/ControlPanelPlugin.java @@ -0,0 +1,58 @@ +package forgprod.abilities.interaction; + +import org.lwjgl.input.Keyboard; + +import java.util.List; + +import com.fs.starfarer.api.campaign.CustomUIPanelPlugin; +import com.fs.starfarer.api.campaign.CustomVisualDialogDelegate.DialogCallbacks; +import com.fs.starfarer.api.campaign.InteractionDialogAPI; +import com.fs.starfarer.api.input.InputEventAPI; +import com.fs.starfarer.api.ui.CustomPanelAPI; +import com.fs.starfarer.api.ui.PositionAPI; + +import forgprod.abilities.interaction.panel.ControlPanelAssembly; +import forgprod.abilities.interaction.panel.ControlPanelManager; +import forgprod.abilities.interaction.panel.PanelConstants; + +/** + * @author Ontheheavens + * @since 25.12.2022 + */ + +public class ControlPanelPlugin implements CustomUIPanelPlugin { + + public void init(CustomPanelAPI panel, DialogCallbacks callbacks, InteractionDialogAPI dialog) { + new ControlPanelManager(panel, callbacks, dialog, this); + ControlPanelAssembly.setShownTab(PanelConstants.ShownTab.MODULES); + ControlPanelAssembly.renderPanel(panel, this); + } + + @Override + public void positionChanged(PositionAPI position) {} + + @Override + public void renderBelow(float alphaMult) { + } + + @Override + public void render(float alphaMult) {} + + @Override + public void advance(float amount) { + ControlPanelManager.getInstance().advance(); + } + + @Override + public void processInput(List events) { + for (InputEventAPI event : events) { + if (event.isConsumed()) continue; + if (event.isKeyDownEvent() && event.getEventValue() == Keyboard.KEY_ESCAPE) { + event.consume(); + ControlPanelManager.getInstance().dismissPanel(); + return; + } + } + } + +} diff --git a/source/forgprod/abilities/interaction/panel/ControlPanelAssembly.java b/source/forgprod/abilities/interaction/panel/ControlPanelAssembly.java new file mode 100644 index 0000000..8fede13 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/ControlPanelAssembly.java @@ -0,0 +1,83 @@ +package forgprod.abilities.interaction.panel; + +import java.util.ArrayList; +import java.util.List; + +import com.fs.starfarer.api.campaign.CustomUIPanelPlugin; +import com.fs.starfarer.api.ui.CustomPanelAPI; +import com.fs.starfarer.api.ui.UIComponentAPI; + +import forgprod.abilities.interaction.panel.components.Footer; +import forgprod.abilities.interaction.panel.components.Header; +import forgprod.abilities.interaction.panel.components.Sidebar; +import forgprod.abilities.interaction.panel.components.tabs.ModulesTab; +import forgprod.abilities.interaction.panel.listeners.ButtonListener; + +/** + * @author Ontheheavens + * @since 29.12.2022 + */ + +public class ControlPanelAssembly { + + private static List components; + + private static PanelConstants.ShownTab shownTab; + + private static CustomPanelAPI displayedTabInstance; + + private static final float CONTENT_WIDTH = PanelConstants.PANEL_WIDTH - PanelConstants.PANEL_CONTENT_OFFSET; + + private static void clearPanel(CustomPanelAPI panel) { + if (components != null) { + for (UIComponentAPI component : components) { + if (component == null) continue; + panel.removeComponent(component); + } + components = null; + } + clearTab(panel); + ModulesTab.setModuleCard(null); + ButtonListener.clearIndex(); + } + + public static void setShownTab(PanelConstants.ShownTab tab) { + ControlPanelAssembly.shownTab = tab; + } + + public static PanelConstants.ShownTab getShownTab() { + return shownTab; + } + + private static void clearTab(CustomPanelAPI panel) { + if (displayedTabInstance != null) { + panel.removeComponent(displayedTabInstance); + } + displayedTabInstance = null; + } + + public static void renderTab(CustomPanelAPI panel, CustomUIPanelPlugin plugin) { + clearTab(panel); + switch (ControlPanelAssembly.shownTab) { + case MODULES: + displayedTabInstance = ModulesTab.create(panel, CONTENT_WIDTH, plugin); + break; + case CAPACITIES: + break; + } + } + + public static void renderPanel(CustomPanelAPI panel, CustomUIPanelPlugin plugin) { + clearPanel(panel); + addComponents(panel, plugin); + } + + private static void addComponents(CustomPanelAPI panel, CustomUIPanelPlugin plugin) { + components = new ArrayList<>(); + components.add(Header.create(panel, CONTENT_WIDTH)); + ControlPanelAssembly.renderTab(panel, plugin); + components.add(Sidebar.create(panel, plugin)); + components.add(Footer.create(panel, CONTENT_WIDTH)); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/ControlPanelManager.java b/source/forgprod/abilities/interaction/panel/ControlPanelManager.java new file mode 100644 index 0000000..bafbd17 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/ControlPanelManager.java @@ -0,0 +1,98 @@ +package forgprod.abilities.interaction.panel; + +import com.fs.starfarer.api.campaign.CustomUIPanelPlugin; +import com.fs.starfarer.api.campaign.CustomVisualDialogDelegate.DialogCallbacks; +import com.fs.starfarer.api.campaign.InteractionDialogAPI; +import com.fs.starfarer.api.ui.CustomPanelAPI; + +import forgprod.abilities.interaction.panel.components.Sidebar; +import forgprod.abilities.interaction.panel.components.tabs.ModulesTab; +import forgprod.abilities.interaction.panel.components.tabs.modules.HullBlueprintSection; +import forgprod.abilities.interaction.panel.components.tabs.modules.ModuleList; +import forgprod.abilities.interaction.panel.listeners.ButtonListener; + +/** + * @author Ontheheavens + * @since 30.12.2022 + */ + +public class ControlPanelManager { + + private static ControlPanelManager instance; + private DialogCallbacks callbacks; + private InteractionDialogAPI dialog; + private CustomPanelAPI panel; + private CustomUIPanelPlugin plugin; + private boolean queuedPanelRedraw = false; + private boolean queuedTabRedraw = false; + + public ControlPanelManager(CustomPanelAPI panel, DialogCallbacks callbacks, + InteractionDialogAPI dialog, CustomUIPanelPlugin plugin) { + this.panel = panel; + this.callbacks = callbacks; + this.dialog = dialog; + this.plugin = plugin; + ControlPanelManager.instance = this; + } + + public static ControlPanelManager getInstance() { + return instance; + } + + public boolean isPanelRedrawQueued() { + return queuedPanelRedraw; + } + + public boolean isTabRedrawQueued() { + return queuedTabRedraw; + } + + public void setQueuedPanelRedraw(boolean queuedPanelRedraw) { + this.queuedPanelRedraw = queuedPanelRedraw; + } + + public void setQueuedTabRedraw(boolean queuedTabRedraw) { + this.queuedTabRedraw = queuedTabRedraw; + } + + public CustomPanelAPI getPanel() { + return panel; + } + + public CustomUIPanelPlugin getPlugin() { + return plugin; + } + + public void advance() { + ButtonListener.checkIndex(); + if (HullBlueprintSection.isCardRedrawQueued()) { + HullBlueprintSection.renderVariantCard(); + HullBlueprintSection.setCardRedrawQueued(false); + } + if (ModulesTab.isCardRedrawQueued()) { + ModulesTab.renderModuleCard(); + Sidebar.renderContent(); + ModulesTab.setCardRedrawQueued(false); + } + if (ModuleList.isListRedrawQueued()) { + ModuleList.renderModuleList(); + ModuleList.setListRedrawQueued(false); + } + if (this.isTabRedrawQueued()) { + ControlPanelAssembly.renderTab(panel, this.plugin); + this.setQueuedTabRedraw(false); + } + if (this.isPanelRedrawQueued()) { + ControlPanelAssembly.renderPanel(panel, this.plugin); + this.setQueuedPanelRedraw(false); + } + } + + public void dismissPanel() { + // Dismissing callbacks dialog means just closing the custom panel. + this.callbacks.dismissDialog(); + // Dismissing dialog itself means exit from initialization dialog too. + this.dialog.dismiss(); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/PanelConstants.java b/source/forgprod/abilities/interaction/panel/PanelConstants.java new file mode 100644 index 0000000..92ac58b --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/PanelConstants.java @@ -0,0 +1,36 @@ +package forgprod.abilities.interaction.panel; + +import java.awt.*; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Ontheheavens + * @since 29.12.2022 + */ + +public class PanelConstants { + + // On-the-spot color creation is to prevent edits to faction colors from affecting panel. + public static final Color PLAYER_COLOR = new Color(170, 222, 255, 255); + public static final Color DARK_PLAYER_COLOR = new Color(31, 94, 112, 175); + public static final Color BRIGHT_PLAYER_COLOR = new Color(203, 245, 255, 255); + + public static final float PANEL_WIDTH = 1200f; + public static final float PANEL_HEIGHT = 701f; + public static final float HEADER_HEIGHT = 120f; + public static final float PANEL_CONTENT_OFFSET = 8f; + public static final float SPEC_LINE_ICON_OFFSET = -198f; + + public enum ShownTab { + MODULES, + CAPACITIES, + } + + public static final Map TAB_NAMES = new HashMap<>(); + static { + TAB_NAMES.put(ShownTab.MODULES, "Modules"); + TAB_NAMES.put(ShownTab.CAPACITIES, "Capacities"); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/Common.java b/source/forgprod/abilities/interaction/panel/components/Common.java new file mode 100644 index 0000000..e3679e7 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/Common.java @@ -0,0 +1,81 @@ +package forgprod.abilities.interaction.panel.components; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.fleet.FleetMemberAPI; +import com.fs.starfarer.api.ui.ButtonAPI; +import com.fs.starfarer.api.ui.PositionAPI; +import com.fs.starfarer.api.ui.TooltipMakerAPI; +import com.fs.starfarer.api.ui.UIComponentAPI; +import com.fs.starfarer.api.util.Misc; +import com.fs.starfarer.api.util.Pair; + +import forgprod.abilities.conversion.support.checks.FleetwideProductionChecks; +import forgprod.abilities.interaction.panel.PanelConstants; +import forgprod.abilities.modules.dataholders.ProductionCapacity; +import forgprod.abilities.modules.dataholders.ProductionModule; + +/** + * @author Ontheheavens + * @since 01.01.2023 + */ + +public class Common { + + public static ButtonAPI addLine(TooltipMakerAPI tooltip, float width) { + ButtonAPI line = tooltip.addButton("", null, PanelConstants.PLAYER_COLOR, + PanelConstants.DARK_PLAYER_COLOR, width, 0f, 0f); + line.setEnabled(false); + line.highlight(); + return line; + } + + public static UIComponentAPI addShipIcon(TooltipMakerAPI tooltip, final ProductionModule module, + float size, float pad, boolean interactive) { + List memberAsList = new ArrayList<>(); + memberAsList.add(module.getParentFleetMember()); + if (interactive) { + tooltip.addShipList(1, 1, size, PanelConstants.PLAYER_COLOR, memberAsList, pad); + } else { + TooltipMakerAPI shipIconPanel = tooltip.beginImageWithText(null, size); + shipIconPanel.setForceProcessInput(false); + float frameSize = size + 8f; + shipIconPanel.addImage(null, frameSize, frameSize, 0f); + UIComponentAPI shipIconFrame = shipIconPanel.getPrev(); + shipIconFrame.getPosition().setXAlignOffset(-(222f)); + shipIconPanel.addShipList(1, 1, size, PanelConstants.PLAYER_COLOR, memberAsList, pad); + PositionAPI shipIconPosition = shipIconPanel.getPrev().getPosition(); + shipIconPosition.belowLeft(shipIconFrame, -54f); + shipIconPosition.setXAlignOffset(-6f); + tooltip.addImageWithText(0f); + } + return tooltip.getPrev(); + } + + public static Pair retrieveCapacityThroughputStatus(ProductionCapacity capacity, CampaignFleetAPI fleet) { + float availability = FleetwideProductionChecks.getMachineryAvailability(fleet); + String status = ""; + Color statusColor = Misc.getTextColor(); + if (capacity.isActive() && availability < 1 && FleetwideProductionChecks.hasMinimumMachinery(fleet)) { + status = "(" + ((int) (availability * 100f)) + "% machinery)"; + statusColor = Misc.getNegativeHighlightColor(); + } else if (capacity.isActive() && (capacity.getCurrentThroughput() > 0f)) { + status = "(" + (capacity.getParentActiveCapacitiesAmount()) + " active)"; + statusColor = Misc.getHighlightColor(); + } else if (capacity.isToggledOn() && !capacity.parentShipOperational()) { + status = "(ship inactive)"; + statusColor = Misc.getNegativeHighlightColor(); + } else if ((capacity.getCurrentThroughput() <= 0f) && !FleetwideProductionChecks.hasMinimumMachinery(fleet)) { + status = "(low machinery)"; + statusColor = Misc.getNegativeHighlightColor(); + } else if (!capacity.isToggledOn()) { + status = "(toggled off)"; + statusColor = Misc.getGrayColor(); + } + return new Pair<>(status, statusColor); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/Footer.java b/source/forgprod/abilities/interaction/panel/components/Footer.java new file mode 100644 index 0000000..9f7bf73 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/Footer.java @@ -0,0 +1,44 @@ +package forgprod.abilities.interaction.panel.components; + +import com.fs.starfarer.api.ui.*; + +import forgprod.abilities.interaction.panel.ControlPanelManager; +import forgprod.abilities.interaction.panel.PanelConstants; +import forgprod.abilities.interaction.panel.objects.Button; + +import static forgprod.abilities.interaction.panel.PanelConstants.PANEL_CONTENT_OFFSET; + +/** + * @author Ontheheavens + * @since 29.12.2022 + */ + +public class Footer { + + public static TooltipMakerAPI create(CustomPanelAPI panel, float width) { + float offset = PANEL_CONTENT_OFFSET * 2; + TooltipMakerAPI footer = panel.createUIElement(width, 10f, false); + footer.setForceProcessInput(true); + ButtonAPI footerLine = Common.addLine(footer, (width - offset + 4f) - 259f); + footerLine.getPosition().setYAlignOffset(17f); + footerLine.getPosition().setXAlignOffset(4f); + Footer.createExitButton(footer); + panel.addUIElement(footer).inBR(2f, 2f); + return footer; + } + + private static void createExitButton(TooltipMakerAPI footer) { + footer.setButtonFontVictor14(); + ButtonAPI exitButtonInstance = footer.addButton("DISMISS", null, PanelConstants.PLAYER_COLOR, + PanelConstants.DARK_PLAYER_COLOR, Alignment.MID, CutStyle.TL_BR, 250f, 25f, 2f); + float offset = PANEL_CONTENT_OFFSET; + exitButtonInstance.getPosition().inBR(offset, offset); + new Button(exitButtonInstance, Button.Type.STANDARD) { + @Override + public void applyEffect() { + ControlPanelManager.getInstance().dismissPanel(); + } + }; + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/Header.java b/source/forgprod/abilities/interaction/panel/components/Header.java new file mode 100644 index 0000000..800c275 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/Header.java @@ -0,0 +1,243 @@ +package forgprod.abilities.interaction.panel.components; + +import java.awt.*; +import java.util.List; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.characters.AbilityPlugin; +import com.fs.starfarer.api.impl.campaign.ids.Strings; +import com.fs.starfarer.api.ui.*; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.ForgeProductionAbility; +import forgprod.abilities.conversion.support.checks.FleetwideProductionChecks; +import forgprod.abilities.interaction.panel.ControlPanelAssembly; +import forgprod.abilities.interaction.panel.ControlPanelManager; +import forgprod.abilities.interaction.panel.PanelConstants; +import forgprod.abilities.interaction.panel.objects.Button; +import forgprod.abilities.interaction.panel.objects.ExclusiveButton; +import forgprod.hullmods.tooltip.TooltipModuleSection; +import forgprod.settings.SettingsHolder; + +import static forgprod.abilities.interaction.panel.PanelConstants.PANEL_CONTENT_OFFSET; + +/** + * @author Ontheheavens + * @since 29.12.2022 + */ + +public class Header { + + public static TooltipMakerAPI create(CustomPanelAPI panel, float width) { + TooltipMakerAPI header = panel.createUIElement(width - PANEL_CONTENT_OFFSET - 4f, + PanelConstants.HEADER_HEIGHT, false); + header.addSectionHeading("Forge production", Alignment.MID, 2f); + UIComponentAPI headerAnchor = header.addSpacer(0f); + header.setForceProcessInput(true); + UIComponentAPI headerInfoContainer = Header.createHeaderInfo(panel, width); + header.addCustom(headerInfoContainer, 0f).getPosition().inTL(0f, 0f); + ButtonAPI tabButtonsLine = Common.addLine(header, width - PANEL_CONTENT_OFFSET - 263f); + tabButtonsLine.getPosition().belowLeft(headerAnchor, PanelConstants.HEADER_HEIGHT + 1f); + tabButtonsLine.getPosition().setXAlignOffset(-5f); + addTabButtons(header); + panel.addUIElement(header).inTMid(PANEL_CONTENT_OFFSET + 2f); + return header; + } + + // Final section needs to have width of 330f. + private static UIComponentAPI createHeaderInfo(CustomPanelAPI panel, float width) { + CustomPanelAPI headerInfoContainer = panel.createCustomPanel(width, + PanelConstants.HEADER_HEIGHT - 20f, ControlPanelManager.getInstance().getPlugin()); + TooltipMakerAPI iconContainer = addReportIcon(headerInfoContainer); + headerInfoContainer.addUIElement(iconContainer).inTL(5f, 35f); + TooltipMakerAPI framing = addFraming(headerInfoContainer); + headerInfoContainer.addUIElement(framing).inTL(5f, 27f); + TooltipMakerAPI coordinationSection = addCoordinationSection(headerInfoContainer); + headerInfoContainer.addUIElement(coordinationSection).inTL(79f, 34f); + TooltipMakerAPI safetySection = addSafetySection(headerInfoContainer); + headerInfoContainer.addUIElement(safetySection).rightOfTop(coordinationSection, 6f); + TooltipMakerAPI logisticsSection = addLogisticsSection(headerInfoContainer); + headerInfoContainer.addUIElement(logisticsSection).rightOfTop(safetySection, 6f); + return headerInfoContainer; + } + + private static TooltipMakerAPI addFraming(CustomPanelAPI headerInfoContainer) { + float width = 916f; + float height = 84f; + TooltipMakerAPI framingContainer = headerInfoContainer.createUIElement(width, height, false); + ButtonAPI topLine = Common.addLine(framingContainer, width); + topLine.getPosition().inTL(0f, 0f); + ButtonAPI bottomLine = Common.addLine(framingContainer, width); + bottomLine.getPosition().inTL(0f, height); + return framingContainer; + } + + private static TooltipMakerAPI addReportIcon(CustomPanelAPI headerInfoContainer) { + TooltipMakerAPI iconContainer = headerInfoContainer.createUIElement(80f, 80f, false); + String frame = "frame"; + if (Global.getSector().getPlayerFleet().getAbility("forge_production").isActiveOrInProgress()) { + frame = "frame_bright"; + } + String frameIconName = Global.getSettings().getSpriteName("forgprod_ui", frame); + float frameWidth = 68f; + iconContainer.addImage(frameIconName, frameWidth, 0f); + UIComponentAPI frameInstance = iconContainer.getPrev(); + frameInstance.getPosition().inTL(0f, 0f); + String reportIconName = Global.getSettings().getSpriteName("forgprod_intel", + "forge_production_report_big"); + iconContainer.addImage(reportIconName, 0f); + UIComponentAPI iconInstance = iconContainer.getPrev(); + iconInstance.getPosition().rightOfMid(frameInstance, -66f); + ButtonAPI frameTopLine = Common.addLine(iconContainer, frameWidth); + frameTopLine.getPosition().aboveLeft(frameInstance, 1f); + ButtonAPI frameBottomLine = Common.addLine(iconContainer, frameWidth); + frameBottomLine.getPosition().belowLeft(frameInstance, 1f); + return iconContainer; + } + + private static TooltipMakerAPI addCoordinationSection(CustomPanelAPI headerInfoContainer) { + TooltipMakerAPI sectionContainer = headerInfoContainer.createUIElement(250f, 85f, false); + String abilityStatusValue = "Standby"; + Color statusColor = Misc.getHighlightColor(); + int totalVolume = FleetwideProductionChecks.getTotalModuleVolume(); + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + AbilityPlugin forgProdAbilityPlugin = fleet.getAbility("forge_production"); + ForgeProductionAbility castedAbilityPlugin = (ForgeProductionAbility) forgProdAbilityPlugin; + if (castedAbilityPlugin.isActiveOrInProgress()) { + abilityStatusValue = "Ongoing"; + } else if (castedAbilityPlugin.isOnCooldown()) { + abilityStatusValue = "Terminating"; + statusColor = Misc.getNegativeHighlightColor(); + } else if (totalVolume < 1 || !FleetwideProductionChecks.hasMinimumMachinery(fleet) || + !castedAbilityPlugin.checkUsableAbilities()) { + abilityStatusValue = "Disabled"; + statusColor = Misc.getGrayColor(); + } + sectionContainer.addSectionHeading("Overview", Alignment.MID, 0f); + sectionContainer.addSpacer(5f); + sectionContainer.getPrev().getPosition().setXAlignOffset(-2f); + addSpecLineToSection(sectionContainer, "Production status:", abilityStatusValue, + statusColor, false); + sectionContainer.addSpacer(2f); + int modulesCount = FleetwideProductionChecks.getTotalModuleCount(); + Color modulesColor = Misc.getHighlightColor(); + if (modulesCount < 1) { + modulesColor = Misc.getNegativeHighlightColor(); + } + addSpecLineToSection(sectionContainer, "Total modules installed:", String.valueOf(modulesCount), + modulesColor, false); + sectionContainer.addSpacer(2f); + String moduleVolume = totalVolume + Strings.X; + Color volumeColor = Misc.getHighlightColor(); + if (totalVolume < 1) { + volumeColor = Misc.getNegativeHighlightColor(); + } + addSpecLineToSection(sectionContainer, "Total module volume:", moduleVolume, volumeColor, false); + return sectionContainer; + } + + private static TooltipMakerAPI addSafetySection(CustomPanelAPI headerInfoContainer) { + TooltipMakerAPI sectionContainer = headerInfoContainer.createUIElement(250f, 85f, false); + sectionContainer.addSectionHeading("Safety", Alignment.MID, 0f); + sectionContainer.addSpacer(5f); + sectionContainer.getPrev().getPosition().setXAlignOffset(-2f); + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + List statistics = TooltipModuleSection.getFleetBreakdownStats(fleet); + float breakdownChance = statistics.get(0); + float breakdownSizeMinimal = statistics.get(1); + float breakdownSizeMaximal = statistics.get(2); + float accidentChance = statistics.get(3); + Color highlight = Misc.getHighlightColor(); + addSpecLineToSection(sectionContainer, "Breakdown chance:", + (Misc.getRoundedValueMaxOneAfterDecimal(breakdownChance * 100f)) + "%", + highlight, false); + sectionContainer.addSpacer(2f); + String breakdownSizeValue = (Misc.getRoundedValueMaxOneAfterDecimal(breakdownSizeMinimal * 100f) + "%") + + " - " + (Misc.getRoundedValueMaxOneAfterDecimal(breakdownSizeMaximal * 100f) + "%"); + addSpecLineToSection(sectionContainer, "Breakdown size:", breakdownSizeValue, highlight, false); + sectionContainer.addSpacer(2f); + addSpecLineToSection(sectionContainer, "Casualty chance:", + Misc.getRoundedValueMaxOneAfterDecimal((breakdownChance * accidentChance) * 100f) + "%", + highlight, false); + return sectionContainer; + } + + private static TooltipMakerAPI addLogisticsSection(CustomPanelAPI headerInfoContainer) { + float width = 330f; + TooltipMakerAPI sectionContainer = headerInfoContainer.createUIElement(width, 85f, false); + sectionContainer.addSectionHeading("Upkeep", Alignment.MID, 0f); + sectionContainer.addSpacer(5f); + sectionContainer.getPrev().getPosition().setXAlignOffset(-2f); + Color highlight = Misc.getHighlightColor(); + int totalVolume = FleetwideProductionChecks.getTotalModuleVolume(); + int crewmanSalary = Global.getSettings().getInt("crewSalary"); + int salaryIncrease = totalVolume * SettingsHolder.CREW_REQ_PER_CAPACITY * crewmanSalary; + int supplyIncrease = totalVolume * SettingsHolder.SUPPLY_REQ_PER_CAPACITY; + addSpecLineToSection(sectionContainer, "Active production CR decrease:", + Misc.getRoundedValueMaxOneAfterDecimal((SettingsHolder.DAILY_CR_DECREASE) * 100f) + "%", + highlight, true); + sectionContainer.addSpacer(2f); + addSpecLineToSection(sectionContainer, "Total additional crew salary:", + (Misc.getRoundedValueMaxOneAfterDecimal(salaryIncrease)) + Strings.C, + highlight, true); + sectionContainer.addSpacer(2f); + addSpecLineToSection(sectionContainer, "Total monthly maintenance:", String.valueOf(supplyIncrease), + highlight, true); + return sectionContainer; + } + + private static void addSpecLineToSection(TooltipMakerAPI sectionContainer, String desc, + String value, Color valueColor, boolean lastSection) { + TooltipMakerAPI lineContainer = sectionContainer.beginImageWithText(null, 2f); + lineContainer.addSpacer(0f); + float widthOffset = 0f; + if (lastSection) { + widthOffset = 80f; + } + lineContainer.getPrev().getPosition().setXAlignOffset(-(242f + widthOffset)); + lineContainer.setTextWidthOverride(250f + widthOffset); + LabelAPI descriptionLabel = lineContainer.addPara(desc, 0f); + LabelAPI descriptionValue; + if (desc.equals("Total monthly maintenance:")) { + descriptionValue = lineContainer.addPara("%s supplies", 0f, + Misc.getHighlightColor(), value); + } else { + descriptionValue = lineContainer.addPara(value, valueColor, 0f); + } + descriptionValue.getPosition().rightOfMid((UIComponentAPI) descriptionLabel, -(252f + widthOffset)); + descriptionValue.getPosition().setYAlignOffset(1f); + descriptionValue.setAlignment(Alignment.RMID); + lineContainer.addSpacer(-15f); + sectionContainer.addImageWithText(0f); + } + + // Offsets by X-axis for consequent buttons are width of button (250f) plus spacer (3f). + private static void addTabButtons(TooltipMakerAPI header) { + ButtonAPI modulesTabButton = createTabButton(header, PanelConstants.ShownTab.MODULES); + float height = PanelConstants.HEADER_HEIGHT + 1f; + modulesTabButton.getPosition().inTL(0f, height); + } + + @SuppressWarnings("SameParameterValue") + private static ButtonAPI createTabButton(TooltipMakerAPI header, final PanelConstants.ShownTab tab) { + ButtonAPI tabButtonInstance = header.addButton(PanelConstants.TAB_NAMES.get(tab), null, + PanelConstants.PLAYER_COLOR, PanelConstants.DARK_PLAYER_COLOR, Alignment.MID, + CutStyle.TOP, 250f, 18f, 2f); + if (ControlPanelAssembly.getShownTab() == tab) { + tabButtonInstance.highlight(); + } + new ExclusiveButton(tabButtonInstance, Button.Type.TAB) { + @Override + public void applyEffect() { + if (ControlPanelAssembly.getShownTab() != tab) { + ControlPanelAssembly.setShownTab(tab); + ControlPanelManager.getInstance().setQueuedTabRedraw(true); + this.getInner().highlight(); + } + } + }; + return tabButtonInstance; + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/Sidebar.java b/source/forgprod/abilities/interaction/panel/components/Sidebar.java new file mode 100644 index 0000000..dea0ef1 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/Sidebar.java @@ -0,0 +1,374 @@ +package forgprod.abilities.interaction.panel.components; + +import java.awt.*; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.campaign.CustomUIPanelPlugin; +import com.fs.starfarer.api.campaign.SpecialItemSpecAPI; +import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI; +import com.fs.starfarer.api.impl.campaign.ids.Commodities; +import com.fs.starfarer.api.impl.campaign.ids.Items; +import com.fs.starfarer.api.ui.*; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.conversion.support.checks.FleetwideProductionChecks; +import forgprod.abilities.conversion.support.checks.ItemBonusesChecks; +import forgprod.abilities.interaction.panel.ControlPanelManager; +import forgprod.abilities.interaction.panel.PanelConstants; + +import static forgprod.abilities.conversion.support.checks.FleetwideProductionChecks.getFleetCapacityForType; + +/** + * @author Ontheheavens + * @since 02.01.2023 + */ + +public class Sidebar { + + private static CustomPanelAPI framePanel; + private static CustomPanelAPI sidebarContent; + + private static float width = 250f; + + public static CustomPanelAPI create(CustomPanelAPI panel, CustomUIPanelPlugin plugin) { + CustomPanelAPI sidebar = panel.createCustomPanel(width, 400f, plugin); + framePanel = sidebar; + Sidebar.createIllustration(sidebar, width); + Sidebar.createFrame(sidebar, width); + Sidebar.renderContent(); + panel.addComponent(sidebar).inTL(938f, 129f); + return sidebar; + } + + private static void createIllustration(CustomPanelAPI sidebar, float width) { + TooltipMakerAPI illustrationContainer = sidebar.createUIElement(width, 1f, false); + illustrationContainer.addImage(Global.getSettings().getSpriteName("forgprod_ui", + "forge_production_illustration"), 2f); + illustrationContainer.getPrev().getPosition().setXAlignOffset(-45f); + illustrationContainer.getPrev().getPosition().setYAlignOffset(-45f); + sidebar.addUIElement(illustrationContainer).inTL(47f, -143f); + } + + private static void createFrame(CustomPanelAPI sidebar, float width) { + TooltipMakerAPI sidebarHeader = sidebar.createUIElement(width, 20f, false); + sidebarHeader.addSectionHeading("Equipment", Alignment.MID, 2f); + UIComponentAPI header = sidebarHeader.getPrev(); + ButtonAPI footerLine = Common.addLine(sidebarHeader, width); + footerLine.getPosition().belowLeft(header, 0f); + footerLine.getPosition().setXAlignOffset(-5f); + sidebar.addUIElement(sidebarHeader).inTL(2f, 2f); + TooltipMakerAPI sidebarFrame = sidebar.createUIElement(width, 380f, false); + sidebarFrame.setForceProcessInput(false); + Sidebar.addFrameBox(sidebarFrame, width); + sidebar.addUIElement(sidebarFrame).belowLeft(sidebarHeader, 0f); + } + + private static void addFrameBox(TooltipMakerAPI sidebarFrame, float width) { + TooltipMakerAPI sidebarBoxContainer = sidebarFrame.beginImageWithText(null, 2f); + Color baseBoxColor = Misc.interpolateColor(PanelConstants.DARK_PLAYER_COLOR, + PanelConstants.BRIGHT_PLAYER_COLOR, 0.5f); + Color boxColor = Misc.scaleColorOnly(baseBoxColor, 0.9f); + sidebarBoxContainer.addAreaCheckbox("", null, PanelConstants.PLAYER_COLOR, + boxColor, PanelConstants.PLAYER_COLOR, + width, 503, 0f); + sidebarBoxContainer.getPrev().getPosition().setXAlignOffset(-245f); + sidebarFrame.addImageWithText(2f); + } + + public static void renderContent() { + if (framePanel == null) return; + if (sidebarContent != null) { + framePanel.removeComponent(sidebarContent); + } + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + CustomPanelAPI sidebarContentPanel = framePanel.createCustomPanel(width - 4, 500f, + ControlPanelManager.getInstance().getPlugin()); + TooltipMakerAPI machineryPanel = sidebarContentPanel.createUIElement(width - 4, 100f, false); + machineryPanel.addSpacer(20f); + Sidebar.addMachinerySection(machineryPanel); + sidebarContentPanel.addUIElement(machineryPanel).inTL(4f, 105f); + SpecialItemSpecAPI nanoforgeSpec = ItemBonusesChecks.getBestNanoforgeInCargoSpec(fleet); + float panelOffset = -37f; + if (nanoforgeSpec != null) { + TooltipMakerAPI nanoforgePanel = sidebarContentPanel.createUIElement(width - 4, 100f, false); + Sidebar.addNanoforgeSection(nanoforgePanel, nanoforgeSpec); + sidebarContentPanel.addUIElement(nanoforgePanel).belowLeft(machineryPanel, panelOffset); + panelOffset += 117f; + } + if (ItemBonusesChecks.hasSpecialItem(fleet, Items.CATALYTIC_CORE)) { + TooltipMakerAPI catalyticCorePanel = sidebarContentPanel.createUIElement(width - 4, 100f, false); + Sidebar.addCatalyticCoreSection(catalyticCorePanel, Global.getSettings().getSpecialItemSpec(Items.CATALYTIC_CORE)); + sidebarContentPanel.addUIElement(catalyticCorePanel).belowLeft(machineryPanel, panelOffset); + panelOffset += 94f; + } + if (ItemBonusesChecks.hasSpecialItem(fleet, Items.SYNCHROTRON)) { + TooltipMakerAPI synchrotronPanel = sidebarContentPanel.createUIElement(width - 4, 100f, false); + Sidebar.addSynchrotronSection(synchrotronPanel, Global.getSettings().getSpecialItemSpec(Items.SYNCHROTRON)); + sidebarContentPanel.addUIElement(synchrotronPanel).belowLeft(machineryPanel, panelOffset); + } + framePanel.addComponent(sidebarContentPanel); + Sidebar.sidebarContent = sidebarContentPanel; + } + + private static void addMachinerySection(TooltipMakerAPI machineryPanel) { + String iconName = Global.getSettings().getSpriteName("forgprod_ui", "production_small"); + UIComponentAPI machineryIcon = Sidebar.createEquipmentIcon(machineryPanel, iconName); + String title = "Heavy Machinery"; + LabelAPI machineryTitle = machineryPanel.addTitle(title); + machineryTitle.getPosition().rightOfMid(machineryIcon, -197f); + machineryTitle.getPosition().setYAlignOffset(8f); + ButtonAPI titleLine = Common.addLine(machineryPanel, 230f); + titleLine.getPosition().leftOfBottom((UIComponentAPI) machineryTitle, -194f); + titleLine.getPosition().setYAlignOffset(-14f); + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + float machineryPresent = fleet.getCargo().getCommodityQuantity(Commodities.HEAVY_MACHINERY); + float machineryRequirement = FleetwideProductionChecks.getFleetMachineryRequirement(); + String requirementValue = Misc.getWithDGS(machineryRequirement); + String availableValue = Misc.getWithDGS(machineryPresent); + machineryPanel.addSpacer(6f); + Sidebar.addMachineryLine(machineryPanel, "Required", requirementValue + "×"); + Sidebar.addMachineryLine(machineryPanel, "Available", availableValue + "×"); + Sidebar.addRatioLine(machineryPanel, fleet); + } + + private static UIComponentAPI createEquipmentIcon(TooltipMakerAPI sidebarPanel, String iconName) { + TooltipMakerAPI equipmentIconContainer = sidebarPanel.beginImageWithText(null, 32f); + equipmentIconContainer.addImage(Global.getSettings().getSpriteName("forgprod_ui", "frame"), + 28f, 28f, 10f); + UIComponentAPI capacityIconFrame = equipmentIconContainer.getPrev(); + capacityIconFrame.getPosition().setXAlignOffset(-233f); + equipmentIconContainer.addImage(iconName, 24f, 24f, 0f); + PositionAPI iconPosition = equipmentIconContainer.getPrev().getPosition(); + float iconOffset = (iconPosition.getY() - capacityIconFrame.getPosition().getY()) - 2f; + iconPosition.belowMid(capacityIconFrame, iconOffset); + sidebarPanel.addImageWithText(2f); + return sidebarPanel.getPrev(); + } + + private static void addMachineryLine(TooltipMakerAPI tooltip, String description, String value) { + TooltipMakerAPI specificationLine = tooltip.beginImageWithText(null, 32f); + specificationLine.setTextWidthOverride(250f); + specificationLine.addPara(description + ":", Misc.getTextColor(), 0f); + UIComponentAPI desc = specificationLine.getPrev(); + desc.getPosition().setXAlignOffset(-234f); + CommoditySpecAPI machinerySpec = Global.getSettings().getCommoditySpec(Commodities.HEAVY_MACHINERY); + specificationLine.addImage(machinerySpec.getIconName(), 24f, 24f, 0f); + UIComponentAPI icon = specificationLine.getPrev(); + icon.getPosition().rightOfMid(desc, -185f); + LabelAPI valueLabel = specificationLine.addPara(value, Misc.getHighlightColor(), 0f); + valueLabel.getPosition().rightOfTop(desc, -358f); + valueLabel.getPosition().setYAlignOffset(1f); + valueLabel.setAlignment(Alignment.RMID); + specificationLine.addPara("Machinery", Misc.getTextColor(), 1f).getPosition().rightOfMid(desc, -106f); + tooltip.addImageWithText(6f); + tooltip.addSpacer(-53f); + } + + private static void addRatioLine(TooltipMakerAPI sidebarPanel, CampaignFleetAPI fleet) { + TooltipMakerAPI specificationLine = sidebarPanel.beginImageWithText(null, 32f); + specificationLine.setTextWidthOverride(250f); + specificationLine.addPara("Ratio:", Misc.getTextColor(), 0f); + UIComponentAPI desc = specificationLine.getPrev(); + desc.getPosition().setXAlignOffset(-234f); + String ratioDesc = "(full output)"; + Color ratioColor = Misc.getHighlightColor(); + String ratioIcon = Global.getSettings().getSpriteName("forgprod_ui", "production_satisfied"); + float availability = FleetwideProductionChecks.getMachineryAvailability(fleet); + String availabilityValue = Misc.getRoundedValueMaxOneAfterDecimal((int) (availability * 100f)); + if (availability < 1f) { + ratioColor = Misc.getNegativeHighlightColor(); + ratioIcon = Global.getSettings().getSpriteName("forgprod_ui", "production_shortage"); + ratioDesc = "(low output)"; + if (!FleetwideProductionChecks.hasMinimumMachinery(fleet)) { + ratioDesc = "(no output)"; + } + } + if (fleet.getCargo().getCommodityQuantity(Commodities.HEAVY_MACHINERY) < 1f) { + ratioDesc = ""; + } + specificationLine.addImage(ratioIcon, 24f, 24f, 0f); + UIComponentAPI icon = specificationLine.getPrev(); + icon.getPosition().rightOfMid(desc, -185f); + LabelAPI valueLabel = specificationLine.addPara(availabilityValue + "%", ratioColor, 0f); + valueLabel.getPosition().rightOfTop(desc, -361f); + valueLabel.getPosition().setYAlignOffset(1f); + valueLabel.setAlignment(Alignment.RMID); + specificationLine.addPara(ratioDesc, ratioColor, 1f).getPosition().rightOfMid(desc, -106f); + sidebarPanel.addImageWithText(6f); + sidebarPanel.addSpacer(-53f); + } + + private static void addNanoforgeSection(TooltipMakerAPI nanoforgePanel, SpecialItemSpecAPI nanoforge) { + String iconName = nanoforge.getIconName(); + UIComponentAPI nanoforgeIcon = Sidebar.createEquipmentIcon(nanoforgePanel, iconName); + String title = nanoforge.getName(); + LabelAPI nanoforgeTitle = nanoforgePanel.addTitle(title); + nanoforgeTitle.getPosition().rightOfMid(nanoforgeIcon, -197f); + nanoforgeTitle.getPosition().setYAlignOffset(8f); + ButtonAPI titleLine = Common.addLine(nanoforgePanel, 230f); + titleLine.getPosition().leftOfBottom((UIComponentAPI) nanoforgeTitle, -194f); + titleLine.getPosition().setYAlignOffset(-14f); + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + int suppliesCapacity = getFleetCapacityForType(fleet, ProductionType.SUPPLIES_PRODUCTION); + int machineryCapacity = getFleetCapacityForType(fleet, ProductionType.MACHINERY_PRODUCTION); + float breakdownsBonus = ItemBonusesChecks.getNanoforgeBreakdownDecrease(fleet); + float bonusOutput = ItemBonusesChecks.getHeavyIndustryBonus(fleet); + String suppliesBonus = Misc.getRoundedValueMaxOneAfterDecimal(bonusOutput * suppliesCapacity); + String machineryBonus = Misc.getRoundedValueMaxOneAfterDecimal(bonusOutput * machineryCapacity); + CommoditySpecAPI suppliesSpec = Global.getSettings().getCommoditySpec(Commodities.SUPPLIES); + CommoditySpecAPI machinerySpec = Global.getSettings().getCommoditySpec(Commodities.HEAVY_MACHINERY); + nanoforgePanel.addSpacer(6f); + Sidebar.addBreakdownDecreaseLine(nanoforgePanel, breakdownsBonus); + Sidebar.addBonusOutputLine(nanoforgePanel, suppliesSpec, suppliesBonus); + Sidebar.addBonusOutputLine(nanoforgePanel, machinerySpec, machineryBonus); + } + + private static void addCatalyticCoreSection(TooltipMakerAPI catalyticCorePanel, SpecialItemSpecAPI catalyticCoreSpec) { + String iconName = catalyticCoreSpec.getIconName(); + UIComponentAPI catalyticCoreIcon = Sidebar.createEquipmentIcon(catalyticCorePanel, iconName); + String title = catalyticCoreSpec.getName(); + LabelAPI catalyticCoreTitle = catalyticCorePanel.addTitle(title); + catalyticCoreTitle.getPosition().rightOfMid(catalyticCoreIcon, -197f); + catalyticCoreTitle.getPosition().setYAlignOffset(8f); + ButtonAPI titleLine = Common.addLine(catalyticCorePanel, 230f); + titleLine.getPosition().leftOfBottom((UIComponentAPI) catalyticCoreTitle, -194f); + titleLine.getPosition().setYAlignOffset(-14f); + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + int metalsCapacity = getFleetCapacityForType(fleet, ProductionType.METALS_PRODUCTION); + int transplutonicsCapacity = getFleetCapacityForType(fleet, ProductionType.TRANSPLUTONICS_PRODUCTION); + float bonusOutput = ItemBonusesChecks.getRefiningBonus(fleet); + String metalsBonus = Misc.getRoundedValueMaxOneAfterDecimal(bonusOutput * metalsCapacity); + String transplutonicsBonus = Misc.getRoundedValueMaxOneAfterDecimal(bonusOutput * transplutonicsCapacity); + CommoditySpecAPI metalsSpec = Global.getSettings().getCommoditySpec(Commodities.METALS); + CommoditySpecAPI transplutonicsSpec = Global.getSettings().getCommoditySpec(Commodities.RARE_METALS); + catalyticCorePanel.addSpacer(6f); + Sidebar.addBonusOutputLine(catalyticCorePanel, metalsSpec, metalsBonus); + Sidebar.addBonusOutputLine(catalyticCorePanel, transplutonicsSpec, transplutonicsBonus); + } + + private static void addSynchrotronSection(TooltipMakerAPI synchrotronPanel, SpecialItemSpecAPI synchrotronSpec) { + String iconName = synchrotronSpec.getIconName(); + UIComponentAPI synchrotronIcon = Sidebar.createEquipmentIcon(synchrotronPanel, iconName); + String title = synchrotronSpec.getName(); + LabelAPI synchrotronTitle = synchrotronPanel.addTitle(title); + synchrotronTitle.getPosition().rightOfMid(synchrotronIcon, -197f); + synchrotronTitle.getPosition().setYAlignOffset(8f); + ButtonAPI titleLine = Common.addLine(synchrotronPanel, 230f); + titleLine.getPosition().leftOfBottom((UIComponentAPI) synchrotronTitle, -194f); + titleLine.getPosition().setYAlignOffset(-14f); + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + int fuelCapacity = getFleetCapacityForType(fleet, ProductionType.FUEL_PRODUCTION); + float bonusOutput = ItemBonusesChecks.getFuelProductionBonus(fleet); + String fuelBonus = Misc.getRoundedValueMaxOneAfterDecimal(bonusOutput * fuelCapacity); + CommoditySpecAPI fuelSpec = Global.getSettings().getCommoditySpec(Commodities.FUEL); + synchrotronPanel.addSpacer(6f); + Sidebar.addBonusOutputLine(synchrotronPanel, fuelSpec, fuelBonus); + } + + private static void addBonusOutputLine(TooltipMakerAPI itemPanel, final CommoditySpecAPI outputSpec, final String value) { + TooltipMakerAPI specificationLine = itemPanel.beginImageWithText(null, 2f); + specificationLine.setTextWidthOverride(250f); + final LabelAPI description = specificationLine.addPara("Increases output:", Misc.getTextColor(), 0f); + description.setHighlightColor(Misc.getPositiveHighlightColor()); + description.setHighlight(10, description.getText().length() - 2); + description.getPosition().setXAlignOffset(-234f); + specificationLine.addImage(outputSpec.getIconName(), 24f, 24f, 0f); + UIComponentAPI icon = specificationLine.getPrev(); + icon.getPosition().rightOfMid((UIComponentAPI) description, -135f); + String outputName = outputSpec.getName(); + if (outputName.equals("Heavy Machinery")) { + outputName = "Machinery"; + } + String outputNameShort = specificationLine.shortenString(outputName, 85f); + specificationLine.addPara(outputNameShort, Misc.getTextColor(), + 1f).getPosition().rightOfMid((UIComponentAPI) description, -106f); + itemPanel.addImageWithText(6f); + String anchorImageName = Global.getSettings().getSpriteName("forgprod_ui", "effect_tooltip_anchor"); + itemPanel.addImage(anchorImageName, 0f); + final String finalOutputName = outputName; + final float outputNameWidth = itemPanel.computeStringWidth(value + finalOutputName); + TooltipMakerAPI.TooltipCreator outputLineTooltip = new BaseTooltipCreator() { + @Override + public float getTooltipWidth(Object tooltipParam) { return 185 + outputNameWidth; } + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { + tooltip.addPara("Total output increase:", Misc.getTextColor(), + 0f).getPosition().inLMid(4f); + tooltip.addImage(outputSpec.getIconName(), 24f, 24f, 0f); + UIComponentAPI icon = tooltip.getPrev(); + icon.getPosition().inLMid(142f); + String valueLabel = value + "× "; + tooltip.addPara(valueLabel + finalOutputName, 0f, Misc.getTextColor(), + Misc.getPositiveHighlightColor(), valueLabel).getPosition().inRMid(-165f); + tooltip.addSpacer(-40f); + } + }; + itemPanel.addTooltipToPrevious(outputLineTooltip, TooltipMakerAPI.TooltipLocation.LEFT); + UIComponentAPI anchorImage = itemPanel.getPrev(); + anchorImage.getPosition().setYAlignOffset(53f); + float offset = 63f; + anchorImage.getPosition().setXAlignOffset(offset); + itemPanel.addSpacer(0f); + itemPanel.getPrev().getPosition().setXAlignOffset(-offset); + itemPanel.addSpacer(2f); + } + + private static void addBreakdownDecreaseLine(TooltipMakerAPI itemPanel, final float value) { + TooltipMakerAPI specificationLine = itemPanel.beginImageWithText(null, 32f); + specificationLine.setTextWidthOverride(250f); + LabelAPI description = specificationLine.addPara("Increases quality:", Misc.getTextColor(), 0f); + description.setHighlightColor(Misc.getPositiveHighlightColor()); + description.setHighlight(10, description.getText().length() - 2); + description.getPosition().setXAlignOffset(-234f); + final String breakdownsIconName = Global.getSettings().getSpriteName("forgprod_ui", + "production_breakdowns"); + specificationLine.addImage(breakdownsIconName, 24f, 24f, 0f); + UIComponentAPI icon = specificationLine.getPrev(); + icon.getPosition().rightOfMid((UIComponentAPI) description, -135f); + specificationLine.addPara("Breakdowns", Misc.getTextColor(), + 1f).getPosition().rightOfMid((UIComponentAPI) description, -106f); + itemPanel.addImageWithText(6f); + itemPanel.addSpacer(-38f); + final String valueFormat = Misc.getRoundedValueMaxOneAfterDecimal((int) (100 - (value * 100))); + String anchorImageName = Global.getSettings().getSpriteName("forgprod_ui", "effect_tooltip_anchor"); + itemPanel.addImage(anchorImageName, 0f); + TooltipMakerAPI.TooltipCreator outputLineTooltip = new BaseTooltipCreator() { + @Override + public float getTooltipWidth(Object tooltipParam) { + return 250; + } + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { + tooltip.addPara("Breakdown reduction:", Misc.getTextColor(), + 0f).getPosition().inTL(4f, -2f); + tooltip.addImage(breakdownsIconName, 24f, 24f, 0f); + UIComponentAPI icon = tooltip.getPrev(); + icon.getPosition().inTL(145f, -7f); + String valueLabel = "-" + valueFormat + "%"; + tooltip.addPara("%s Chance", 0f, Misc.getTextColor(), + Misc.getPositiveHighlightColor(), valueLabel).getPosition().inTR(-165f, -2f); + tooltip.addSpacer(-35f); + tooltip.addPara("Breakdown reduction:", Misc.getTextColor(), + 0f).getPosition().inTL(4f, 21f); + tooltip.addImage(breakdownsIconName, 24f, 24f, 0f); + UIComponentAPI iconSecondRow = tooltip.getPrev(); + iconSecondRow.getPosition().inTL(145f, 16f); + String valueLabelSecond = "-" + valueFormat + "%"; + tooltip.addPara("%s Size", 0f, Misc.getTextColor(), + Misc.getPositiveHighlightColor(), valueLabelSecond).getPosition().inTR(-165f, 21f); + tooltip.addSpacer(-36f); + } + }; + itemPanel.addTooltipToPrevious(outputLineTooltip, TooltipMakerAPI.TooltipLocation.LEFT); + UIComponentAPI anchorImage = itemPanel.getPrev(); + anchorImage.getPosition().setYAlignOffset(15f); + float offset = 63f; + anchorImage.getPosition().setXAlignOffset(offset); + itemPanel.addSpacer(0f); + itemPanel.getPrev().getPosition().setXAlignOffset(-offset); + itemPanel.addSpacer(2f); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/tabs/ModulesTab.java b/source/forgprod/abilities/interaction/panel/components/tabs/ModulesTab.java new file mode 100644 index 0000000..f42aeb7 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/tabs/ModulesTab.java @@ -0,0 +1,153 @@ +package forgprod.abilities.interaction.panel.components.tabs; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.campaign.CustomUIPanelPlugin; +import com.fs.starfarer.api.fleet.FleetMemberAPI; +import com.fs.starfarer.api.ui.*; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.conversion.support.ProductionConstants; +import forgprod.abilities.interaction.panel.components.tabs.modules.ModuleCard; +import forgprod.abilities.interaction.panel.components.tabs.modules.ModuleList; +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.abilities.modules.dataholders.ProductionModule; + +import static forgprod.abilities.interaction.panel.PanelConstants.PANEL_CONTENT_OFFSET; + +/** + * @author Ontheheavens + * @since 29.12.2022 + */ + +public class ModulesTab { + + private static ProductionModule shownModule; + + private static ProductionModule moduleQueuedToShow; + + private static CustomPanelAPI tabInstance; + + private static CustomPanelAPI moduleCard; + + private static boolean cardRedrawQueued; + + public static Map MODULE_DISPLAY_TAGS = new HashMap<>(); + static { + MODULE_DISPLAY_TAGS.put(ProductionConstants.REFINING_MODULE, true); + MODULE_DISPLAY_TAGS.put(ProductionConstants.FUEL_PRODUCTION_MODULE, true); + MODULE_DISPLAY_TAGS.put(ProductionConstants.HEAVY_INDUSTRY_MODULE, true); + } + + public static CustomPanelAPI create(CustomPanelAPI panel, float width, CustomUIPanelPlugin plugin) { + CustomPanelAPI moduleTab = panel.createCustomPanel(width, 400f, plugin); + ModulesTab.tabInstance = moduleTab; + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + if (fleet == null) return null; + List modules = getModulesForList(fleet, false); + if (modules.size() == 0) { + ModulesTab.addNoModulesNote(panel, width); + return moduleTab; + } + if (shownModule != null) { + moduleQueuedToShow = shownModule; + } else { + moduleQueuedToShow = modules.get(0); + } + ModuleList.create(moduleTab); + ModulesTab.renderModuleCard(); + PositionAPI tabPosition = panel.addComponent(moduleTab); + tabPosition.setYAlignOffset(1f); + return moduleTab; + } + + public static void setModuleCard(CustomPanelAPI moduleCard) { + ModulesTab.moduleCard = moduleCard; + } + + public static void setModuleQueuedToShow(ProductionModule module) { + ModulesTab.moduleQueuedToShow = module; + } + + public static ProductionModule getModuleQueuedToShow() { + return moduleQueuedToShow; + } + + public static CustomPanelAPI getTabInstance() { + return tabInstance; + } + + public static ProductionModule getShownModule() { + return shownModule; + } + + public static CustomPanelAPI getModuleCard() { + return moduleCard; + } + + public static boolean isCardRedrawQueued() { + return cardRedrawQueued; + } + + public static void setCardRedrawQueued(boolean cardRedrawQueued) { + ModulesTab.cardRedrawQueued = cardRedrawQueued; + } + + private static boolean isDisplayTagEnabled(ProductionModule module) { + String hullmodId = module.getHullmodId(); + return MODULE_DISPLAY_TAGS.get(hullmodId); + } + + public static List getModulesForList(CampaignFleetAPI fleet, boolean applyTags) { + List modules = new LinkedList<>(); + Map moduleIndex = FleetwideModuleManager.getInstance().getModuleIndex(); + List membersList = fleet.getFleetData().getMembersListCopy(); + for (FleetMemberAPI member : membersList) { + ProductionModule checkedModule = moduleIndex.get(member); + if (checkedModule != null) { + if (applyTags && !isDisplayTagEnabled(checkedModule)) { + continue; + } + modules.add(checkedModule); + } + } + return modules; + } + + public static void renderModuleCard() { + if (tabInstance == null) return; + if (moduleCard != null) { + tabInstance.removeComponent(moduleCard); + } + // Additional check in case refresh failed for some reason. + if (moduleQueuedToShow.getModuleCapacities() == null) { + FleetwideModuleManager.getInstance().refreshIndexes(Global.getSector().getPlayerFleet()); + return; + } + boolean queuedModuleHasCapacities = moduleQueuedToShow.getModuleCapacities().size() > 0; + if (!queuedModuleHasCapacities) return; + CustomPanelAPI moduleCard = ModuleCard.create(tabInstance, moduleQueuedToShow); + shownModule = moduleQueuedToShow; + tabInstance.addComponent(moduleCard).inTL(263f, -147f); + ModulesTab.moduleCard = moduleCard; + } + + public static void addNoModulesNote(CustomPanelAPI panel, float width) { + TooltipMakerAPI notePanel = panel.createUIElement(width - PANEL_CONTENT_OFFSET - 4f, + 120f, false); + notePanel.setForceProcessInput(false); + notePanel.setParaFont("graphics/fonts/insignia21LTaa.fnt"); + notePanel.setTextWidthOverride(800f); + LabelAPI noteText = notePanel.addPara("Your fleet does not have any production modules installed.", + Misc.getGrayColor(), 20f); + noteText.getPosition().setXAlignOffset(210f); + noteText.getPosition().setYAlignOffset(-380f); + panel.addUIElement(notePanel).inTMid(PANEL_CONTENT_OFFSET + 2f); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/tabs/modules/HullBlueprintSection.java b/source/forgprod/abilities/interaction/panel/components/tabs/modules/HullBlueprintSection.java new file mode 100644 index 0000000..98bf864 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/tabs/modules/HullBlueprintSection.java @@ -0,0 +1,562 @@ +package forgprod.abilities.interaction.panel.components.tabs.modules; + +import java.awt.*; +import java.util.List; +import java.util.*; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.campaign.CustomUIPanelPlugin; +import com.fs.starfarer.api.combat.ShieldAPI; +import com.fs.starfarer.api.combat.ShipAPI; +import com.fs.starfarer.api.combat.ShipHullSpecAPI; +import com.fs.starfarer.api.combat.ShipVariantAPI; +import com.fs.starfarer.api.fleet.FleetMemberAPI; +import com.fs.starfarer.api.fleet.FleetMemberType; +import com.fs.starfarer.api.impl.campaign.fleets.DefaultFleetInflater; +import com.fs.starfarer.api.impl.campaign.ids.Factions; +import com.fs.starfarer.api.impl.campaign.ids.Items; +import com.fs.starfarer.api.impl.campaign.ids.Strings; +import com.fs.starfarer.api.ui.*; +import com.fs.starfarer.api.util.ListMap; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.conversion.logic.production.HullPartsProductionLogic; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.interaction.panel.ControlPanelManager; +import forgprod.abilities.interaction.panel.PanelConstants; +import forgprod.abilities.interaction.panel.components.Common; +import forgprod.abilities.interaction.panel.objects.Button; +import forgprod.abilities.interaction.panel.objects.ExclusiveButton; +import forgprod.abilities.modules.FleetwideModuleManager; +import forgprod.hullmods.tooltip.TooltipCapacitySection; + +import static forgprod.abilities.conversion.support.ProductionConstants.HULL_PARTS_OUTPUT; +import static forgprod.abilities.conversion.support.checks.FleetwideProductionChecks.getFleetCapacityForType; +import static forgprod.abilities.conversion.support.checks.ItemBonusesChecks.hasSpecialItem; + +/** + * @author Ontheheavens + * @since 20.01.2023 + */ + +public class HullBlueprintSection { + + private static CustomPanelAPI sectionInstance; + private static TooltipMakerAPI variantCardInstance; + private static boolean cardRedrawQueued; + + public static CustomPanelAPI create(CustomPanelAPI moduleCard, float width) { + CustomUIPanelPlugin plugin = ControlPanelManager.getInstance().getPlugin(); + CustomPanelAPI blueprintSection = moduleCard.createCustomPanel(width, 200, plugin); + HullBlueprintSection.sectionInstance = blueprintSection; + TooltipMakerAPI header = HullBlueprintSection.addHeader(blueprintSection, width); + blueprintSection.addUIElement(header).inTL(0f, 0f); + HullBlueprintSection.renderVariantCard(); + float listWidth = 295f; + float listHeight = 104f; + TooltipMakerAPI listFrame = HullBlueprintSection.addListFrame(blueprintSection, listWidth, listHeight); + blueprintSection.addUIElement(listFrame).inTR(12f, 26f); + TooltipMakerAPI blueprintList = HullBlueprintSection.addBlueprintList(blueprintSection, + listWidth + 1f, listHeight - 4f); + blueprintSection.addUIElement(blueprintList).inTR(11f, 49f); + moduleCard.addComponent(blueprintSection); + return blueprintSection; + } + + public static boolean isCardRedrawQueued() { + return cardRedrawQueued; + } + + public static void setCardRedrawQueued(boolean cardRedrawQueued) { + HullBlueprintSection.cardRedrawQueued = cardRedrawQueued; + } + + public static void renderVariantCard() { + if (sectionInstance == null) return; + if (variantCardInstance != null) { + sectionInstance.removeComponent(variantCardInstance); + } + TooltipMakerAPI variantCard = HullBlueprintSection.createVariantCard(sectionInstance); + sectionInstance.addUIElement(variantCard).inTL(12f, 26f); + HullBlueprintSection.variantCardInstance = variantCard; + } + + private static TooltipMakerAPI addHeader(CustomPanelAPI blueprintSection, float width) { + TooltipMakerAPI sectionHeader = blueprintSection.createUIElement(width, 100, false); + sectionHeader.setForceProcessInput(true); + Color transparent = Misc.scaleAlpha(PanelConstants.DARK_PLAYER_COLOR, 0f); + String label = "Hulls"; + float labelWidth = sectionHeader.computeStringWidth(label); + float lineWidth = (width - (labelWidth + 22f)) / 2; + sectionHeader.addSectionHeading(label, PanelConstants.PLAYER_COLOR, transparent, Alignment.MID, 0f); + ButtonAPI titleLeftLine = Common.addLine(sectionHeader, lineWidth); + float verticalOffset = -10f; + titleLeftLine.getPosition().inTL(0f, 0f); + titleLeftLine.getPosition().setYAlignOffset(verticalOffset); + ButtonAPI titleRightLine = Common.addLine(sectionHeader, lineWidth); + titleRightLine.getPosition().inTR(0f, 0f); + titleRightLine.getPosition().setYAlignOffset(verticalOffset); + return sectionHeader; + } + + private static TooltipMakerAPI createVariantCard(CustomPanelAPI blueprintSection) { + float cardWidth = 90f; + float height = 104f; + TooltipMakerAPI variantCard = blueprintSection.createUIElement(cardWidth, height, false); + variantCard.addSectionHeading("Blueprint", Alignment.MID, 3f); + TooltipMakerAPI.TooltipCreator qualityOverviewTooltip = new BaseTooltipCreator() { + @Override + public float getTooltipWidth(Object tooltipParam) { + return 350f; + } + @SuppressWarnings("UnusedAssignment") + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { + HullPartsProductionLogic logic = HullPartsProductionLogic.getInstance(); + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + tooltip.addTitle("Quality Modifiers", PanelConstants.PLAYER_COLOR); + String quality = (int) (logic.getHullQuality() * 100f) + "%"; + float averageDMods = DefaultFleetInflater.getAverageDmodsForQuality(logic.getHullQuality()); + String averageDModsRounded = Misc.getRoundedValueMaxOneAfterDecimal(averageDMods); + tooltip.addPara("Ship quality: %s, an average of %s d-mods per hull.", 10f, + Misc.getHighlightColor(), quality, averageDModsRounded); + tooltip.beginGridFlipped(340f, 1, 45f, 6f); + int row = 0; + tooltip.addToGrid(0, row++, "Parts production: Makeshift Production Forge", "+20%"); + if (logic.hasOperationalSalvageRigs()) { + tooltip.addToGrid(0, row++, "Hull assembly: Salvage Rigs", "+10%"); + } + if (hasSpecialItem(fleet, Items.PRISTINE_NANOFORGE)) { + tooltip.addToGrid(0, row++, "Special equipment: Pristine Nanoforge", "+50%"); + } else if (hasSpecialItem(fleet, Items.CORRUPTED_NANOFORGE)) { + tooltip.addToGrid(0, row++, "Special equipment: Corrupted Nanoforge", "+20%"); + } + tooltip.addGrid(4f); + + } + }; + variantCard.addTooltipToPrevious(qualityOverviewTooltip, TooltipMakerAPI.TooltipLocation.ABOVE); + ButtonAPI headingLine = Common.addLine(variantCard, cardWidth); + headingLine.getPosition().setXAlignOffset(-5f); + UIComponentAPI frame = addFrameBox(variantCard, cardWidth, height); + frame.getPosition().inTL(10f, 22f); + UIComponentAPI variantIcon = addVariantIcon(variantCard); + variantIcon.getPosition().rightOfBottom(headingLine, -81f); + variantIcon.getPosition().setYAlignOffset(-86f); + UIComponentAPI progressBar = addProgressBar(variantCard, cardWidth); + progressBar.getPosition().rightOfBottom(frame, -cardWidth + 12f); + progressBar.getPosition().setYAlignOffset(3f); + UIComponentAPI specifications = addBlueprintSpec(variantCard); + specifications.getPosition().rightOfTop(frame, -62f); + specifications.getPosition().setYAlignOffset(30f); + return variantCard; + } + + private static UIComponentAPI addFrameBox(TooltipMakerAPI variantCard, float width, float height) { + TooltipMakerAPI frameContainer = variantCard.beginImageWithText(null, 2f); + frameContainer.setForceProcessInput(false); + Color baseBoxColor = Misc.interpolateColor(PanelConstants.DARK_PLAYER_COLOR, + PanelConstants.BRIGHT_PLAYER_COLOR, 0.5f); + Color boxColor = Misc.scaleColorOnly(baseBoxColor, 0.9f); + ButtonAPI box = frameContainer.addAreaCheckbox("", null, PanelConstants.PLAYER_COLOR, + boxColor, PanelConstants.PLAYER_COLOR, + width, height, 0f); + box.getPosition().setXAlignOffset(-width); + ButtonAPI barToppingLine = Common.addLine(frameContainer, width - 2f); + barToppingLine.getPosition().belowLeft(box, -22f); + barToppingLine.getPosition().setXAlignOffset(1f); + variantCard.addImageWithText(0f); + return variantCard.getPrev(); + } + + private static UIComponentAPI addVariantIcon(TooltipMakerAPI variantCard) { + List variantAsList = new ArrayList<>(); + ShipVariantAPI designatedVariant = FleetwideModuleManager.getInstance().getDesignatedVariant(); + FleetMemberAPI dummyMember = Global.getFactory().createFleetMember(FleetMemberType.SHIP, designatedVariant); + variantAsList.add(dummyMember); + variantCard.addShipList(1, 1, 72f, PanelConstants.PLAYER_COLOR, variantAsList, 0f); + return variantCard.getPrev(); + } + + private static UIComponentAPI addProgressBar(TooltipMakerAPI variantCard, float width) { + TooltipMakerAPI barContainer = variantCard.beginImageWithText(null, 2f); + barContainer.setForceProcessInput(false); + HullPartsProductionLogic logic = HullPartsProductionLogic.getInstance(); + float cost = logic.computeDesignatedHullCostInParts(); + float accumulated = FleetwideModuleManager.getInstance().getAccumulatedHullParts(); + float progressMultiplier = accumulated / cost; + if (progressMultiplier > 1f) { + progressMultiplier = 1f; + } + TextFieldAPI bar = barContainer.addTextField((width - 6f) * progressMultiplier, 16f, + "graphics/fonts/orbitron12condensed.fnt", 0f); + bar.setMidAlignment(); + Color backgroundColor = Misc.interpolateColor(PanelConstants.DARK_PLAYER_COLOR, + PanelConstants.BRIGHT_PLAYER_COLOR, 0.65f); + bar.setBgColor(backgroundColor); + PositionAPI barPosition = barContainer.getPrev().getPosition(); + barPosition.setXAlignOffset(-width + 1f); + barPosition.setYAlignOffset(-15f); + barContainer.setTextWidthOverride(100f); + String progressValue = Misc.getRoundedValueMaxOneAfterDecimal(100f * progressMultiplier); + LabelAPI progressLabel = barContainer.addPara(progressValue + "%", PanelConstants.PLAYER_COLOR, 0f); + progressLabel.getPosition().setYAlignOffset(16f); + progressLabel.getPosition().setXAlignOffset(-8f); + progressLabel.setAlignment(Alignment.MID); + variantCard.addImageWithText(0f); + return variantCard.getPrev(); + } + + private static UIComponentAPI addBlueprintSpec(TooltipMakerAPI variantCard) { + TooltipMakerAPI specContainer = variantCard.beginImageWithText(null, 2f); + FleetwideModuleManager manager = FleetwideModuleManager.getInstance(); + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + ShipVariantAPI variant = manager.getDesignatedVariant(); + specContainer.addSpacer(4f); + String creditsIconName = Global.getSettings().getSpriteName("forgprod_ui", "credits"); + HullPartsProductionLogic logic = HullPartsProductionLogic.getInstance(); + Color highlight = Misc.getHighlightColor(); + addSpecificationLine(specContainer, logic.getHullPrice(variant), creditsIconName, "Price", highlight); + String partsIconName = TooltipCapacitySection.getCapacitySpriteName(ProductionType.HULL_PARTS_PRODUCTION); + float costValue = logic.computeDesignatedHullCostInParts(); + addSpecificationLine(specContainer, costValue, partsIconName, "Cost", highlight); + addTooltipToCostLine(specContainer, variant); + float stockValue = manager.getAccumulatedHullParts(); + addSpecificationLine(specContainer, stockValue, partsIconName, "Stock", highlight); + float capacityFromModules = getFleetCapacityForType(fleet, ProductionType.HULL_PARTS_PRODUCTION); + float capacityByRigs = logic.getPossibleAssemblingCapacity(); + float hullPartsIncome = Math.min(capacityFromModules, capacityByRigs) * HULL_PARTS_OUTPUT; + Color incomeColor = highlight; + if (capacityFromModules > capacityByRigs) { + incomeColor = Misc.getNegativeHighlightColor(); + } + addSpecificationLine(specContainer, hullPartsIncome, partsIconName, "Income", incomeColor); + addTooltipToIncomeLine(specContainer); + String hullsIconName = Global.getSettings().getSpriteName("forgprod_ui", "hull_parts_symbolic"); + float hullsOutput = hullPartsIncome / costValue; + addSpecificationLine(specContainer, hullsOutput, hullsIconName, "Output", incomeColor); + variantCard.addImageWithText(2f); + return variantCard.getPrev(); + } + + private static void addTooltipToCostLine(TooltipMakerAPI specContainer, final ShipVariantAPI variant) { + String costTooltipAnchorName = Global.getSettings().getSpriteName("forgprod_ui", + "cost_tooltip_anchor"); + specContainer.setForceProcessInput(true); + specContainer.addImage(costTooltipAnchorName, 0f); + TooltipMakerAPI.TooltipCreator costTooltip = new BaseTooltipCreator() { + @Override + public float getTooltipWidth(Object tooltipParam) { + return 215f; + } + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { + tooltip.addTitle("Cost Modifiers", PanelConstants.PLAYER_COLOR); + tooltip.beginGridFlipped(210f, 1, 45f, 6f); + if (variant.getHullSpec().hasTag(Factions.DERELICT)) { + tooltip.addToGrid(0, 0, "Built-in blueprint", Strings.X + "0.6"); + } else { + tooltip.addToGrid(0, 0, "Extraneous blueprint", Strings.X + "1.8"); + } + if (variant.getHullSpec().getShieldType() != ShieldAPI.ShieldType.NONE) { + tooltip.addToGrid(0, 1, "Shield emitter machinery", Strings.X + "1.2"); + } + tooltip.addGrid(4f); + tooltip.addSpacer(2f); + } + }; + specContainer.addTooltipToPrevious(costTooltip, TooltipMakerAPI.TooltipLocation.LEFT); + UIComponentAPI anchorImage = specContainer.getPrev(); + anchorImage.getPosition().setYAlignOffset(15f); + float offset = -11f; + anchorImage.getPosition().setXAlignOffset(offset); + specContainer.addSpacer(2f); + specContainer.getPrev().getPosition().setXAlignOffset(-offset); + } + + private static void addTooltipToIncomeLine(TooltipMakerAPI specContainer) { + String incomeTooltipAnchorName = Global.getSettings().getSpriteName("forgprod_ui", + "income_tooltip_anchor"); + specContainer.setForceProcessInput(true); + specContainer.addImage(incomeTooltipAnchorName, 0f); + TooltipMakerAPI.TooltipCreator costTooltip = new BaseTooltipCreator() { + @Override + public float getTooltipWidth(Object tooltipParam) { + return 350f; + } + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { + tooltip.addTitle("Hull Parts Assembly", PanelConstants.PLAYER_COLOR); + tooltip.addPara("Hull parts produced by individual forge modules are " + + "unsuitable for bulk storage and need to be assembled into complete hulls " + + "in short order.", 10f); + tooltip.addPara("Since proper orbital works are unavailable in circumstance of moving fleet, " + + "hull assembly must involve makeshift scaffolding and manual " + + "parts fitting.", 6f); + tooltip.addPara("Hence, production is effectively " + + "limited by %s, which can be increased by " + + "acquiring construction vessels like %s.", 6f, Misc.getHighlightColor(), + "assembling capacity", "Salvage Rigs"); + tooltip.addSpacer(12f); + float separatorWidth = 120f; + ButtonAPI separatorLeft = Common.addLine(tooltip, separatorWidth); + LabelAPI separatorTitle = tooltip.addTitle("Limiting factors", PanelConstants.PLAYER_COLOR); + separatorTitle.getPosition().rightOfMid(separatorLeft, 4f); + ButtonAPI separatorRight = Common.addLine(tooltip, separatorWidth); + separatorRight.getPosition().rightOfMid((UIComponentAPI) separatorTitle, 4f); + separatorRight.getPosition().setXAlignOffset(-244f); + separatorRight.getPosition().setYAlignOffset(-1f); + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + HullPartsProductionLogic logic = HullPartsProductionLogic.getInstance(); + int productionFromModules = (int) (getFleetCapacityForType(fleet, ProductionType.HULL_PARTS_PRODUCTION) + * HULL_PARTS_OUTPUT); + int assemblyCapacityFromRigs = (int) (logic.getPossibleAssemblingCapacity() * HULL_PARTS_OUTPUT); + tooltip.beginGrid(310f, 1); + tooltip.addToGrid(0, 0, "Total module production:", productionFromModules + Strings.X); + Color assemblyCapacityColor = Misc.getHighlightColor(); + if (productionFromModules > assemblyCapacityFromRigs) { + assemblyCapacityColor = Misc.getNegativeHighlightColor(); + } + tooltip.addToGrid(0, 1, "Total assembling capacity:", + assemblyCapacityFromRigs + Strings.X, assemblyCapacityColor); + tooltip.addGrid(12f); + tooltip.getPrev().getPosition().setXAlignOffset(-210f); + tooltip.addSpacer(-10f); + } + }; + specContainer.addTooltipToPrevious(costTooltip, TooltipMakerAPI.TooltipLocation.ABOVE); + UIComponentAPI anchorImage = specContainer.getPrev(); + anchorImage.getPosition().setYAlignOffset(15f); + float offset = -10f; + anchorImage.getPosition().setXAlignOffset(offset); + specContainer.addSpacer(2f); + specContainer.getPrev().getPosition().setXAlignOffset(-offset); + } + + private static void addSpecificationLine(TooltipMakerAPI variantCard, float value, + String iconName, String description, Color valueColor) { + TooltipMakerAPI specificationLine = variantCard.beginImageWithText(null, 32f); + String valueName = "Hull Parts"; + if (description.equals("Output")) { + valueName = "Hulls"; + } else if (description.equals("Price")) { + valueName = "Credits"; + } + specificationLine.setTextWidthOverride(250f); + specificationLine.addPara(description + ":", 0f); + UIComponentAPI desc = specificationLine.getPrev(); + desc.getPosition().setXAlignOffset(0f); + specificationLine.addImage(iconName, 24f, 24f, 0f); + UIComponentAPI icon = specificationLine.getPrev(); + icon.getPosition().rightOfMid(desc, PanelConstants.SPEC_LINE_ICON_OFFSET); + String displayedValueLabel = Misc.getRoundedValueMaxOneAfterDecimal(value); + String format = displayedValueLabel + "×"; + if (description.equals("Price")) { + displayedValueLabel = Misc.getWithDGS(value); + format = displayedValueLabel; + } + specificationLine.setParaFontColor(valueColor); + specificationLine.addPara(format, 0f).getPosition().rightOfTop(desc, -345f); + LabelAPI valueLabel = (LabelAPI) specificationLine.getPrev(); + valueLabel.getPosition().setYAlignOffset(1f); + valueLabel.setAlignment(Alignment.RMID); + specificationLine.setParaFontColor(Misc.getTextColor()); + specificationLine.addPara(valueName, 1f).getPosition().rightOfMid(desc, -90f); + variantCard.addImageWithText(6f); + variantCard.addSpacer(-53f); + } + + private static TooltipMakerAPI addListFrame(CustomPanelAPI blueprintSection, float frameWidth, + float frameHeight) { + TooltipMakerAPI listFrame = blueprintSection.createUIElement(frameWidth, frameHeight, false); + listFrame.addSectionHeading("Available", Alignment.MID, 3f); + TooltipMakerAPI.TooltipCreator blueprintsOverviewTooltip = new BaseTooltipCreator() { + @Override + public float getTooltipWidth(Object tooltipParam) { + return 350f; + } + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { + tooltip.addTitle("Blueprint Limitations", PanelConstants.PLAYER_COLOR); + tooltip.addPara("Developed by Domain engineers in times before advent of High Tech " + + "fleet doctrines, Mothership autoforge modules were not expected to produce " + + "anything but the simplest hull parts and systems of Explorarium drones.", 10f); + tooltip.addPara("Consequently, even the basic Low Tech spaceship blueprints loaded " + + "in the module through software modifications are manufactured with " + + "considerably lower efficiency, while any piece of more delicate machinery like " + + "phase coils, flux shunts or drive field modulators is completely out of reach for " + + "autoforge production capacities.", 6f); + Color[] highlights = {Misc.getHighlightColor(), Misc.getDesignTypeColor("Explorarium"), + Misc.getDesignTypeColor("Low Tech")}; + tooltip.addPara("Only %s blueprints of %s or %s design types are supported.", 6f, + highlights, "Frigate", "Explorarium", "Low Tech"); + tooltip.addPara("Unsupported built-ins:", 6f); + tooltip.setBulletedListMode(" - "); + tooltip.setBulletColor(Misc.getTextColor()); + tooltip.setParaFontColor(Misc.getHighlightColor()); + List unsupportedHullmods = getUnsupportedHullmodsIds(); + for (String hullmodId : unsupportedHullmods) { + String hullmodName = Global.getSettings().getHullModSpec(hullmodId).getDisplayName(); + tooltip.addPara(hullmodName, 4f); + } + tooltip.setBulletedListMode(""); + tooltip.setParaFontColor(Misc.getTextColor()); + } + }; + listFrame.addTooltipToPrevious(blueprintsOverviewTooltip, TooltipMakerAPI.TooltipLocation.ABOVE); + ButtonAPI footingLine = Common.addLine(listFrame, frameWidth); + footingLine.getPosition().setYAlignOffset(-(frameHeight + 3f)); + footingLine.getPosition().setXAlignOffset(-5f); + return listFrame; + } + + private static TooltipMakerAPI addBlueprintList(CustomPanelAPI blueprintSection, float listWidth, + float listHeight) { + TooltipMakerAPI listPanel = blueprintSection.createUIElement(listWidth, listHeight, true); + List blueprints = sortBlueprintsByCost(getAvailableBlueprints()); + float summaryOffset = 0f; + for (ShipVariantAPI blueprint : blueprints) { + addBlueprintEntry(listPanel, blueprint, listWidth); + summaryOffset += 92f; + } + listPanel.addSpacer(-summaryOffset); + return listPanel; + } + + private static void addBlueprintEntry(TooltipMakerAPI listPanel, ShipVariantAPI blueprint, float entryWidth) { + boolean createChecked = FleetwideModuleManager.getInstance().getDesignatedVariant() == blueprint; + ButtonAPI frameCheckbox = addCheckbox(listPanel, blueprint, entryWidth - 5f, createChecked); + frameCheckbox.getPosition().setXAlignOffset(0f); + listPanel.addSpacer(0f); + listPanel.getPrev().getPosition().setXAlignOffset(0f); + FleetMemberAPI dummyMember = Global.getFactory().createFleetMember(FleetMemberType.SHIP, blueprint); + List memberAsList = new ArrayList<>(); + memberAsList.add(dummyMember); + listPanel.addShipList(1, 1, 30f, PanelConstants.PLAYER_COLOR, memberAsList, 0f); + UIComponentAPI hullIcon = listPanel.getPrev(); + hullIcon.getPosition().setYAlignOffset(30f); + String hullClass = blueprint.getHullSpec().getHullNameWithDashClass(); + LabelAPI hullClassLabel = listPanel.addPara(hullClass, PanelConstants.PLAYER_COLOR, 0f); + hullClassLabel.getPosition().rightOfMid(hullIcon, 4f); + hullClassLabel.getPosition().setYAlignOffset(0f); + listPanel.addImage(TooltipCapacitySection.getCapacitySpriteName(ProductionType.HULL_PARTS_PRODUCTION), + 24f, 24f, 0f); + UIComponentAPI partsIcon = listPanel.getPrev(); + partsIcon.getPosition().rightOfMid(hullIcon, 228f); + partsIcon.getPosition().setYAlignOffset(0f); + HullPartsProductionLogic logic = HullPartsProductionLogic.getInstance(); + String hullCost = Misc.getRoundedValueMaxOneAfterDecimal(logic.computeHullCostInParts(blueprint)) + "×"; + LabelAPI hullCostLabel = listPanel.addPara(hullCost, Misc.getHighlightColor(), 0f); + hullCostLabel.getPosition().leftOfMid(partsIcon, 2f); + hullCostLabel.getPosition().setYAlignOffset(0f); + hullCostLabel.setAlignment(Alignment.RMID); + listPanel.addSpacer(10f); + listPanel.getPrev().getPosition().setXAlignOffset(25f); + } + + private static ButtonAPI addCheckbox(TooltipMakerAPI listPanel, final ShipVariantAPI blueprint, + float width, boolean createChecked) { + Color checkedColor = Misc.scaleColorOnly(PanelConstants.DARK_PLAYER_COLOR, 0.7f); + ButtonAPI checkbox = listPanel.addAreaCheckbox("", null, PanelConstants.PLAYER_COLOR, + Misc.scaleAlpha(checkedColor, 0.99f), PanelConstants.BRIGHT_PLAYER_COLOR, + width, 30f, 1f); + if (createChecked) { + checkbox.setChecked(true); + checkbox.highlight(); + checkbox.setEnabled(false); + checkbox.setButtonDisabledPressedSound("ui_button_pressed"); + } + new ExclusiveButton(checkbox, Button.Type.BLUEPRINT, createChecked) { + @Override + public void applyEffect() { + if (!(FleetwideModuleManager.getInstance().getDesignatedVariant() == blueprint) || + HullBlueprintSection.variantCardInstance == null) { + FleetwideModuleManager.getInstance().setDesignatedVariant(blueprint); + HullBlueprintSection.setCardRedrawQueued(true); + this.getInner().highlight(); + this.getInner().setEnabled(false); + this.getInner().setButtonDisabledPressedSound("ui_button_pressed"); + } + } + }; + return checkbox; + } + + private static Set getAvailableBlueprints() { + Set allKnown = Global.getSector().getPlayerFaction().getKnownShips(); + Set resultVariants = new HashSet<>(); + ListMap allVariants = Global.getSettings().getHullIdToVariantListMap(); + for (String blueprintId : allKnown) { + ShipHullSpecAPI spec = Global.getSettings().getHullSpec(blueprintId); + if (!isEligibleHull(spec) || !isEligibleTech(spec)) { + continue; + } + List hullVariants = allVariants.getList(spec.getBaseHullId()); + ShipVariantAPI targetVariant = null; + for (String variantId : hullVariants) { + ShipVariantAPI checked = Global.getSettings().getVariant(variantId); + if (checked.isGoalVariant()) { + targetVariant = checked; + } + } + if (targetVariant != null) { + resultVariants.add(targetVariant); + } + } + resultVariants.add(Global.getSettings().getVariant("picket_Assault")); + resultVariants.add(Global.getSettings().getVariant("warden_Defense")); + resultVariants.add(Global.getSettings().getVariant("sentry_FS")); + return resultVariants; + } + + private static List sortBlueprintsByCost(Set blueprintSet) { + List result = new ArrayList<>(blueprintSet); + Collections.sort(result, new Comparator() { + @Override + public int compare(ShipVariantAPI first, ShipVariantAPI second) { + HullPartsProductionLogic logic = HullPartsProductionLogic.getInstance(); + Float firstCost = logic.computeHullCostInParts(first); + Float secondCost = logic.computeHullCostInParts(second); + return firstCost.compareTo(secondCost); + } + }); + return result; + } + + private static List getUnsupportedHullmodsIds() { + List unsupportedHullmods = new ArrayList<>(); + unsupportedHullmods.add("phasefield"); + unsupportedHullmods.add("fluxshunt"); + unsupportedHullmods.add("delicate"); + unsupportedHullmods.add("high_maintenance"); + unsupportedHullmods.add("drive_field_stabilizer"); + return unsupportedHullmods; + } + + private static boolean isEligibleHull(ShipHullSpecAPI spec) { + if (spec.getHullSize() != ShipAPI.HullSize.FRIGATE) { + return false; + } + List unsupportedHullmods = getUnsupportedHullmodsIds(); + for (String hullmod : unsupportedHullmods) { + if (spec.isBuiltInMod(hullmod)) { + return false; + } + } + return true; + } + + private static boolean isEligibleTech(ShipHullSpecAPI spec) { + Set eligibleTech = new HashSet<>(); + eligibleTech.add("Explorarium"); + eligibleTech.add("Low Tech"); + for (String tech : eligibleTech) { + if (spec.getManufacturer().equals(tech)) { + return true; + } + } + return false; + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModeButtonTooltip.java b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModeButtonTooltip.java new file mode 100644 index 0000000..61fcad5 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModeButtonTooltip.java @@ -0,0 +1,42 @@ +package forgprod.abilities.interaction.panel.components.tabs.modules; + +import com.fs.starfarer.api.ui.TooltipMakerAPI; +import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipCreator; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.modules.dataholders.ProductionModule.ProductionMode; + +/** + * @author Ontheheavens + * @since 16.01.2023 + */ + +public class ModeButtonTooltip implements TooltipCreator { + + private final ProductionMode associatedMode; + + public ModeButtonTooltip(ProductionMode mode) { + this.associatedMode = mode; + } + + @Override + public boolean isTooltipExpandable(Object tooltipParam) { + return false; + } + + @Override + public float getTooltipWidth(Object tooltipParam) { + return 200; + } + + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { + String modeTitle = "Primary"; + if (associatedMode == ProductionMode.SECONDARY) { + modeTitle = "Secondary"; + } + tooltip.addTitle(modeTitle + " Mode", Misc.getBasePlayerColor()); + tooltip.addPara("Production mode determines which module capacities can be activated.", Misc.getTextColor(), 10f); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleCapacityPanel.java b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleCapacityPanel.java new file mode 100644 index 0000000..1dbc590 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleCapacityPanel.java @@ -0,0 +1,296 @@ +package forgprod.abilities.interaction.panel.components.tabs.modules; + +import java.awt.*; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI; +import com.fs.starfarer.api.impl.campaign.ids.Commodities; +import com.fs.starfarer.api.ui.*; +import com.fs.starfarer.api.util.Misc; +import com.fs.starfarer.api.util.Pair; + +import forgprod.abilities.conversion.logic.base.ProductionLogic; +import forgprod.abilities.conversion.logic.production.FuelProductionLogic; +import forgprod.abilities.conversion.logic.production.HullPartsProductionLogic; +import forgprod.abilities.conversion.support.ProductionConstants; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.interaction.panel.PanelConstants; +import forgprod.abilities.interaction.panel.components.Common; +import forgprod.abilities.interaction.panel.components.tabs.ModulesTab; +import forgprod.abilities.interaction.panel.objects.Button; +import forgprod.abilities.interaction.panel.objects.ExclusiveButton; +import forgprod.abilities.modules.dataholders.ProductionCapacity; +import forgprod.hullmods.tooltip.TooltipCapacitySection; + +/** + * @author Ontheheavens + * @since 04.01.2023 + */ + +public class ModuleCapacityPanel { + + public static TooltipMakerAPI createCapacityPanel(CustomPanelAPI moduleCard, ProductionCapacity capacity, + float width) { + TooltipMakerAPI capacityPanel = moduleCard.createUIElement(width, 200, false); + ButtonAPI headerLine = Common.addLine(capacityPanel, width); + headerLine.getPosition().setXAlignOffset(0f); + UIComponentAPI title = addCapacityTitle(capacityPanel, capacity); + title.getPosition().setXAlignOffset(-549f); + UIComponentAPI iconAnchor = ModuleCapacityPanel.createCapacityIcon(capacityPanel, + capacity.getProductionType(), capacity.isToggledOn()); + iconAnchor.getPosition().inTL(-286f, 37f); + UIComponentAPI capacitySpec = ModuleCapacityPanel.addCapacitySpec(capacityPanel, capacity); + capacitySpec.getPosition().belowLeft(title, 0f); + capacitySpec.getPosition().setXAlignOffset(525f); + float specYOffset = 19f; + if (shouldAddStatusNote(capacity)) { + specYOffset = 34f; + } + capacitySpec.getPosition().setYAlignOffset(specYOffset); + ButtonAPI toggleButton = createToggleButton(capacityPanel, capacity); + toggleButton.getPosition().belowLeft(iconAnchor, 4f); + toggleButton.getPosition().setYAlignOffset(138f); + toggleButton.getPosition().setXAlignOffset(298f); + if (capacity.getProductionType() == ProductionType.FUEL_PRODUCTION) { + ButtonAPI footerLine = Common.addLine(capacityPanel, width); + footerLine.getPosition().setXAlignOffset(-12f); + footerLine.getPosition().setYAlignOffset(-36f); + } + return capacityPanel; + } + + private static UIComponentAPI createCapacityIcon(TooltipMakerAPI capacityPanel, ProductionType type, boolean enabled) { + String imageId = "frame_bright"; + if (!enabled) { + imageId = "frame"; + } + TooltipMakerAPI capacityIconContainer = capacityPanel.beginImageWithText(null, 32f); + capacityIconContainer.addImage(Global.getSettings().getSpriteName("forgprod_ui", imageId), + 90f, 90f, 0f); + UIComponentAPI capacityIconFrame = capacityIconContainer.getPrev(); + capacityIconFrame.getPosition().setXAlignOffset(-360f); + capacityIconContainer.addImage(Global.getSettings().getSpriteName("forgprod_ui", imageId), + 64f, 64f, 0f); + UIComponentAPI backgroundFrame = capacityIconContainer.getPrev(); + backgroundFrame.getPosition().belowMid(capacityIconFrame, -77f); + capacityIconContainer.addImage(TooltipCapacitySection.getCapacitySpriteName(type), + 80f, 80f, 0f); + PositionAPI iconPosition = capacityIconContainer.getPrev().getPosition(); + float iconOffset = (iconPosition.getY() - capacityIconFrame.getPosition().getY()) - 18f; + iconPosition.belowMid(capacityIconFrame, iconOffset); + capacityPanel.addImageWithText(2f); + return capacityPanel.getPrev(); + } + + private static ButtonAPI createToggleButton(TooltipMakerAPI capacityPanel, final ProductionCapacity capacity) { + boolean enabled = capacity.isToggledOn(); + String status = "Enabled"; + Color buttonColor = Misc.interpolateColor(Misc.scaleColorOnly(PanelConstants.DARK_PLAYER_COLOR, 1.4f), + PanelConstants.PLAYER_COLOR, 0.15f); + if (!enabled) { + status = "Disabled"; + buttonColor = PanelConstants.DARK_PLAYER_COLOR; + } + ButtonAPI toggleButtonInstance = capacityPanel.addButton(status, null, + PanelConstants.PLAYER_COLOR, buttonColor, Alignment.MID, + CutStyle.BOTTOM, 90f, 22f, 2f); + // Isn't actually exclusive. + new ExclusiveButton(toggleButtonInstance, Button.Type.STANDARD) { + @Override + public void affectOthersInGroup() { + } + @Override + public void applyEffect() { + capacity.setToggledOn(!capacity.isToggledOn()); + ModulesTab.setCardRedrawQueued(true); + } + }; + return toggleButtonInstance; + } + + private static UIComponentAPI addCapacityTitle(TooltipMakerAPI capacityPanel, ProductionCapacity capacity) { + TooltipMakerAPI capacityTitleContainer = capacityPanel.beginImageWithText(null, 2f); + capacityTitleContainer.setTextWidthOverride(250f); + ProductionType type = capacity.getProductionType(); + String capacityName = ProductionConstants.PRODUCTION_NAMES.get(type) + " Production"; + String status; + Color statusColor; + boolean toggled = capacity.isToggledOn(); + boolean active = capacity.isActive(); + boolean hasThroughput = capacity.getCurrentThroughput() > 0f; + if (active && hasThroughput) { + status = "Active"; + statusColor = Misc.getHighlightColor(); + } else if (toggled) { + status = "Inactive"; + statusColor = Misc.getNegativeHighlightColor(); + } else { + status = "Disabled"; + statusColor = Misc.getGrayColor(); + } + capacityTitleContainer.setParaFont("graphics/fonts/orbitron12condensed.fnt"); + capacityTitleContainer.addPara(capacityName, PanelConstants.PLAYER_COLOR, 10f); + capacityTitleContainer.setParaFontDefault(); + LabelAPI statusLabel = capacityTitleContainer.addPara("Status:", 8f); + + boolean tanksFull = (capacity.getProductionType() == ProductionType.FUEL_PRODUCTION) && + FuelProductionLogic.getInstance().areFuelTanksFull(Global.getSector().getPlayerFleet()); + boolean rosterFull = (capacity.getProductionType() == ProductionType.HULL_PARTS_PRODUCTION) && + HullPartsProductionLogic.getInstance().isPlayerFleetRosterFull(); + boolean noActiveRigs = (capacity.getProductionType() == ProductionType.HULL_PARTS_PRODUCTION) && + !HullPartsProductionLogic.getInstance().hasOperationalSalvageRigs(); + boolean shouldAddStatusNote = tanksFull || rosterFull || noActiveRigs; + float statusValueXPad = -90f; + LabelAPI statusNoteLabel = null; + if (shouldAddStatusNote) { + statusValueXPad = -45f; + String statusNote = ""; + String tanksFullNote = "(tanks full)"; + String rosterFullNote = "(roster full)"; + String noActiveRigsNote = "(no active rigs)"; + if (tanksFull) { + statusNote = tanksFullNote; + } + if (rosterFull) { + statusNote = rosterFullNote; + } + if (noActiveRigs) { + statusNote = noActiveRigsNote; + } + statusNoteLabel = capacityTitleContainer.addPara(statusNote, Misc.getNegativeHighlightColor(), 0f); + statusNoteLabel.getPosition().rightOfMid((UIComponentAPI) statusLabel, -90f); + } + LabelAPI statusValue = capacityTitleContainer.addPara(status, statusColor, 0f); + statusValue.getPosition().rightOfMid((UIComponentAPI) statusLabel, statusValueXPad); + if (shouldAddStatusNote) { + statusValue.getPosition().leftOfMid((UIComponentAPI) statusNoteLabel, 7f); + statusValue.getPosition().setYAlignOffset(1f); + statusValue.setAlignment(Alignment.RMID); + } + capacityPanel.addImageWithText(2f); + return capacityPanel.getPrev(); + } + + private static boolean shouldAddStatusNote(ProductionCapacity capacity) { + boolean tanksFull = (capacity.getProductionType() == ProductionType.FUEL_PRODUCTION) && + FuelProductionLogic.getInstance().areFuelTanksFull(Global.getSector().getPlayerFleet()); + boolean rosterFull = (capacity.getProductionType() == ProductionType.HULL_PARTS_PRODUCTION) && + HullPartsProductionLogic.getInstance().isPlayerFleetRosterFull(); + boolean noActiveRigs = (capacity.getProductionType() == ProductionType.HULL_PARTS_PRODUCTION) && + !HullPartsProductionLogic.getInstance().hasOperationalSalvageRigs(); + return (tanksFull || rosterFull || noActiveRigs); + } + + private static UIComponentAPI addCapacitySpec(TooltipMakerAPI capacityPanel, ProductionCapacity capacity) { + CampaignFleetAPI fleet = capacity.getParentModule().getParentFleetMember().getFleetData().getFleet(); + ProductionLogic logic = capacity.getLogic(); + ProductionType type = capacity.getProductionType(); + int shipCycles = capacity.getModifiedCapacity(fleet); + float bonusOutput = logic.getCycleOutputBonus(fleet); + TooltipMakerAPI capacitySpecContainer = capacityPanel.beginImageWithText(null, 2f); + capacitySpecContainer.setTextWidthOverride(270f); + UIComponentAPI anchor = capacitySpecContainer.addSpacer(0f); + float offsetX = -520f; + anchor.getPosition().setXAlignOffset(offsetX); + capacityPanel.addSpacer(0f); + capacityPanel.getPrev().getPosition().setXAlignOffset(-offsetX); + ModuleCapacityPanel.addThroughputLine(capacitySpecContainer, capacity, fleet); + capacitySpecContainer.getPrev().getPosition().setXAlignOffset(-150f); + capacitySpecContainer.getPrev().getPosition().setYAlignOffset(-35f); + float primaryInputValue = (shipCycles * logic.getCyclePrimaryInputAmount()); + float outputValue = (shipCycles * (logic.getCycleOutputAmount() + bonusOutput)); + float meansValue = (shipCycles * logic.getCycleMachineryUse()); + CommoditySpecAPI primaryInput = TooltipCapacitySection.getCommoditySpecByType(type, false, true); + CommoditySpecAPI output = TooltipCapacitySection.getCommoditySpecByType(type, true, false); + String meansIcon = Global.getSettings().getCommoditySpec(Commodities.HEAVY_MACHINERY).getIconName(); + boolean hasSecondaryInput = logic.hasSecondaryInput(); + ModuleCapacityPanel.addSpecificationLine(capacitySpecContainer, primaryInput.getIconName(), + primaryInputValue, 0, "Input", primaryInput.getName(), capacity); + if (hasSecondaryInput) { + CommoditySpecAPI secondaryInput = TooltipCapacitySection.getCommoditySpecByType(type, false, false); + float secondaryInputValue = (shipCycles * logic.getCycleSecondaryInputAmount()); + ModuleCapacityPanel.addSpecificationLine(capacitySpecContainer, secondaryInput.getIconName(), + secondaryInputValue, 0, "Input", secondaryInput.getName(), capacity); + } + String outputName; + String outputIconName; + if (logic instanceof HullPartsProductionLogic) { + HullPartsProductionLogic partsLogic = (HullPartsProductionLogic) logic; + CommoditySpecAPI tertiaryInput = TooltipCapacitySection.getCommoditySpecById(partsLogic.getTertiaryInputType()); + float tertiaryInputValue = shipCycles * (partsLogic.getCycleTertiaryInputAmount()); + ModuleCapacityPanel.addSpecificationLine(capacitySpecContainer, tertiaryInput.getIconName(), + tertiaryInputValue, 0, "Input", tertiaryInput.getName(), capacity); + CommoditySpecAPI quaternaryInput = TooltipCapacitySection.getCommoditySpecById(partsLogic.getQuaternaryInputType()); + float quaternaryInputValue = shipCycles * (partsLogic.getCycleQuaternaryInputAmount()); + ModuleCapacityPanel.addSpecificationLine(capacitySpecContainer, quaternaryInput.getIconName(), + quaternaryInputValue, 0, "Input", "Machinery", capacity); + outputName = ProductionConstants.PRODUCTION_NAMES.get(ProductionType.HULL_PARTS_PRODUCTION); + outputIconName = TooltipCapacitySection.getCapacitySpriteName(ProductionType.HULL_PARTS_PRODUCTION); + } else { + outputName = output.getName(); + outputIconName = output.getIconName(); + if (outputName.equals("Heavy Machinery")) { + outputName = "Machinery"; + } + } + ModuleCapacityPanel.addSpecificationLine(capacitySpecContainer, outputIconName, + outputValue, (bonusOutput * shipCycles), "Output", outputName, capacity); + ModuleCapacityPanel.addSpecificationLine(capacitySpecContainer, meansIcon, + meansValue, 0, "Means", "Machinery", capacity); + capacityPanel.addImageWithText(4f); + return capacityPanel.getPrev(); + } + + private static void addThroughputLine(TooltipMakerAPI capacityPanel, + ProductionCapacity capacity, CampaignFleetAPI fleet) { + LabelAPI anchor = capacityPanel.addPara("Throughput:", 11f); + int throughput = (int) (capacity.getCurrentThroughput() * 100f); + capacityPanel.addPara(throughput + "%", Misc.getHighlightColor(), + 0f).getPosition().rightOfMid((UIComponentAPI) anchor, -388f); + LabelAPI value = (LabelAPI) capacityPanel.getPrev(); + value.setAlignment(Alignment.TR); + Pair status = Common.retrieveCapacityThroughputStatus(capacity, fleet); + String statusLabel = status.one; + Color statusColor = status.two; + capacityPanel.addPara(statusLabel, statusColor, 0f).getPosition().rightOfMid((UIComponentAPI) anchor, -110f); + capacityPanel.addSpacer(-33f); + } + + private static void addSpecificationLine(TooltipMakerAPI capacityPanel, String iconSprite, float value, + float bonusValue, String description, String commodity, + ProductionCapacity capacity) { + TooltipMakerAPI specificationLine = capacityPanel.beginImageWithText(null, 32f); + specificationLine.setTextWidthOverride(250f); + specificationLine.addPara(description + ":", 0f); + UIComponentAPI desc = specificationLine.getPrev(); + desc.getPosition().setXAlignOffset(0f); + specificationLine.addImage(iconSprite, 24f, 24f, 0f); + UIComponentAPI icon = specificationLine.getPrev(); + icon.getPosition().rightOfMid(desc, PanelConstants.SPEC_LINE_ICON_OFFSET); + if (!capacity.isActive() || capacity.getCurrentThroughput() <= 0f) { + value = 0; + } + String displayedValueLabel = Misc.getRoundedValueMaxOneAfterDecimal(value); + specificationLine.setParaFontColor(Misc.getHighlightColor()); + String format = displayedValueLabel + "×"; + boolean bonusValid = bonusValue > 0 && capacity.getCurrentThroughput() > 0f; + if (bonusValid) { + String bonusValueLabel = "(+" + Misc.getRoundedValueMaxOneAfterDecimal(bonusValue) + ")"; + format = displayedValueLabel + bonusValueLabel + "×"; + } + specificationLine.addPara(format, 0f).getPosition().rightOfTop(desc, -345f); + LabelAPI valueLabel = (LabelAPI) specificationLine.getPrev(); + if (bonusValid) { + valueLabel.setHighlightColor(Misc.getPositiveHighlightColor()); + valueLabel.setHighlight(displayedValueLabel.length(), format.length() - 2); + } + valueLabel.getPosition().setYAlignOffset(1f); + valueLabel.setAlignment(Alignment.RMID); + specificationLine.setParaFontColor(Misc.getTextColor()); + specificationLine.addPara(commodity, 1f).getPosition().rightOfMid(desc, -90f); + capacityPanel.addImageWithText(6f); + capacityPanel.addSpacer(-53f); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleCard.java b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleCard.java new file mode 100644 index 0000000..1a20451 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleCard.java @@ -0,0 +1,184 @@ +package forgprod.abilities.interaction.panel.components.tabs.modules; + +import java.awt.*; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CustomUIPanelPlugin; +import com.fs.starfarer.api.combat.MutableStat; +import com.fs.starfarer.api.combat.ShipAPI; +import com.fs.starfarer.api.fleet.FleetMemberAPI; +import com.fs.starfarer.api.ui.*; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.conversion.support.ProductionConstants; +import forgprod.abilities.conversion.support.ProductionType; +import forgprod.abilities.conversion.support.checks.ModuleConditionChecks; +import forgprod.abilities.interaction.panel.ControlPanelManager; +import forgprod.abilities.interaction.panel.PanelConstants; +import forgprod.abilities.interaction.panel.components.Common; +import forgprod.abilities.interaction.panel.components.tabs.ModulesTab; +import forgprod.abilities.interaction.panel.objects.Button; +import forgprod.abilities.interaction.panel.objects.ExclusiveButton; +import forgprod.abilities.modules.dataholders.ProductionCapacity; +import forgprod.abilities.modules.dataholders.ProductionModule; + +/** + * @author Ontheheavens + * @since 01.01.2023 + */ + +public class ModuleCard { + + public static CustomPanelAPI create(CustomPanelAPI moduleTab, ProductionModule module) { + if (module == null) return null; + + CustomUIPanelPlugin plugin = ControlPanelManager.getInstance().getPlugin(); + float width = 668f; + CustomPanelAPI moduleCard = moduleTab.createCustomPanel(width, 500, plugin); + TooltipMakerAPI cardHeader = ModuleCard.addHeader(moduleCard, width, module); + moduleCard.addUIElement(cardHeader).inTL(0f, 0f); + float offset = 0f; + for (ProductionCapacity capacity : module.getModuleCapacities()) { + TooltipMakerAPI capacityPanel = ModuleCapacityPanel.createCapacityPanel(moduleCard, capacity, width); + moduleCard.addUIElement(capacityPanel).belowRight(cardHeader, offset - 177f); + offset += 191f; + if (capacity.getProductionType() == ProductionType.HULL_PARTS_PRODUCTION) { + CustomPanelAPI blueprintSection = HullBlueprintSection.create(moduleCard, width); + blueprintSection.getPosition().inTL(0f, 335f); + } + } + return moduleCard; + } + + private static TooltipMakerAPI addHeader(CustomPanelAPI moduleCard, float width, ProductionModule module) { + TooltipMakerAPI cardHeader = moduleCard.createUIElement(width, 100, false); + cardHeader.setForceProcessInput(true); + cardHeader.addSectionHeading("Module", Alignment.MID, 2f); + UIComponentAPI headingAnchor = cardHeader.addSpacer(0f); + UIComponentAPI shipIcon = Common.addShipIcon(cardHeader, module, 72f, 4f, true); + UIComponentAPI shipInfo = ModuleCard.addShipInfo(cardHeader, module); + shipInfo.getPosition().rightOfTop(shipIcon, -660f); + UIComponentAPI imageAnchor = cardHeader.addSpacer(0f); + imageAnchor.getPosition().inTR(104f, 21f); + String illustrationIcon = Global.getSettings().getSpriteName("forgprod_hullmods", + module.getHullmodId() + "_big"); + cardHeader.addImage(illustrationIcon, 0f); + UIComponentAPI illustrationAnchor = cardHeader.getPrev(); + illustrationAnchor.getPosition().belowLeft(imageAnchor, 0f); + cardHeader.addTooltipToPrevious(new ModuleTooltip(module), TooltipMakerAPI.TooltipLocation.BELOW); + UIComponentAPI moduleInfo = ModuleCard.addModuleInfo(cardHeader, module); + moduleInfo.getPosition().leftOfTop(illustrationAnchor, 264f); + cardHeader.addSpacer(0f).getPosition().belowLeft(headingAnchor, 78f); + ModuleCard.addCapacitiesSubHeader(cardHeader, module); + return cardHeader; + } + + private static void addCapacitiesSubHeader(TooltipMakerAPI cardHeader, ProductionModule module) { + String headerAddendum = ""; + boolean isHeavyIndustryModule = module.getHullmodId().equals(ProductionConstants.HEAVY_INDUSTRY_MODULE); + if (isHeavyIndustryModule) { + if (module.getCapacitiesMode() == ProductionModule.ProductionMode.PRIMARY) { + headerAddendum = " (primary)"; + } else if (module.getCapacitiesMode() == ProductionModule.ProductionMode.SECONDARY) { + headerAddendum = " (secondary)"; + } + } + cardHeader.addSectionHeading("Capacities" + headerAddendum, Alignment.MID, 2f); + UIComponentAPI capacitiesHeading = cardHeader.getPrev(); + if (isHeavyIndustryModule) { + ButtonAPI primaryModeButton = ModuleCard.createModeButton(cardHeader, + module, ProductionModule.ProductionMode.PRIMARY); + primaryModeButton.getPosition().rightOfBottom(capacitiesHeading, -108f); + primaryModeButton.getPosition().setYAlignOffset(1f); + ButtonAPI secondaryModeButton = ModuleCard.createModeButton(cardHeader, + module, ProductionModule.ProductionMode.SECONDARY); + secondaryModeButton.getPosition().rightOfBottom(capacitiesHeading, -50f); + secondaryModeButton.getPosition().setYAlignOffset(1f); + cardHeader.addSpacer(-36f); + } + } + + private static ButtonAPI createModeButton(TooltipMakerAPI cardHeader, final ProductionModule module, + final ProductionModule.ProductionMode mode) { + String name = "I"; + if (mode == ProductionModule.ProductionMode.SECONDARY) { + name = "II"; + } + Color buttonColor = Misc.interpolateColor(Misc.scaleColorOnly(PanelConstants.DARK_PLAYER_COLOR, 1.2f), + PanelConstants.PLAYER_COLOR, 0.1f); + Color highlightColor = Misc.interpolateColor(Misc.scaleColorOnly(PanelConstants.PLAYER_COLOR, 0.9f), + PanelConstants.DARK_PLAYER_COLOR, 0.1f); + ButtonAPI modeButtonInstance = cardHeader.addButton(name, null, + highlightColor, buttonColor, Alignment.MID, + CutStyle.ALL, 54f, 16f, 2f); + cardHeader.addTooltipToPrevious(new ModeButtonTooltip(mode), TooltipMakerAPI.TooltipLocation.BELOW); + if (module.getCapacitiesMode() == mode) { + modeButtonInstance.highlight(); + } + new ExclusiveButton(modeButtonInstance, Button.Type.MODULE_MODE) { + @Override + public void applyEffect() { + if (module.getCapacitiesMode() != mode) { + module.setCapacitiesMode(mode); + ModulesTab.setCardRedrawQueued(true); + } + } + }; + return modeButtonInstance; + } + + private static UIComponentAPI addShipInfo(TooltipMakerAPI cardHeader, ProductionModule module) { + FleetMemberAPI member = module.getParentFleetMember(); + Color highlight = Misc.getHighlightColor(); + TooltipMakerAPI infoPanel = cardHeader.beginImageWithText(null, 1f); + infoPanel.setTextWidthOverride(250f); + infoPanel.addPara(member.getShipName(), PanelConstants.PLAYER_COLOR, 2f); + String hullsize = "Cruiser"; + if (member.getHullSpec().getHullSize() != ShipAPI.HullSize.CRUISER) { + hullsize = "Capital"; + } + infoPanel.addPara("Hull size: " + hullsize, 2f, highlight, hullsize); + String reason = "Operational (CR: " + + Misc.getRoundedValueMaxOneAfterDecimal(member.getRepairTracker().getCR() * 100f) + "%)"; + String status = "Status: %s"; + Color reasonColor = highlight; + if (!ModuleConditionChecks.isOperational(member)) { + reasonColor = Misc.getNegativeHighlightColor(); + String disabled = "Inactive "; + if (member.getRepairTracker().isMothballed()) { + reason = disabled + "(mothballed)"; + } else if (member.getRepairTracker().isSuspendRepairs()) { + reason = disabled + "(repairs suspended)"; + } else { + reason = disabled + "(CR: " + + Misc.getRoundedValueMaxOneAfterDecimal(member.getRepairTracker().getCR() * 100f) + "%)"; + } + } + infoPanel.addPara(status, 2f, Misc.getTextColor(), reasonColor, reason); + MutableStat suppliesPerMonth = member.getStats().getSuppliesPerMonth(); + float maintenance = suppliesPerMonth.getModifiedValue() / 30f; + String maintenanceLabel = Misc.getRoundedValueMaxOneAfterDecimal(maintenance); + infoPanel.addPara("Ship daily maintenance: " + maintenanceLabel + " supplies", + 2f, Misc.getTextColor(), highlight, maintenanceLabel); + cardHeader.addImageWithText(2f); + return cardHeader.getPrev(); + } + + private static UIComponentAPI addModuleInfo(TooltipMakerAPI cardHeader, ProductionModule module) { + Color highlight = Misc.getHighlightColor(); + TooltipMakerAPI infoPanel = cardHeader.beginImageWithText(null, 1f); + infoPanel.setTextWidthOverride(250f); + String moduleId = "Production Module #" + module.getParentFleetMember().getId().toUpperCase(); + LabelAPI idLine = infoPanel.addPara(moduleId, PanelConstants.PLAYER_COLOR, 2f); + idLine.setAlignment(Alignment.RMID); + ShipAPI.HullSize shipSize = module.getParentFleetMember().getHullSpec().getHullSize(); + int baseCapacity = ProductionConstants.SHIPSIZE_CAPACITY.get(shipSize); + String baseCapacityLabel = baseCapacity + "×"; + String moduleSize = "Module volume: " + baseCapacityLabel; + LabelAPI sizeLine = infoPanel.addPara(moduleSize, 2f, Misc.getTextColor(), highlight, baseCapacityLabel); + sizeLine.setAlignment(Alignment.RMID); + cardHeader.addImageWithText(2f); + return cardHeader.getPrev(); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleList.java b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleList.java new file mode 100644 index 0000000..5a2cd65 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleList.java @@ -0,0 +1,256 @@ +package forgprod.abilities.interaction.panel.components.tabs.modules; + +import java.awt.*; +import java.util.List; + +import com.fs.starfarer.api.Global; +import com.fs.starfarer.api.campaign.CampaignFleetAPI; +import com.fs.starfarer.api.fleet.FleetMemberAPI; +import com.fs.starfarer.api.ui.*; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.conversion.support.ProductionConstants; +import forgprod.abilities.interaction.panel.ControlPanelManager; +import forgprod.abilities.interaction.panel.PanelConstants; +import forgprod.abilities.interaction.panel.components.Common; +import forgprod.abilities.interaction.panel.components.tabs.ModulesTab; +import forgprod.abilities.interaction.panel.objects.Button; +import forgprod.abilities.interaction.panel.objects.ExclusiveButton; +import forgprod.abilities.modules.dataholders.ProductionModule; + +/** + * @author Ontheheavens + * @since 01.01.2023 + */ + +public class ModuleList { + + private static CustomPanelAPI listInstance; + + private static boolean listRedrawQueued; + + public static void create(CustomPanelAPI moduleTab) { + float width = 250f; + float listHeight = 478f; + float verticalOffset = PanelConstants.HEADER_HEIGHT + 27f; + TooltipMakerAPI moduleListHeader = createHeader(moduleTab, width, listHeight); + moduleTab.addUIElement(moduleListHeader).inTL(PanelConstants.PANEL_CONTENT_OFFSET + 2f, -verticalOffset); + ModuleList.renderModuleList(); + } + + public static boolean isListRedrawQueued() { + return listRedrawQueued; + } + + public static void setListRedrawQueued(boolean listRedrawQueued) { + ModuleList.listRedrawQueued = listRedrawQueued; + } + + public static void renderModuleList() { + CustomPanelAPI tabInstance = ModulesTab.getTabInstance(); + if (tabInstance == null) return; + if (listInstance != null) { + tabInstance.removeComponent(listInstance); + } + CustomPanelAPI moduleList = createModuleList(tabInstance); + float verticalOffset = PanelConstants.HEADER_HEIGHT + 27f; + tabInstance.addComponent(moduleList).inTL(PanelConstants.PANEL_CONTENT_OFFSET + 2f, -(verticalOffset - 22f)); + ModuleList.listInstance = moduleList; + } + + private static CustomPanelAPI createModuleList(CustomPanelAPI moduleTab) { + float width = 250f; + float listHeight = 478f; + CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); + List modules = ModulesTab.getModulesForList(fleet, true); + CustomPanelAPI listContainer = moduleTab.createCustomPanel(width - 2f, + listHeight, ControlPanelManager.getInstance().getPlugin()); + TooltipMakerAPI moduleList = listContainer.createUIElement(width - 2f, listHeight, true); + moduleList.setForceProcessInput(true); + if (modules.size() == 0) { + addHiddenModulesNote(moduleList, fleet); + } else { + for (ProductionModule module : modules) { + addModuleEntry(moduleList, module, width, module == ModulesTab.getModuleQueuedToShow()); + } + } + float verticalOffsetMultiplier = 110f; + moduleList.addSpacer(-(modules.size() * verticalOffsetMultiplier)); + listContainer.addUIElement(moduleList); + return listContainer; + } + + private static TooltipMakerAPI createHeader(CustomPanelAPI moduleTab, float width, float listHeight) { + TooltipMakerAPI moduleListHeader = moduleTab.createUIElement(width, 20f, false); + moduleListHeader.addSectionHeading("Ships", Alignment.MID, 2f); + moduleListHeader.setForceProcessInput(false); + UIComponentAPI rightBoundaryLine = ModuleList.addScrollerBackgroundLine(moduleListHeader, listHeight - 6f); + rightBoundaryLine.getPosition().inTL(width - 6f, 3f); + rightBoundaryLine.getPosition().setYAlignOffset(-19f); + ModuleList.addSortingButtons(moduleListHeader); + return moduleListHeader; + } + + private static void addSortingButtons(TooltipMakerAPI moduleListHeader) { + ButtonAPI toggleHeavyIndustryButton = createDisplayTagButton(moduleListHeader, + ProductionConstants.HEAVY_INDUSTRY_MODULE); + toggleHeavyIndustryButton.getPosition().inTR(1f, 1f); + ButtonAPI toggleFuelProductionButton = createDisplayTagButton(moduleListHeader, + ProductionConstants.FUEL_PRODUCTION_MODULE); + toggleFuelProductionButton.getPosition().leftOfMid(toggleHeavyIndustryButton, 2f); + ButtonAPI toggleRefiningButton = createDisplayTagButton(moduleListHeader, + ProductionConstants.REFINING_MODULE); + toggleRefiningButton.getPosition().leftOfMid(toggleFuelProductionButton, 2f); + } + + private static ButtonAPI createDisplayTagButton(TooltipMakerAPI moduleListHeader, final String associatedHullmodId) { + Color buttonColor = Misc.interpolateColor(Misc.scaleColorOnly(PanelConstants.DARK_PLAYER_COLOR, 1.2f), + PanelConstants.PLAYER_COLOR, 0.1f); + Color highlightColor = Misc.interpolateColor(Misc.scaleColorOnly(PanelConstants.PLAYER_COLOR, 0.9f), + PanelConstants.DARK_PLAYER_COLOR, 0.1f); + String firstLetter = Character.toString(ProductionConstants.HULLMOD_NAMES.get(associatedHullmodId).charAt(0)); + ButtonAPI tagButtonInstance = moduleListHeader.addButton(firstLetter, null, + highlightColor, buttonColor, Alignment.MID, + CutStyle.ALL, 16, 16f, 0f); + if (ModulesTab.MODULE_DISPLAY_TAGS.get(associatedHullmodId)) { + tagButtonInstance.highlight(); + } + final String moduleName = ProductionConstants.HULLMOD_NAMES.get(associatedHullmodId); + final float nameWidth = moduleListHeader.computeStringWidth(moduleName); + TooltipMakerAPI.TooltipCreator tagButtonTooltip = new BaseTooltipCreator() { + @Override + public float getTooltipWidth(Object tooltipParam) { + return 185f + nameWidth; + } + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { + tooltip.addTitle("Display Options"); + String status = "Shown"; + if (!ModulesTab.MODULE_DISPLAY_TAGS.get(associatedHullmodId)) { + status = "Hidden"; + } + tooltip.addPara("Ships with %s installed: %s", 6f, Misc.getTextColor(), + Misc.getHighlightColor(), moduleName, + status); + } + }; + moduleListHeader.addTooltipToPrevious(tagButtonTooltip, TooltipMakerAPI.TooltipLocation.ABOVE); + new Button(tagButtonInstance, Button.Type.DISPLAY_TAG) { + @Override + public void applyEffect() { + if (!ModulesTab.MODULE_DISPLAY_TAGS.get(associatedHullmodId)) { + ModulesTab.MODULE_DISPLAY_TAGS.put(associatedHullmodId, true); + this.getInner().highlight(); + } else { + ModulesTab.MODULE_DISPLAY_TAGS.put(associatedHullmodId, false); + this.getInner().unhighlight(); + } + ModuleList.setListRedrawQueued(true); + } + }; + return tagButtonInstance; + } + + private static UIComponentAPI addScrollerBackgroundLine(TooltipMakerAPI moduleListHeader, float height) { + TooltipMakerAPI lineContainer = moduleListHeader.beginImageWithText(null, 2f); + Color transparent = Misc.scaleAlpha(PanelConstants.DARK_PLAYER_COLOR, 0f); + Color playerColor = PanelConstants.PLAYER_COLOR; + Color modified = new Color(playerColor.getRed() - 20, playerColor.getGreen() - 10, playerColor.getBlue()); + ButtonAPI scrollerLine = lineContainer.addAreaCheckbox("", null, modified, + transparent, modified, 5f, height + 10f, 0f); + scrollerLine.setHighlightBrightness(1f); + scrollerLine.highlight(); + scrollerLine.getPosition().setXAlignOffset(-240f); + scrollerLine.setEnabled(false); + moduleListHeader.addImageWithText(0f); + return moduleListHeader.getPrev(); + } + + private static void addModuleEntry(TooltipMakerAPI moduleList, final ProductionModule module, + float width, boolean createChecked) { + ButtonAPI frameCheckbox = addCheckbox(moduleList, module, width, createChecked); + frameCheckbox.getPosition().setXAlignOffset(0f); + moduleList.addSpacer(0f); + moduleList.getPrev().getPosition().setXAlignOffset(0f); + UIComponentAPI shipIcon = Common.addShipIcon(moduleList, module, 48f, 2f, false); + shipIcon.getPosition().belowLeft(frameCheckbox, -110f); + addModuleIcon(moduleList, module); + addModuleInfo(moduleList, module); + moduleList.addSpacer(-194f); + } + + private static ButtonAPI addCheckbox(TooltipMakerAPI moduleList, final ProductionModule module, + float width, boolean createChecked) { + Color checkedColor = Misc.scaleColorOnly(PanelConstants.DARK_PLAYER_COLOR, 0.7f); + ButtonAPI checkbox = moduleList.addAreaCheckbox("", null, PanelConstants.PLAYER_COLOR, + checkedColor, PanelConstants.BRIGHT_PLAYER_COLOR, + width - 7f, 120f, 1f); + if (createChecked) { + checkbox.setChecked(true); + checkbox.highlight(); + checkbox.setEnabled(false); + checkbox.setButtonDisabledPressedSound("ui_button_pressed"); + } + new ExclusiveButton(checkbox, Button.Type.MODULE, createChecked) { + @Override + public void applyEffect() { + if (!(ModulesTab.getShownModule() == module) || ModulesTab.getModuleCard() == null) { + ModulesTab.setModuleQueuedToShow(module); + ModulesTab.setCardRedrawQueued(true); + this.getInner().highlight(); + this.getInner().setEnabled(false); + this.getInner().setButtonDisabledPressedSound("ui_button_pressed"); + } + } + }; + return checkbox; + } + + private static void addModuleIcon(TooltipMakerAPI moduleList, ProductionModule module) { + TooltipMakerAPI modulePanel = moduleList.beginImageWithText(null, 32f); + modulePanel.addImage(Global.getSettings().getSpriteName("forgprod_ui", "frame_small"), + 36f, 36f, 20f); + UIComponentAPI moduleIconFrame = modulePanel.getPrev(); + moduleIconFrame.getPosition().setXAlignOffset(-(222f)); + moduleIconFrame.getPosition().setYAlignOffset(45f); + modulePanel.addImage(Global.getSettings().getSpriteName("forgprod_hullmods", module.getHullmodId()), + 32f, 32f, 20f); + float iconOffset = modulePanel.getPrev().getPosition().getY() - moduleIconFrame.getPosition().getY(); + PositionAPI iconPosition = modulePanel.getPrev().getPosition(); + iconPosition.belowMid(moduleIconFrame, iconOffset + 18f); + moduleList.addImageWithText(2f); + } + + private static void addModuleInfo(TooltipMakerAPI moduleList, ProductionModule module) { + TooltipMakerAPI moduleInfoPanel = moduleList.beginImageWithText(null, 64f); + FleetMemberAPI member = module.getParentFleetMember(); + moduleInfoPanel.setTextWidthOverride(200f); + String shortenedShipName = moduleInfoPanel.shortenString(member.getShipName(), 175); + moduleInfoPanel.setParaFont("graphics/fonts/orbitron12condensed.fnt"); + LabelAPI shipNameLabel = moduleInfoPanel.addPara(shortenedShipName, PanelConstants.PLAYER_COLOR, 2f); + moduleInfoPanel.setParaFontDefault(); + shipNameLabel.getPosition().setXAlignOffset(-175f); + shipNameLabel.getPosition().setYAlignOffset(216f); + moduleInfoPanel.addPara(member.getHullSpec().getHullNameWithDashClass(), 2f); + moduleInfoPanel.addPara(member.getVariant().getDesignation(), 2f); + String moduleName = ProductionConstants.HULLMOD_NAMES.get(module.getHullmodId()); + moduleInfoPanel.addSpacer(20f); + moduleInfoPanel.addPara(moduleName, Misc.getHighlightColor(), 2f); + moduleList.addImageWithText(2f); + } + + private static void addHiddenModulesNote(TooltipMakerAPI moduleList, CampaignFleetAPI fleet) { + moduleList.setParaFont("graphics/fonts/insignia21LTaa.fnt"); + moduleList.setTextWidthOverride(210f); + LabelAPI noEligibleNote = moduleList.addPara("No module meets display criteria.", Misc.getGrayColor(), 0f); + noEligibleNote.getPosition().inMid(); + noEligibleNote.setAlignment(Alignment.MID); + noEligibleNote.getPosition().setYAlignOffset(-210f); + int totalModules = ModulesTab.getModulesForList(fleet, false).size(); + LabelAPI hiddenCountNote = moduleList.addPara("(" + totalModules + " modules hidden)", Misc.getGrayColor(), 0f); + hiddenCountNote.getPosition().belowLeft((UIComponentAPI) noEligibleNote, 4f); + hiddenCountNote.getPosition().setXAlignOffset(22f); + moduleList.setParaFontDefault(); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleTooltip.java b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleTooltip.java new file mode 100644 index 0000000..fdfdc84 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/components/tabs/modules/ModuleTooltip.java @@ -0,0 +1,46 @@ +package forgprod.abilities.interaction.panel.components.tabs.modules; + +import java.awt.*; + +import com.fs.starfarer.api.ui.TooltipMakerAPI; +import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipCreator; +import com.fs.starfarer.api.util.Misc; + +import forgprod.abilities.conversion.support.ProductionConstants; +import forgprod.abilities.interaction.panel.PanelConstants; +import forgprod.abilities.modules.dataholders.ProductionModule; + +/** + * @author Ontheheavens + * @since 03.01.2023 + */ + +public class ModuleTooltip implements TooltipCreator { + + private final ProductionModule module; + + public ModuleTooltip(ProductionModule module) { + this.module = module; + } + + @Override + public boolean isTooltipExpandable(Object tooltipParam) { + return false; + } + + @Override + public float getTooltipWidth(Object tooltipParam) { + return 220; + } + + @Override + public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { + tooltip.addTitle(ProductionConstants.HULLMOD_NAMES.get(module.getHullmodId()), PanelConstants.PLAYER_COLOR); + Color designType = new Color(155, 155, 155, 255); + String design = "Domain Restricted"; + tooltip.addPara("Design type: " + design, 10f, Misc.getGrayColor(), designType, design); + String description = ProductionConstants.HULLMOD_DESCRIPTIONS.get(module.getHullmodId()); + tooltip.addPara(description, 10f); + } + +} diff --git a/source/forgprod/abilities/interaction/panel/listeners/ButtonListener.java b/source/forgprod/abilities/interaction/panel/listeners/ButtonListener.java new file mode 100644 index 0000000..6e20ad6 --- /dev/null +++ b/source/forgprod/abilities/interaction/panel/listeners/ButtonListener.java @@ -0,0 +1,37 @@ +package forgprod.abilities.interaction.panel.listeners; + +import java.util.HashSet; +import java.util.Set; + +import forgprod.abilities.interaction.panel.objects.Button; + +/** + * @author Ontheheavens + * @since 29.12.2022 + */ + +public class ButtonListener { + + private static Set