diff --git a/.github/workflows/blacklist.txt b/.github/workflows/blacklist.txt
index 9eacd848..e66294ce 100644
--- a/.github/workflows/blacklist.txt
+++ b/.github/workflows/blacklist.txt
@@ -8,7 +8,8 @@ DO_NOT_USE
.flac$
banner.png$
.github/
-media/
+^media/
^release_checklist.md$
-docs/
-dokka/
\ No newline at end of file
+^docs/
+^dokka/
+^lib/
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..ae7499cc
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "CommunityApiDocs"]
+ path = CommunityApiDocs
+ url = git@github.com:StarsectorCommunityApiDocs/CommunityApiDocs.git
diff --git a/.idea/artifacts/MagicLib.xml b/.idea/artifacts/MagicLib.xml
index a53a9d44..f90a74ff 100644
--- a/.idea/artifacts/MagicLib.xml
+++ b/.idea/artifacts/MagicLib.xml
@@ -2,8 +2,8 @@
$PROJECT_DIR$/jars
-
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..cf247ce4
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index d7333dff..0773fa01 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,3 +1,4 @@
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 35eb1ddf..63e6ac6b 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/.run/Run Starsector.run.xml b/.run/Run Starsector.run.xml
index 69337b6c..bf77367a 100644
--- a/.run/Run Starsector.run.xml
+++ b/.run/Run Starsector.run.xml
@@ -1,11 +1,11 @@
-
+
-
+
\ No newline at end of file
diff --git a/data/campaign/rules.csv b/data/campaign/rules.csv
index 7b90ebbb..4d0ce7e1 100644
--- a/data/campaign/rules.csv
+++ b/data/campaign/rules.csv
@@ -1,9 +1,13 @@
id,trigger,conditions,script,text,options,notes
-"#RULESET_NAME MagicLib (bounties)",,,,,,
-magiclib_bountyboard_addevent,AddBarEvents,"ShouldShowMagicBountyBoard","AddBarEvent magiclib_bountyboard_selected ""Connect to the local unsanctioned bounty board."" ""A subroutine from your implant informs you that this establishment is broadcasting an informal job board.""",,,
-magiclib_bountyboard_selected,DialogOptionSelected,"$option == magiclib_bountyboard_selected","ShowMagicBountyBoardCmd",,,
+#RULESET_NAME MagicLib (bounties),,,,,,
+magiclib_bountyboard_addevent,AddBarEvents,ShouldShowMagicBountyBoard,"AddBarEvent magiclib_bountyboard_selected ""Connect to the local unsanctioned bounty board."" ""A subroutine from your implant informs you that this establishment is broadcasting an informal job board.""",,,
+magiclib_bountyboard_selected,DialogOptionSelected,$option == magiclib_bountyboard_selected,ShowMagicBountyBoardCmd,,,
magiclib_bountyCommText,OpenCommLink,"$entity.MagicLib_Bounty_target_hasReply score:1000
!$entity.ignorePlayerCommRequests","$entity.ignorePlayerCommRequests = true
MagicBountyCommsReplyCmd",,cutCommLinkNoText:Continue,
-#magiclib_test_bounty_completed,"magiclib_test_bounty_completed",,"BountyScriptExample",,,
-
+,,,,,,
+magiclib_fleetencounter_shiny,BeginFleetEncounter,DoesFleetHaveShiny score:9999998,"FireBest BeginFleetEncounter
+AddText ""One of the ships displayed on sensors appears to have a non-standard paintjob.""
+Highlight ""paintjob""",,,
+,,,,,,
+#magiclib_test_bounty_completed,magiclib_test_bounty_completed,,BountyScriptExample,,,
diff --git a/data/config/LunaSettings.csv b/data/config/LunaSettings.csv
index a022aa50..72b9c32c 100644
--- a/data/config/LunaSettings.csv
+++ b/data/config/LunaSettings.csv
@@ -17,8 +17,8 @@ magiclib_subsystems_InfoKeyBind,Info Keybind,Keycode,23,,"Keybind for viewing su
magiclib_subsystems_KeyBindText,Description,Text,"Keybinds for subsystems. They are used in order. If any are empty they will be ignored and the next available keybind will be used.",,,,,
# 56 = left alt
magiclib_subsystems_KeyBind1,Keybind 1,Keycode,56,,"First assigned keybind",,,
-# 48 = KEY_B
-magiclib_subsystems_KeyBind2,Keybind 2,Keycode,48,,"Second assigned keybind",,,
+# 21 = KEY_Y
+magiclib_subsystems_KeyBind2,Keybind 2,Keycode,21,,"Second assigned keybind",,,
# 35 = KEY_H
magiclib_subsystems_KeyBind3,Keybind 3,Keycode,35,,"Third assigned keybind",,,
# 36 = KEY_J
diff --git a/data/config/magic_achievements.csv b/data/config/magic_achievements.csv
index fb1fc05f..c55fcb86 100644
--- a/data/config/magic_achievements.csv
+++ b/data/config/magic_achievements.csv
@@ -4,7 +4,7 @@ id,name,description,tooltip,script,image,spoilerLevel,rarity,
# description: Required. The description of the achievement. Use past tense ("Killed 10 ships"). Aim for one or two sentences.
# tooltip: Optional. A hardcoded tooltip to display when the player hovers over the achievement in the achievement list,
# for example for any detailed explanation that doesn't fit in the description.
-# script: Required. The fully qualified name of the script (Java/Kotlin) class that implements the achievement. Must be a subclass of MagicAchievement.
+# script: Optional. The fully qualified name of the script (Java/Kotlin) class that implements the achievement. Must be a subclass of MagicAchievement. If not provided, achievement should be unlocked by your code somewhere else.
# image: Optional. The path to the image to display for the achievement once unlocked. If not specified, the image will be based on rarity.
# spoilerLevel: Optional. Think about how the visibility may incentivize the player's behavior.
# VISIBLE (default): The achievement is visible to the player at all times, and its progress is shown.
@@ -18,3 +18,4 @@ id,name,description,tooltip,script,image,spoilerLevel,rarity,
# Examples
#magiclib_exampleSimple,"Example Regular Achievement","Created your first achievement.",,org.magiclib.achievements.ExampleSimpleAchievement,,VISIBLE,UNCOMMON
#magiclib_exampleCustom,"Example Customized Achievement","Created your first achievement with a custom icon and tooltip.","Optional tooltip. To learn how to create an achievement, read the documentation!",org.magiclib.achievements.ExampleCustomAchievement,"/graphics/magic/icons/example_achievement.png",SPOILER,RARE
+#magiclib_exampleNoScript,"Example Scriptless Achievement","Created your first achievement without a script.","Optional tooltip. To learn how to create an achievement, read the documentation!",,"/graphics/magic/icons/example_achievement.png",SPOILER,RARE
\ No newline at end of file
diff --git a/data/config/modFiles/magicBounty_data.json b/data/config/modFiles/magicBounty_data.json
index 7694ba76..ea372ac8 100644
--- a/data/config/modFiles/magicBounty_data.json
+++ b/data/config/modFiles/magicBounty_data.json
@@ -74,6 +74,7 @@
"fleet_flagship_alwaysRecoverable":false,
"fleet_preset_ships":{
"tempest_Attack":2,
+ #"asdf":1,
#"shade_Assault":1,
#"omen_PD":2,
"revenant_Elite":1,
diff --git a/data/config/settings.json b/data/config/settings.json
index 0d0d9942..72197e91 100644
--- a/data/config/settings.json
+++ b/data/config/settings.json
@@ -59,6 +59,7 @@
}
},
"ruleCommandPackages": [
- "org.magiclib.bounty.rulecmd"
+ "org.magiclib.bounty.rulecmd",
+ "org.magiclib.paintjobs.rulecmd"
]
}
\ No newline at end of file
diff --git a/data/console/commands.csv b/data/console/commands.csv
index 52f1c4cb..3d8776ea 100644
--- a/data/console/commands.csv
+++ b/data/console/commands.csv
@@ -2,4 +2,5 @@ command,class,tags,syntax,help
MagicLib_ListBounties,org.magiclib.bounty.console.ListBountiesCommand,"MagicLib,campaign",MagicLib_ListBounties [optionalBountyId],Lists bounties loaded on MagicLib's Bounty Board.
MagicLib_ListRequirements,org.magiclib.bounty.console.ListBountiesRequirementsCommand,"MagicLib,campaign",MagicLib_ListRequirements [optionalBountyId],Lists the currently active bounties loaded on MagicLib's Bounty Board. and their requirements to show up.
MagicLib_ResetBounty,org.magiclib.bounty.console.ResetBountyCommand,"MagicLib,campaign",MagicLib_ResetBounty ,Resets a MagicLib bounty.
+MagicLib_ListPaintjobs,org.magiclib.paintjobs.console.ListPaintjobsCommand,"MagicLib,campaign",ListPaintjobsCommand,"List all paintjobs."
MagicLib_UnlockPaintjob,org.magiclib.paintjobs.console.UnlockPaintjobCommand,"MagicLib,campaign",MagicLib_UnlockPaintjob [all|],"Use 'all' to unlock all paintjobs permanently, or specify an id. Run with no arguments to list all."
diff --git a/dokka/dokka-base-1.9.20.jar b/dokka/dokka-base-1.9.20.jar
new file mode 100644
index 00000000..870d5143
Binary files /dev/null and b/dokka/dokka-base-1.9.20.jar differ
diff --git a/dokka/dokka-cli-1.9.20.jar b/dokka/dokka-cli-1.9.20.jar
new file mode 100644
index 00000000..c1a8b83b
Binary files /dev/null and b/dokka/dokka-cli-1.9.20.jar differ
diff --git a/jars/MagicLib-Kotlin.jar b/jars/MagicLib-Kotlin.jar
index 853b61b1..34711cf0 100644
Binary files a/jars/MagicLib-Kotlin.jar and b/jars/MagicLib-Kotlin.jar differ
diff --git a/jars/MagicLib.jar b/jars/MagicLib.jar
index 8b5cec99..49889cab 100644
Binary files a/jars/MagicLib.jar and b/jars/MagicLib.jar differ
diff --git a/lib/hotswap-agent.jar b/lib/hotswap-agent.jar
new file mode 100644
index 00000000..70408e53
Binary files /dev/null and b/lib/hotswap-agent.jar differ
diff --git a/magiclib.version b/magiclib.version
index c2ba6c43..e8c02dbe 100644
--- a/magiclib.version
+++ b/magiclib.version
@@ -8,6 +8,6 @@
{
"major": "1",
"minor": "5",
- "patch": "4-RC01"
+ "patch": "7"
}
}
diff --git a/mod_info.json b/mod_info.json
index dd39f582..c2714ba9 100644
--- a/mod_info.json
+++ b/mod_info.json
@@ -4,7 +4,7 @@
"author": "Modding Community: Dark.Revenant, LazyWizard, Nicke, Originem, Rubi, Schaf-Unschaf, Snrasha, Tartiflette, Wisp, Wyvern...",
"utility": "false",
# Using the long version format lets the game correctly compare major/minor/patch versions.
- "version": { "major": '1', "minor": '5', "patch": '4' },
+ "version": { "major": '1', "minor": '5', "patch": '7' },
"description": "A collection of classes to aid modding. Not a mod in itself, but required by other mods.",
"gameVersion": "0.98a-RC7",
"dependencies": [
diff --git a/src/org/magiclib/achievements/MagicAchievement.java b/src/org/magiclib/achievements/MagicAchievement.java
index 73a42b1c..3e8adfb8 100644
--- a/src/org/magiclib/achievements/MagicAchievement.java
+++ b/src/org/magiclib/achievements/MagicAchievement.java
@@ -215,11 +215,11 @@ public void advanceInCombat(float amount, List events, boolean is
*/
public void saveChanges() {
getLogger().info("Saving achievements triggered by '" + spec.getId() + "' from mod '" + spec.getModName() + "'.");
- MagicAchievementManager.getInstance().saveAchievements(true);
+ MagicAchievementManager.getInstance().saveAchievements(true, false);
}
private void saveChangesWithoutLogging() {
- MagicAchievementManager.getInstance().saveAchievements(false);
+ MagicAchievementManager.getInstance().saveAchievements(false, false);
}
/**
diff --git a/src/org/magiclib/achievements/MagicAchievementManager.java b/src/org/magiclib/achievements/MagicAchievementManager.java
index fd2b65f3..0032e20d 100644
--- a/src/org/magiclib/achievements/MagicAchievementManager.java
+++ b/src/org/magiclib/achievements/MagicAchievementManager.java
@@ -150,7 +150,7 @@ public void setAchievementsEnabled(boolean areAchievementsEnabled, boolean isSav
} else {
logger.info("MagicLib achievements are disabled.");
removeIntel();
- saveAchievements(true);
+ saveAchievements(true, false);
if (isSaveLoaded) {
Global.getSector().removeTransientScriptsOfClass(MagicAchievementRunner.class);
@@ -221,13 +221,13 @@ public Map getAchievements() {
/**
* This writes to disk.
*/
- protected void saveAchievements(boolean printUnchangedResultToLog) {
+ protected void saveAchievements(boolean printUnchangedResultToLog, boolean forceSave) {
JSONObject commonJson;
JSONArray savedAchievements = new JSONArray();
// Prevents accidentally wiping achievements if the feature is disabled on game load.
// Also, no reason to save nothing anyway.
- if (achievements.isEmpty()) {
+ if (!forceSave && achievements.isEmpty()) {
return;
}
@@ -246,7 +246,7 @@ protected void saveAchievements(boolean printUnchangedResultToLog) {
try {
newAchievementsJsonString = savedAchievements.toString(indent);
- if (newAchievementsJsonString.equals(lastSavedJson)) {
+ if (!forceSave && newAchievementsJsonString.equals(lastSavedJson)) {
if (printUnchangedResultToLog) {
logger.info("Not saving achievements because they haven't changed.");
}
@@ -303,7 +303,7 @@ public void reloadAchievements(boolean isSaveGameLoaded) {
try {
// Create file if it doesn't exist.
if (!Global.getSettings().fileExistsInCommon(commonFilename)) {
- saveAchievements(true);
+ saveAchievements(true, true);
}
try {
@@ -314,7 +314,7 @@ public void reloadAchievements(boolean isSaveGameLoaded) {
logger.warn("Unable to load achievements from " + commonFilename + ", making a backup and remaking it.", ex);
Global.getSettings().writeTextFileToCommon(commonFilename + ".backup", Global.getSettings().readTextFileFromCommon(commonFilename));
Global.getSettings().deleteTextFileFromCommon(commonFilename);
- saveAchievements(true);
+ saveAchievements(true, true);
commonJson = JSONUtils.loadCommonJSON(commonFilename);
savedAchievementsJson = commonJson.getJSONArray(achievementsJsonObjectKey);
}
@@ -406,8 +406,14 @@ public void reloadAchievements(boolean isSaveGameLoaded) {
Map newAchievementsById = new HashMap<>();
for (MagicAchievementSpec spec : specs.values()) {
+ String script = spec.getScript();
+
+ if (script == null || script.isBlank()) {
+ script = MagicAchievement.class.getCanonicalName();
+ }
+
try {
- final Class> commandClass = Global.getSettings().getScriptClassLoader().loadClass(spec.getScript());
+ final Class> commandClass = Global.getSettings().getScriptClassLoader().loadClass(script);
if (!MagicAchievement.class.isAssignableFrom(commandClass)) {
throw new RuntimeException(String.format("%s does not extend %s", commandClass.getCanonicalName(), MagicAchievement.class.getCanonicalName()));
}
@@ -415,10 +421,10 @@ public void reloadAchievements(boolean isSaveGameLoaded) {
MagicAchievement magicAchievement = (MagicAchievement) commandClass.newInstance();
magicAchievement.spec = spec;
newAchievementsById.put(spec.getId(), magicAchievement);
- logger.info("Loaded achievement " + spec.getId() + " from " + spec.getModId() + " with script " + spec.getScript() + ".");
+ logger.info("Loaded achievement " + spec.getId() + " from " + spec.getModId() + " with script " + script + ".");
} catch (Exception e) {
if (spec != null)
- logger.warn(String.format("Unable to load achievement '%s' because class '%s' didn't load!", spec.getId(), spec.getScript()), e);
+ logger.warn(String.format("Unable to load achievement '%s' because class '%s' didn't load!", spec.getId(), script), e);
else
logger.warn("Unable to load achievement because spec was null! What are you doing?!", e);
}
@@ -452,6 +458,9 @@ public void reloadAchievements(boolean isSaveGameLoaded) {
try {
JSONObject item = modCsv.getJSONObject(i);
id = item.getString("id").trim();
+
+ if (id.isBlank()) continue;
+
String name = item.getString("name").trim();
String description = item.getString("description").trim();
String tooltip = item.getString("tooltip").trim();
diff --git a/src/org/magiclib/achievements/MagicAchievementSpec.kt b/src/org/magiclib/achievements/MagicAchievementSpec.kt
index 3cc19b4f..97480cae 100644
--- a/src/org/magiclib/achievements/MagicAchievementSpec.kt
+++ b/src/org/magiclib/achievements/MagicAchievementSpec.kt
@@ -13,7 +13,7 @@ open class MagicAchievementSpec(
var name: String,
var description: String,
var tooltip: String?,
- var script: String,
+ var script: String?,
var image: String?,
var spoilerLevel: MagicAchievementSpoilerLevel,
var rarity: MagicAchievementRarity,
@@ -42,7 +42,7 @@ open class MagicAchievementSpec(
val name = json.getString("name")
val description = json.getString("description")
val tooltip = json.optString("tooltip", null)
- val script = json.getString("script")
+ val script = json.optString("script", null)
val image = json.optString("image", null)
val spoilerLevel = MagicAchievementSpoilerLevel.valueOf(
json.optString("spoilerLevel", "VISIBLE").lowercase().let { Misc.ucFirst(it) })
diff --git a/src/org/magiclib/achievements/MagicTargetListAchievement.java b/src/org/magiclib/achievements/MagicTargetListAchievement.java
index 39966828..acda7da7 100644
--- a/src/org/magiclib/achievements/MagicTargetListAchievement.java
+++ b/src/org/magiclib/achievements/MagicTargetListAchievement.java
@@ -19,7 +19,7 @@ public class MagicTargetListAchievement extends MagicAchievement {
protected String KEY = "magictargetlist_targets";
public Map getTargets() {
- Object obj = getMemory().get(KEY);
+ Object obj = getAchievementMemory().get(KEY);
if (obj == null) {
return new HashMap<>();
@@ -30,7 +30,7 @@ public Map getTargets() {
JSONObject jsonTargets = (JSONObject) obj;
Map result = new HashMap<>(jsonTargets.length());
- for (Iterator it = jsonTargets.keys(); it.hasNext(); ) {
+ for (var it = jsonTargets.keys(); it.hasNext(); ) {
String key = (String) it.next();
JSONObject jsonData = jsonTargets.getJSONObject(key);
result.put(key, new Data(jsonData.getString(Data.DISPLAY_NAME), jsonData.getBoolean(Data.IS_COMPLETE)));
@@ -85,7 +85,7 @@ public void setTargets(Map targets) {
savedTargets.remove(key);
}
- getMemory().put(KEY, savedTargets);
+ getAchievementMemory().put(KEY, savedTargets);
}
/**
@@ -100,7 +100,7 @@ public void addTarget(String targetKey, String displayName) {
if (savedTargets.containsKey(targetKey)) return;
savedTargets.put(targetKey, new Data(displayName));
- getMemory().put(KEY, savedTargets);
+ getAchievementMemory().put(KEY, savedTargets);
}
/**
@@ -126,7 +126,7 @@ public void setTargetComplete(String targetKey, boolean isComplete) {
savedTargets.put(targetKey, new Data(targetKey, isComplete));
}
- getMemory().put(KEY, savedTargets);
+ getAchievementMemory().put(KEY, savedTargets);
}
@Override
@@ -138,7 +138,7 @@ public void advanceAfterInterval(float amount) {
return;
// If all targets are complete, complete the achievement.
- if (shouldComplete(targets)) return;
+ if (!shouldComplete(targets)) return;
completeAchievement();
saveChanges();
@@ -150,13 +150,14 @@ public void advanceInCombat(float amount, List events, boolean is
super.advanceInCombat(amount, events, isSimulation);
if (isSimulation) return;
+ if (isComplete()) return;
Map targets = getTargets();
if (targets.isEmpty())
return;
- if (shouldComplete(targets)) return;
+ if (!shouldComplete(targets)) return;
completeAchievement();
saveChanges();
@@ -181,10 +182,10 @@ public boolean shouldComplete(Map targets) {
for (Data data : targets.values()) {
if (!data.isComplete) {
- return true;
+ return false;
}
}
- return false;
+ return true;
}
@Override
@@ -214,12 +215,7 @@ public void createTooltip(@NotNull TooltipMakerAPI tooltipMakerAPI, boolean isEx
tooltipMakerAPI.setBulletedListMode(" - ");
List values = new ArrayList<>(getTargets().values());
- Collections.sort(values, new Comparator() {
- @Override
- public int compare(Data o1, Data o2) {
- return o1.displayName.compareTo(o2.displayName);
- }
- });
+ values.sort(Comparator.comparing(o -> o.displayName));
for (Data data : values) {
tooltipMakerAPI.addPara(data.displayName, data.isComplete ? Misc.getTextColor() : Misc.getNegativeHighlightColor(), 0f);
diff --git a/src/org/magiclib/achievements/ShipKillsAchievement.kt b/src/org/magiclib/achievements/ShipKillsAchievement.kt
index 4b3f7732..fce6d2c6 100644
--- a/src/org/magiclib/achievements/ShipKillsAchievement.kt
+++ b/src/org/magiclib/achievements/ShipKillsAchievement.kt
@@ -8,6 +8,7 @@ import com.fs.starfarer.api.combat.listeners.ApplyDamageResultAPI
import com.fs.starfarer.api.combat.listeners.DamageListener
import com.fs.starfarer.api.input.InputEventAPI
import com.fs.starfarer.api.mission.FleetSide
+import org.magiclib.kotlin.magicJoinToString
import org.magiclib.paintjobs.MagicPaintjobManager
/**
@@ -27,10 +28,13 @@ import org.magiclib.paintjobs.MagicPaintjobManager
* @property killCount Number of ships the player must kill to complete this achievement.
* @property rewardedPaintjobIds Paintjob IDs to unlock when this achievement is completed.
*/
-abstract class ShipKillsAchievement(
+
+abstract class ShipKillsAchievement @JvmOverloads constructor(
val playerShipHullIds: List? = null,
val killCount: Float,
- val rewardedPaintjobIds: List
+ val rewardedPaintjobIds: List = emptyList(),
+ var generateDescription: Boolean = true,
+ var generateTooltip: Boolean = true,
) : MagicAchievement() {
companion object {
val shipIdToListener: MutableMap =
@@ -80,6 +84,51 @@ abstract class ShipKillsAchievement(
applyShipListeners(ships)
}
+ override fun getDescription(): String {
+ if (generateDescription) {
+ val sb = StringBuilder("Destroy ${killCount.toInt()} enemies")
+
+ if (playerShipHullIds?.isNotEmpty() == true) {
+ sb.append(
+ " using a ${
+ playerShipHullIds.magicJoinToString {
+ runCatching {
+ Global.getSettings().getHullSpec(it).hullName
+ }.getOrDefault(it)
+ }
+ }"
+ )
+ }
+
+ sb.append(".")
+ return sb.toString()
+ } else {
+ return super.getDescription()
+ }
+ }
+
+ override fun getTooltip(): String? {
+ return if (generateTooltip && rewardedPaintjobIds.isNotEmpty()) {
+ buildString {
+ append("Awards the ")
+ append(
+ rewardedPaintjobIds.magicJoinToString(
+ twoElementSeparator = " and ",
+ manyElementsFinalSeparator = ", and "
+ ) {
+ runCatching {
+ MagicPaintjobManager.getPaintjob(it)!!.name
+ }.getOrDefault(it)
+ })
+ append(" paintjob")
+ if (rewardedPaintjobIds.size > 1) append("s")
+ append(" when completed.")
+ }
+ } else {
+ super.getTooltip()
+ }
+ }
+
override fun onDestroyed() {
super.onDestroyed()
shipIdToListener.clear()
diff --git a/src/org/magiclib/bounty/ActiveBounty.java b/src/org/magiclib/bounty/ActiveBounty.java
index 19eabe19..4e54b559 100644
--- a/src/org/magiclib/bounty/ActiveBounty.java
+++ b/src/org/magiclib/bounty/ActiveBounty.java
@@ -195,6 +195,7 @@ public void acceptBounty(@NotNull SectorEntityToken bountySource, @Nullable Floa
if (intelForBounty == null) {
intelForBounty = new MagicBountyIntel(bountyKey);
intelManager.addIntel(intelForBounty);
+ intelForBounty.setImportant(true);
}
if (MagicTxt.nullStringIfEmpty(spec.job_memKey) != null) {
diff --git a/src/org/magiclib/bounty/MagicBountyBattleListener.java b/src/org/magiclib/bounty/MagicBountyBattleListener.java
index 9fd9af5f..ae8382b6 100644
--- a/src/org/magiclib/bounty/MagicBountyBattleListener.java
+++ b/src/org/magiclib/bounty/MagicBountyBattleListener.java
@@ -10,7 +10,9 @@
import com.fs.starfarer.api.impl.campaign.fleets.AutoDespawnScript;
import org.jetbrains.annotations.NotNull;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Ends bounties based on battle results.
@@ -24,31 +26,68 @@ public final class MagicBountyBattleListener implements FleetEventListener {
private boolean isDone = false;
@NotNull
- private final String bountyKey;
+ private Set bountyKeys;
+ @Deprecated
+ private String bountyKey;
+
+ public MagicBountyBattleListener(@NotNull Set bountyKeys) {
+ this.bountyKeys = bountyKeys;
+ }
+
+ /**
+ * Use `MagicBountyBattleListener(Set bountyKeys)` instead.
+ */
+ @Deprecated
public MagicBountyBattleListener(@NotNull String bountyKey) {
- this.bountyKey = bountyKey;
+ this.bountyKeys = new HashSet<>();
+ this.bountyKeys.add(bountyKey);
}
+ /**
+ * If a key is already present, does nothing.
+ */
+ public void addBountyKey(@NotNull String bountyKey) {
+ handleBackwardsCompat();
+ this.bountyKeys.add(bountyKey);
+ }
+
+ /**
+ * If the fleet has despawned, end all bounties on the fleet as [EndedWithoutPlayerInvolvement].
+ */
@Override
public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, CampaignEventListener.FleetDespawnReason reason, Object param) {
if (isDone) {
return;
}
+ // Backwards compatibility.
+ handleBackwardsCompat();
+
fleet.removeEventListener(this);
- ActiveBounty bounty = MagicBountyCoordinator.getInstance().getActiveBounty(bountyKey);
+ ActiveBounty firstAcceptedBountyOnFleet = null;
- if (bounty == null) return;
+ for (String key : bountyKeys) {
+ firstAcceptedBountyOnFleet = MagicBountyCoordinator.getInstance().getActiveBounty(key);
+ if (firstAcceptedBountyOnFleet != null && firstAcceptedBountyOnFleet.getStage() == ActiveBounty.Stage.Accepted) {
+ break;
+ }
+ }
+
+ if (firstAcceptedBountyOnFleet == null) return;
- if (fleet.getId().equals(bounty.getFleet().getId())) {
+ if (fleet.getId().equals(firstAcceptedBountyOnFleet.getFleet().getId())) {
fleet.setCommander(fleet.getFaction().createRandomPerson());
- bounty.endBounty(new ActiveBounty.BountyResult.EndedWithoutPlayerInvolvement());
- // Quietly despawn the fleet when player goes away, since they can't complete the bounty.
+ for (String key : bountyKeys) {
+ ActiveBounty bounty = MagicBountyCoordinator.getInstance().getActiveBounty(key);
+ if (bounty != null) {
+ bounty.endBounty(new ActiveBounty.BountyResult.EndedWithoutPlayerInvolvement());
+ }
+ }
+
Global.getSector().addScript(new AutoDespawnScript(fleet));
- return;
}
}
@@ -59,24 +98,39 @@ public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, CampaignEvent
*/
@Override
public void reportBattleOccurred(CampaignFleetAPI bountyFleet, CampaignFleetAPI winningFleet, BattleAPI battle) {
- ActiveBounty bounty = MagicBountyCoordinator.getInstance().getActiveBounty(bountyKey);
+ handleBackwardsCompat();
+
+ ActiveBounty firstAcceptedBountyOnFleet = null;
+
+ for (String key : bountyKeys) {
+ firstAcceptedBountyOnFleet = MagicBountyCoordinator.getInstance().getActiveBounty(key);
+ if (firstAcceptedBountyOnFleet != null && firstAcceptedBountyOnFleet.getStage() == ActiveBounty.Stage.Accepted) {
+ break;
+ }
+ }
+
CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
- if (bounty == null) return;
+ if (firstAcceptedBountyOnFleet == null) return;
/////////// Below is copied (and heavily modified) from PersonBountyIntel.reportBattleOccurred.
if (isDone) return;
boolean playerInvolved = battle.isPlayerInvolved();
- if (bountyFleet.getId().equals(bounty.getFleet().getId())) {
- PersonAPI bountyCommander = bounty.getCaptain();
+ if (bountyFleet.getId().equals(firstAcceptedBountyOnFleet.getFleet().getId())) {
+ PersonAPI bountyCommander = firstAcceptedBountyOnFleet.getCaptain();
if (battle.isInvolved(bountyFleet) && !playerInvolved) {
if (bountyFleet.getFlagship() == null || bountyFleet.getFlagship().getCaptain() != bountyCommander) {
bountyFleet.setCommander(bountyFleet.getFaction().createRandomPerson());
//Global.getSector().reportEventStage(this, "other_end", market.getPrimaryEntity(), messagePriority);
- bounty.endBounty(new ActiveBounty.BountyResult.EndedWithoutPlayerInvolvement());
+ for (String key : bountyKeys) {
+ ActiveBounty bounty = MagicBountyCoordinator.getInstance().getActiveBounty(key);
+ if (bounty != null) {
+ bounty.endBounty(new ActiveBounty.BountyResult.EndedWithoutPlayerInvolvement());
+ }
+ }
// Quietly despawn the fleet when player goes away, since they can't complete the bounty.
Global.getSector().addScript(new AutoDespawnScript(bountyFleet));
// result = new PersonBountyIntel.BountyResult(PersonBountyIntel.BountyResultType.END_OTHER, 0, null);
@@ -94,12 +148,12 @@ public void reportBattleOccurred(CampaignFleetAPI bountyFleet, CampaignFleetAPI
boolean didPlayerSalvageFlagship = false;
List bountyFleetBeforeBattle = bountyFleet.getFleetData().getSnapshot();
- if (bounty.getFlagshipId() != null) {
+ if (firstAcceptedBountyOnFleet.getFlagshipId() != null) {
for (FleetMemberAPI fleetMember : playerFleet.getFleetData().getMembersListCopy()) {
for (FleetMemberAPI ship : bountyFleetBeforeBattle) {
// Look for the flagship of the bounty fleet's presence in the player fleet.
- if (fleetMember.getId().equals(bounty.getFlagshipId()) && fleetMember.getId().equals(ship.getId())) {
+ if (fleetMember.getId().equals(firstAcceptedBountyOnFleet.getFlagshipId()) && fleetMember.getId().equals(ship.getId())) {
Global.getLogger(MagicBountyBattleListener.class).info(String.format("Player salvaged flagship %s (%s)", ship.getShipName(), ship.getId()));
didPlayerSalvageFlagship = true;
}
@@ -107,45 +161,87 @@ public void reportBattleOccurred(CampaignFleetAPI bountyFleet, CampaignFleetAPI
}
}
- switch (bounty.getSpec().job_type) {
- case Assassination:
- if (didDisableOrDestroyOriginalFlagship) {
- bounty.endBounty(new ActiveBounty.BountyResult.Succeeded(true));
- isDone = true;
- }
+ for (String key : bountyKeys) {
+ ActiveBounty bounty = MagicBountyCoordinator.getInstance().getActiveBounty(key);
+ if (bounty == null) {
+ continue;
+ }
+
+ // Skip bounties that have already been completed (not in "ready to accept" or "accepted" stages)
+ if (bounty.getStage() != ActiveBounty.Stage.NotAccepted && bounty.getStage() != ActiveBounty.Stage.Accepted) {
+ continue;
+ }
- break;
- case Destruction:
- if (didDisableOrDestroyOriginalFlagship)
- if (!didPlayerSalvageFlagship) {
+ // Go through each bounty's win conditions and end the bounty if met.
+ switch (bounty.getSpec().job_type) {
+ case Assassination:
+ if (didDisableOrDestroyOriginalFlagship) {
bounty.endBounty(new ActiveBounty.BountyResult.Succeeded(true));
- isDone = true;
- } else {
- // If the bounty required destroying the target, but player salvaged their ship, they don't get credits.
- bounty.endBounty(new ActiveBounty.BountyResult.FailedSalvagedFlagship());
- isDone = true;
}
- break;
- case Obliteration:
- if (bountyFleet.getFleetSizeCount() <= 0) {
- bounty.endBounty(new ActiveBounty.BountyResult.Succeeded(true));
- isDone = true;
- }
+ break;
+ case Destruction:
+ if (didDisableOrDestroyOriginalFlagship)
+ if (!didPlayerSalvageFlagship) {
+ bounty.endBounty(new ActiveBounty.BountyResult.Succeeded(true));
+ } else {
+ // If the bounty required destroying the target, but player salvaged their ship, they don't get credits.
+ bounty.endBounty(new ActiveBounty.BountyResult.FailedSalvagedFlagship());
+ }
+
+ break;
+ case Obliteration:
+ if (bountyFleet.getFleetSizeCount() <= 0) {
+ bounty.endBounty(new ActiveBounty.BountyResult.Succeeded(true));
+ }
- break;
- case Neutralization:
- case Neutralisation:
- float fpPostFight = bountyFleet.getFleetPoints();
+ break;
+ case Neutralization:
+ case Neutralisation:
+ float fpPostFight = bountyFleet.getFleetPoints();
+
+ if ((fpPostFight / bounty.getInitialBountyFleetPoints()) <= (1f / 3f)) {
+ bounty.endBounty(new ActiveBounty.BountyResult.Succeeded(true));
+ }
- if ((fpPostFight / bounty.getInitialBountyFleetPoints()) <= (1f / 3f)) {
- bounty.endBounty(new ActiveBounty.BountyResult.Succeeded(true));
- isDone = true;
+ break;
+ }
+ }
+
+ if (bountyFleet.getFleetSizeCount() <= 0) {
+ // Fleet is totally dead, player is not gonna be starting another battle to finish another bounty on this fleet, so cancel out the listener.
+ isDone = true;
+ } else {
+ // Handle the case where a battle occurred but player didn't complete all bounties.
+ // We keep the BattleListener running until all bounties on the fleet have been completed (or the fleet is defeated, in reportFleetDespawnedToListener).
+ boolean hasUnfinishedBounty = false;
+
+ for (String key : bountyKeys) {
+ ActiveBounty bounty = MagicBountyCoordinator.getInstance().getActiveBounty(key);
+ if (bounty != null) {
+ if (bounty.getStage() == ActiveBounty.Stage.NotAccepted || bounty.getStage() == ActiveBounty.Stage.Accepted) {
+ hasUnfinishedBounty = true;
+ }
}
+ }
- break;
+ if (!hasUnfinishedBounty) {
+ isDone = true;
+ }
}
+ }
+ }
+
+ /**
+ * If restoring a save from before `bountyKeys` was added, migrate `bountyKey` to `bountyKeys`.
+ */
+ private void handleBackwardsCompat() {
+ if (bountyKeys == null) {
+ bountyKeys = new HashSet<>();
+ }
+ if (bountyKey != null) {
+ bountyKeys.add(bountyKey);
}
}
}
diff --git a/src/org/magiclib/bounty/MagicBountyCoordinator.java b/src/org/magiclib/bounty/MagicBountyCoordinator.java
index d3d853ef..270417f7 100644
--- a/src/org/magiclib/bounty/MagicBountyCoordinator.java
+++ b/src/org/magiclib/bounty/MagicBountyCoordinator.java
@@ -478,6 +478,8 @@ public void configureBountyListeners() {
for (FleetEventListener eventListener : bounty.getFleet().getEventListeners()) {
if (eventListener instanceof MagicBountyBattleListener) {
doesBountyHaveListener = true;
+ // Attempt to add this bounty key if it doesn't already exist, along with the already present one. This lets multiple bounties target the same fleet.
+ ((MagicBountyBattleListener) eventListener).addBountyKey(bounty.getKey());
break;
}
}
diff --git a/src/org/magiclib/bounty/intel/MagicBountyInfo.kt b/src/org/magiclib/bounty/intel/MagicBountyInfo.kt
index 859a63bb..d1d02bb9 100644
--- a/src/org/magiclib/bounty/intel/MagicBountyInfo.kt
+++ b/src/org/magiclib/bounty/intel/MagicBountyInfo.kt
@@ -4,7 +4,6 @@ import com.fs.starfarer.api.Global
import com.fs.starfarer.api.campaign.LocationAPI
import com.fs.starfarer.api.campaign.StarSystemAPI
import com.fs.starfarer.api.campaign.comm.IntelInfoPlugin
-import com.fs.starfarer.api.campaign.rules.MemoryAPI
import com.fs.starfarer.api.fleet.FleetMemberAPI
import com.fs.starfarer.api.impl.campaign.ids.Factions
import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BreadcrumbSpecial
@@ -13,6 +12,7 @@ import com.fs.starfarer.api.ui.LabelAPI
import com.fs.starfarer.api.ui.MapParams
import com.fs.starfarer.api.ui.TooltipMakerAPI
import com.fs.starfarer.api.util.Misc
+import org.lwjgl.input.Keyboard
import org.magiclib.bounty.ActiveBounty
import org.magiclib.bounty.MagicBountyCoordinator
import org.magiclib.bounty.MagicBountyLoader.*
@@ -141,15 +141,19 @@ open class MagicBountyInfo(val bountyKey: String, val bountySpec: MagicBountySpe
}
//CHECK FOR EXISTING FLEET
- if (bountySpec.existing_target_memkey != null) {
- var targetFleetGone = true
+ if (bountySpec.existing_target_memkey != null) { // Bounty has pre-existing target
+ var targetFleetFound = false
for (s in Global.getSector().starSystems) {
for (f in s.fleets) {
if (f.memoryWithoutUpdate.contains(bountySpec.existing_target_memkey)) {
- // The fleet already exists, so don't offer the bounty.
- return false
+ targetFleetFound = true
+ break
}
}
+ if (targetFleetFound) break
+ }
+ if (!targetFleetFound) { // If the bounty was intended to target an existing fleet, and no fleet was found
+ return false // Don't show it, as the target fleet does not exist
}
}
@@ -261,6 +265,7 @@ open class MagicBountyInfo(val bountyKey: String, val bountySpec: MagicBountySpe
if (activeBountyLocal.stage == ActiveBounty.Stage.NotAccepted) {
val acceptButton = actionTooltip.addButton(bountySpec.job_pick_option, null, rightPanelWidth, 24f, 0f)
+ acceptButton.setShortcut(Keyboard.KEY_T, true)
rightPanelPlugin.addButton(acceptButton) {
acceptButton.isChecked = false
activeBountyLocal.let {
diff --git a/src/org/magiclib/paintjobs/MagicPaintjobManager.kt b/src/org/magiclib/paintjobs/MagicPaintjobManager.kt
index 2d2cb209..f6122896 100644
--- a/src/org/magiclib/paintjobs/MagicPaintjobManager.kt
+++ b/src/org/magiclib/paintjobs/MagicPaintjobManager.kt
@@ -11,6 +11,7 @@ import com.fs.starfarer.api.combat.ShipVariantAPI
import com.fs.starfarer.api.combat.WeaponAPI
import com.fs.starfarer.api.fleet.FleetMemberAPI
import com.fs.starfarer.api.graphics.SpriteAPI
+import com.fs.starfarer.api.util.IntervalUtil
import com.fs.starfarer.combat.entities.ship.trackers.MultiBarrelRecoilTracker
import org.dark.shaders.util.ShaderLib
import org.json.JSONArray
@@ -42,6 +43,7 @@ object MagicPaintjobManager {
private val unlockedPaintjobsInner = mutableSetOf()
private val paintjobsInner = mutableListOf()
private val completedPaintjobIdsThatUserHasBeenNotifiedFor = mutableListOf()
+ private val advanceIntervalUtil = IntervalUtil(1f, 1f)
private val weaponPaintjobsInner = mutableListOf()
@@ -116,7 +118,9 @@ object MagicPaintjobManager {
loadUnlockedPaintjobs()
initIntel()
- Global.getSector().addTransientListener(MagicPaintjobShinyAdder())
+ val shinyAdder = MagicPaintjobShinyAdder()
+ shinyAdder.checkAndApplyShiniesToAllFleetsInPlayerLocation()
+ Global.getSector().addTransientScript(shinyAdder)
if (!Global.getSector().hasTransientScript(MagicPaintjobRunner::class.java)) {
Global.getSector().addTransientScript(MagicPaintjobRunner())
}
@@ -331,12 +335,12 @@ object MagicPaintjobManager {
return@forEach
}
- /* TODO: Uncomment this for 0.98
+ /* TODO: Uncomment this for 0.98 */
if (!Global.getSettings().actuallyAllWeaponSpecs.any { it.weaponId in weaponIds }) {
logger.warn("Weapon Paintjob with id: $id has no valid weaponId's, skipping.")
return@forEach
}
- */
+
val spriteMap = weaponPJEntry.getString("spriteMap").split(",").mapNotNull { mapEntry ->
val parts = mapEntry.split("->")
if (parts.size == 2) {
@@ -679,24 +683,29 @@ object MagicPaintjobManager {
@JvmStatic
fun advance(amount: Float) {
if (!isEnabled) return
- val intel = getIntel() ?: return
-
- // For all paintjobs that were just unlocked, show intel update.
- // Only notify intel if in campaign and not showing a dialog.
- // If in combat, the intel will be shown when the player returns to the campaign.
- if (Global.getCurrentState() == GameState.CAMPAIGN && Global.getSector().campaignUI.currentInteractionDialog == null) {
- for (paintjob in getPaintjobs()) {
- if (paintjob.isUnlocked() && !completedPaintjobIdsThatUserHasBeenNotifiedFor.contains(paintjob.id)) {
- // Player has unlocked a new paintjob! Let's notify them.
-
- try {
- intel.tempPaintjobForIntelNotification = paintjob
- intel.sendUpdateIfPlayerHasIntel(null, false, false)
- intel.tempPaintjobForIntelNotification = null
+
+ advanceIntervalUtil.advance(amount)
+
+ if (advanceIntervalUtil.intervalElapsed()) {
+ val intel = getIntel() ?: return
+
+ // For all paintjobs that were just unlocked, show intel update.
+ // Only notify intel if in campaign and not showing a dialog.
+ // If in combat, the intel will be shown when the player returns to the campaign.
+ if (Global.getCurrentState() == GameState.CAMPAIGN && Global.getSector().campaignUI.currentInteractionDialog == null) {
+ for (paintjob in getPaintjobs()) {
+ if (paintjob.isUnlocked() && !completedPaintjobIdsThatUserHasBeenNotifiedFor.contains(paintjob.id)) {
+ // Player has unlocked a new paintjob! Let's notify them.
+
+ try {
+ intel.tempPaintjobForIntelNotification = paintjob
+ intel.sendUpdateIfPlayerHasIntel(null, false, false)
+ intel.tempPaintjobForIntelNotification = null
// MagicAchievementManager.playSoundEffect(paintjob)
- completedPaintjobIdsThatUserHasBeenNotifiedFor.add(paintjob.id)
- } catch (e: java.lang.Exception) {
- logger.w(ex = e, message = { "Unable to notify intel of paintjob " + paintjob.id })
+ completedPaintjobIdsThatUserHasBeenNotifiedFor.add(paintjob.id)
+ } catch (e: java.lang.Exception) {
+ logger.w(ex = e, message = { "Unable to notify intel of paintjob " + paintjob.id })
+ }
}
}
}
diff --git a/src/org/magiclib/paintjobs/MagicPaintjobRefitPanel.kt b/src/org/magiclib/paintjobs/MagicPaintjobRefitPanel.kt
index 1ee0e7f1..3a97af70 100644
--- a/src/org/magiclib/paintjobs/MagicPaintjobRefitPanel.kt
+++ b/src/org/magiclib/paintjobs/MagicPaintjobRefitPanel.kt
@@ -13,17 +13,21 @@ import org.lwjgl.input.Keyboard
import org.lwjgl.opengl.GL11
import org.magiclib.ReflectionUtils
import org.magiclib.internalextensions.*
-import org.magiclib.kotlin.*
-import java.awt.Color
-import org.magiclib.paintjobs.MagicPaintjobSelector.createPaintjobSelector
+import org.magiclib.kotlin.alphaf
+import org.magiclib.kotlin.bluef
+import org.magiclib.kotlin.greenf
+import org.magiclib.kotlin.redf
import org.magiclib.paintjobs.MagicPaintjobSelector.MagicPaintjobSelectorPlugin
+import org.magiclib.paintjobs.MagicPaintjobSelector.createPaintjobSelector
import org.magiclib.util.MagicTxt
+import java.awt.Color
/**
* @author Starficz
*/
internal object MagicPaintjobRefitPanel {
private const val BACKGROUND_ALPHA = 0.7f
+
internal class MagicPaintjobRefitPanelPlugin(private val refitTab: UIPanelAPI) : BaseCustomUIPanelPlugin() {
lateinit var paintjobPanel: CustomPanelAPI
@@ -63,11 +67,11 @@ internal object MagicPaintjobRefitPanel {
GL11.glPopMatrix()
}
- private fun drawBorder(x1: Float, y1: Float, x2: Float, y2: Float){
- GL11.glRectf(x1, y1, x2+1, y1-1)
- GL11.glRectf(x2, y1, x2+1, y2+1)
- GL11.glRectf(x1, y2, x1-1, y1-1)
- GL11.glRectf(x2, y2, x1-1, y2+1)
+ private fun drawBorder(x1: Float, y1: Float, x2: Float, y2: Float) {
+ GL11.glRectf(x1, y1, x2 + 1, y1 - 1)
+ GL11.glRectf(x2, y1, x2 + 1, y2 + 1)
+ GL11.glRectf(x1, y2, x1 - 1, y1 - 1)
+ GL11.glRectf(x2, y2, x1 - 1, y2 + 1)
}
override fun processInput(events: MutableList?) {
@@ -76,7 +80,8 @@ internal object MagicPaintjobRefitPanel {
paintjobPanel.parent?.removeComponent(paintjobPanel)
event.consume()
} else if (!event.isConsumed && (event.isKeyboardEvent || event.isMouseMoveEvent ||
- event.isMouseDownEvent || event.isMouseScrollEvent)) {
+ event.isMouseDownEvent || event.isMouseScrollEvent)
+ ) {
event.consume()
}
}
@@ -86,8 +91,10 @@ internal object MagicPaintjobRefitPanel {
}
}
- internal fun createMagicPaintjobRefitPanel(refitTab: UIPanelAPI, refitPanel : UIPanelAPI,
- width: Float, height: Float): CustomPanelAPI {
+ internal fun createMagicPaintjobRefitPanel(
+ refitTab: UIPanelAPI, refitPanel: UIPanelAPI,
+ width: Float, height: Float
+ ): CustomPanelAPI {
val endPad = 6f
val midPad = 5f
val selectorsPerRow = 3
@@ -97,7 +104,7 @@ internal object MagicPaintjobRefitPanel {
paintjobPlugin.paintjobPanel = paintjobPanel
// borders are drawn outside of panel, so +2 needed to lineup scrollbar with border
- val scrollerTooltip = paintjobPanel.createUIElement(width+2f, height, true)
+ val scrollerTooltip = paintjobPanel.createUIElement(width + 2f, height, true)
scrollerTooltip.position.inTL(0f, 0f)
val shipDisplay = ReflectionUtils.invoke("getShipDisplay", refitPanel) as UIPanelAPI
@@ -105,9 +112,10 @@ internal object MagicPaintjobRefitPanel {
val currentPaintjob = MagicPaintjobManager.getCurrentShipPaintjob(baseVariant)
val baseHullPaintjobs = MagicPaintjobManager.getPaintjobsForHull(
- (baseVariant as ShipVariantAPI).hullSpec, false)
+ (baseVariant as ShipVariantAPI).hullSpec, false
+ )
- val selectorWidth = (paintjobPanel.width-(endPad*2+midPad*(selectorsPerRow-1)))/selectorsPerRow
+ val selectorWidth = (paintjobPanel.width - (endPad * 2 + midPad * (selectorsPerRow - 1))) / selectorsPerRow
var firstInRow: UIPanelAPI? = null
var prev: UIPanelAPI? = null
val selectorPlugins = mutableListOf()
@@ -117,7 +125,7 @@ internal object MagicPaintjobRefitPanel {
val selectorPanel = createPaintjobSelector(baseVariant, paintjobSpec, selectorWidth)
val selectorPlugin = selectorPanel.plugin as MagicPaintjobSelectorPlugin
selectorPlugins.add(selectorPlugin)
- if(currentPaintjob == paintjobSpec) {
+ if (currentPaintjob == paintjobSpec) {
selectorPlugin.isSelected = true
selectorPlugin.highlightFader.forceIn()
}
@@ -127,17 +135,15 @@ internal object MagicPaintjobRefitPanel {
if (prev == null) {
pos.inTL(endPad, endPad)
firstInRow = selectorPanel
- }
- else if(index % selectorsPerRow == 0){
+ } else if (index % selectorsPerRow == 0) {
pos.belowLeft(firstInRow, midPad)
firstInRow = selectorPanel
- }
- else pos.rightOfTop(prev, midPad)
+ } else pos.rightOfTop(prev, midPad)
prev = selectorPanel
}
// add tooltip to locked paintjobs
- if(!selectorPlugin.isUnlocked && !paintjobSpec?.unlockConditions.isNullOrBlank()){
+ if (!selectorPlugin.isUnlocked && !paintjobSpec?.unlockConditions.isNullOrBlank()) {
scrollerTooltip.addTooltip(selectorPanel, TooltipMakerAPI.TooltipLocation.BELOW, 250f) { tooltip ->
tooltip.addTitle(MagicTxt.getString("ml_mp_refit_locked"))
tooltip.addPara(paintjobSpec!!.unlockConditions, 0f)
@@ -147,23 +153,32 @@ internal object MagicPaintjobRefitPanel {
// sync all the selectors, and apply the paintjob
for (selectorPlugin in selectorPlugins) {
+ selectorPlugin.onHoverEnter {
+ Global.getSoundPlayer().playUISound("ui_button_mouseover", 1f, 1f)
+ }
selectorPlugin.onClick {
if (selectorPlugin.isUnlocked || Global.getSettings().isDevMode) {
+ Global.getSoundPlayer().playUISound("ui_button_pressed", 1f, 1f)
selectorPlugins.forEach { it.isSelected = false }
selectorPlugin.isSelected = true
- if(selectorPlugin.paintjobSpec == null) MagicPaintjobManager.removePaintjobFromShip(baseVariant)
- else MagicPaintjobManager.applyPaintjob(baseVariant, selectorPlugin.paintjobSpec)
+ if (selectorPlugin.paintjobSpec == null) {
+ MagicPaintjobManager.removePaintjobFromShip(baseVariant)
+ } else {
+ MagicPaintjobManager.applyPaintjob(baseVariant, selectorPlugin.paintjobSpec)
+ }
baseVariant.moduleVariants?.values?.forEach { moduleVariant ->
- if(selectorPlugin.paintjobSpec == null)
+ if (selectorPlugin.paintjobSpec == null)
MagicPaintjobManager.removePaintjobFromShip(moduleVariant)
- else{
+ else {
val moduleHullID = (moduleVariant as ShipVariantAPI).hullSpec.baseHullId
MagicPaintjobManager.getPaintjobsForHull(moduleHullID).firstOrNull {
it.paintjobFamily == selectorPlugin.paintjobSpec.paintjobFamily
- }?.let { MagicPaintjobManager.applyPaintjob(moduleVariant, it) }
+ }?.let {
+ MagicPaintjobManager.applyPaintjob(moduleVariant, it)
+ }
}
}
ReflectionUtils.invoke("syncWithCurrentVariant", refitPanel)
@@ -174,8 +189,8 @@ internal object MagicPaintjobRefitPanel {
}
// add scroll at end after setting heightSoFar, needed when using addCustom to the tooltip
- val rows = (baseHullPaintjobs.size/selectorsPerRow) + 1
- scrollerTooltip.heightSoFar = endPad*2 + prev!!.height*rows + midPad*(rows-1)
+ val rows = (baseHullPaintjobs.size / selectorsPerRow) + 1
+ scrollerTooltip.heightSoFar = endPad * 2 + prev!!.height * rows + midPad * (rows - 1)
paintjobPanel.addUIElement(scrollerTooltip)
return paintjobPanel
}
diff --git a/src/org/magiclib/paintjobs/MagicPaintjobRefitPanelCreator.kt b/src/org/magiclib/paintjobs/MagicPaintjobRefitPanelCreator.kt
index 711e32a4..e33d2390 100644
--- a/src/org/magiclib/paintjobs/MagicPaintjobRefitPanelCreator.kt
+++ b/src/org/magiclib/paintjobs/MagicPaintjobRefitPanelCreator.kt
@@ -1,15 +1,19 @@
package org.magiclib.paintjobs
+import com.fs.starfarer.api.Global
import com.fs.starfarer.api.fleet.FleetMemberAPI
-import com.fs.starfarer.api.ui.*
+import com.fs.starfarer.api.ui.Alignment
+import com.fs.starfarer.api.ui.ButtonAPI
+import com.fs.starfarer.api.ui.CutStyle
+import com.fs.starfarer.api.ui.UIPanelAPI
import org.lwjgl.input.Keyboard
import org.magiclib.ReflectionUtils
import org.magiclib.internalextensions.*
-import java.awt.Color
import org.magiclib.kotlin.setAlpha
import org.magiclib.paintjobs.MagicPaintjobRefitPanel.createMagicPaintjobRefitPanel
import org.magiclib.util.MagicTxt
+import java.awt.Color
/**
* @author Starficz
@@ -20,7 +24,8 @@ internal object MagicPaintjobRefitPanelCreator {
fun addPaintjobButton(refitTab: UIPanelAPI, inCampaign: Boolean) {
val refitPanel = refitTab.findChildWithMethod("syncWithCurrentVariant") as? UIPanelAPI ?: return
val statsAndHullmodsPanel = refitPanel.findChildWithMethod("getColorFor") as? UIPanelAPI ?: return
- val hullmodsPanel = statsAndHullmodsPanel.findChildWithMethod("removeNotApplicableMods") as? UIPanelAPI ?: return
+ val hullmodsPanel =
+ statsAndHullmodsPanel.findChildWithMethod("removeNotApplicableMods") as? UIPanelAPI ?: return
val fleetMember = ReflectionUtils.invoke("getMember", refitPanel) as? FleetMemberAPI
val existingElements = hullmodsPanel.getChildrenCopy()
@@ -41,7 +46,10 @@ internal object MagicPaintjobRefitPanelCreator {
// addHullmods button should always exist in hullmodsPanel
val addButton = existingElements.filter { ReflectionUtils.hasMethodOfName("getText", it) }.find {
- (ReflectionUtils.invoke("getText", it) as String).contains(MagicTxt.getString("ml_mp_refit_vanillaHullmodAddButtonText"))
+ (ReflectionUtils.invoke(
+ "getText",
+ it
+ ) as String).contains(MagicTxt.getString("ml_mp_refit_vanillaHullmodAddButtonText"))
} ?: return
// make a new button
@@ -61,8 +69,8 @@ internal object MagicPaintjobRefitPanelCreator {
newPaintjobButton.setShortcut(Keyboard.KEY_S, true)
newPaintjobButton.onClick {
// width/height calcs here are to match vanilla's hullmod panel sizes when screen size grow/shrink
- val width = if(inCampaign) (refitTab.width - 343).coerceIn(667f, 700f) else 667f
- val height = if(inCampaign) (refitTab.height - 12).coerceIn(722f, 800f) else 722f
+ val width = if (inCampaign) (refitTab.width - 343).coerceIn(667f, 700f) else 667f
+ val height = if (inCampaign) (refitTab.height - 12).coerceIn(722f, 800f) else 722f
val paintjobPanel = createMagicPaintjobRefitPanel(refitTab, refitPanel, width, height)
val coreUI = ReflectionUtils.invoke("getCoreUI", refitPanel) as UIPanelAPI
@@ -70,7 +78,7 @@ internal object MagicPaintjobRefitPanelCreator {
// the numbers might look like magic, but they are actually offsets from where the vanilla refit panel ends up.
// the other calcs here do ensure correct relative placement
- val xOffset = if(inCampaign) (refitTab.width - 1037).coerceIn(6f, 213f) else 6f
+ val xOffset = if (inCampaign) (refitTab.width - 1037).coerceIn(6f, 213f) else 6f
paintjobPanel.xAlignOffset = refitTab.left - paintjobPanel.left + xOffset
paintjobPanel.yAlignOffset = refitTab.top - paintjobPanel.top - 6
diff --git a/src/org/magiclib/paintjobs/MagicPaintjobShinyAdder.kt b/src/org/magiclib/paintjobs/MagicPaintjobShinyAdder.kt
index 41d46303..e1ca3578 100644
--- a/src/org/magiclib/paintjobs/MagicPaintjobShinyAdder.kt
+++ b/src/org/magiclib/paintjobs/MagicPaintjobShinyAdder.kt
@@ -1,46 +1,109 @@
package org.magiclib.paintjobs
-import com.fs.starfarer.api.campaign.BaseCampaignEventListener
+import com.fs.starfarer.api.EveryFrameScript
+import com.fs.starfarer.api.Global
import com.fs.starfarer.api.campaign.CampaignFleetAPI
+import com.fs.starfarer.api.combat.ShipVariantAPI
+import com.fs.starfarer.api.fleet.FleetMemberAPI
import com.fs.starfarer.api.impl.campaign.ids.Tags
+import com.fs.starfarer.api.loading.VariantSource
+import com.fs.starfarer.api.util.IntervalUtil
+import com.fs.starfarer.api.util.Misc
import org.magiclib.kotlin.getSalvageSeed
import kotlin.random.Random
-class MagicPaintjobShinyAdder : BaseCampaignEventListener(false) {
+
+class MagicPaintjobShinyAdder : EveryFrameScript {
+ companion object {
+ @JvmStatic
+ var probability = 25 // 1 in 25 chance of spawning a shiny
+ }
+
+ private var isDoneInternal = false
+ override fun isDone() = isDoneInternal
+ override fun runWhilePaused() = false
+
+ private val interval = IntervalUtil(2f, 3f)
private val fleetsCheckedIds = mutableSetOf()
- override fun reportFleetSpawned(fleet: CampaignFleetAPI?) {
- if (fleet == null || !MagicPaintjobManager.isEnabled || fleet.isPlayerFleet || fleetsCheckedIds.contains(fleet.id)) {
- return
- }
+ override fun advance(amount: Float) {
+ interval.advance(amount)
+ if (!interval.intervalElapsed()) return
val allShinyPaintjobs =
MagicPaintjobManager.getPaintjobs(includeShiny = true).filter { it.isShiny }
+
+ // If no shiny paintjobs exist, no point in this script.
if (allShinyPaintjobs.isEmpty()) {
+ isDoneInternal = true
return
}
- val shipsInFleetWithAvailableShiny =
- fleet.fleetData.membersListCopy.filter { it.hullId in allShinyPaintjobs.flatMap { pj -> pj.hullIds } }
- val probability = 50 // 1 in X chance of getting a shiny
- var addedShiny = false
-
- for (ship in shipsInFleetWithAvailableShiny) {
- if (addedShiny) continue // Max of one per fleet
- if (Random(fleet.getSalvageSeed()).nextInt(probability) == 1) {
- MagicPaintjobManager.applyPaintjob(
- ship,
- allShinyPaintjobs.filter { ship.hullId in it.hullIds }.random()
- )
-
- if (!ship.variant.hasTag(Tags.UNRECOVERABLE)) {
- ship.variant.addTag(Tags.VARIANT_ALWAYS_RECOVERABLE)
- }
+ checkAndApplyShiniesToAllFleetsInPlayerLocation(allShinyPaintjobs)
+ }
- addedShiny = true
+ fun checkAndApplyShiniesToAllFleetsInPlayerLocation(
+ allShinyPaintjobs: List = MagicPaintjobManager.getPaintjobs(
+ includeShiny = true
+ ).filter { it.isShiny }
+ ) {
+ for (fleet in Global.getSector().playerFleet.containingLocation?.fleets ?: emptyList()) {
+ if (fleet == null || fleetsCheckedIds.contains(fleet.id) || !MagicPaintjobManager.isEnabled || fleet.isPlayerFleet
+ ) {
+ continue
}
+
+ val shipsInFleetWithAvailableShiny =
+ fleet.fleetData.membersListCopy.filter { it.hullId in allShinyPaintjobs.flatMap { pj -> pj.hullIds } }
+ var addedShiny = false
+
+ for (ship in shipsInFleetWithAvailableShiny) {
+ if (addedShiny) continue // Max of one per fleet
+
+ // Roll the dice once per fleet member that has an available shiny paintjob.
+ if (Random(fleet.getSalvageSeed()).nextInt(probability) == 1) {
+ // If PJ already applied to ship, don't reapply.
+ if (!ship.variant.hasHullMod(MagicPaintjobHullMod.ID)) {
+ applyShinyPaintjob(ship, fleet, allShinyPaintjobs)
+ }
+
+ addedShiny = true
+ }
+ }
+
+ // TODO: Removed this optimization that prevents checking a fleet more than once
+ // because the game keeps erasing the variant tags and therefore paintjobs
+ // so we need to keep re-applying them!
+ // fleetsCheckedIds.add(fleet.id)
}
+ }
+
+ private fun applyShinyPaintjob(
+ ship: FleetMemberAPI,
+ fleet: CampaignFleetAPI,
+ allShinyPaintjobs: List
+ ) {
+ setClonedVariant(ship)
+ MagicPaintjobManager.applyPaintjob(
+ ship,
+ allShinyPaintjobs.filter { ship.hullId in it.hullIds }.random(Random(fleet.getSalvageSeed()))
+ )
+
+ if (!ship.variant.hasTag(Tags.UNRECOVERABLE)) {
+ ship.variant.addTag(Tags.VARIANT_ALWAYS_RECOVERABLE)
+ }
+ }
- fleetsCheckedIds.add(fleet.id)
+ /**
+ * Clones the variant and sets it as a 'custom' variant, rather than as the base, so that
+ * the game doesn't replace/mess with it.
+ */
+ private fun setClonedVariant(member: FleetMemberAPI, setNullOrigVariant: Boolean = true): ShipVariantAPI {
+ val variantClone = member.variant.clone()
+ variantClone.hullVariantId = member.hullId + "_" + Misc.genUID()
+ variantClone.source = VariantSource.REFIT
+ if (setNullOrigVariant) variantClone.originalVariant = null
+ member.setVariant(variantClone, false, false)
+ return variantClone
}
}
\ No newline at end of file
diff --git a/src/org/magiclib/paintjobs/console/ListPaintjobsCommand.kt b/src/org/magiclib/paintjobs/console/ListPaintjobsCommand.kt
new file mode 100644
index 00000000..89231de7
--- /dev/null
+++ b/src/org/magiclib/paintjobs/console/ListPaintjobsCommand.kt
@@ -0,0 +1,24 @@
+package org.magiclib.paintjobs.console
+
+import org.lazywizard.console.BaseCommand
+import org.lazywizard.console.Console
+import org.magiclib.paintjobs.MagicPaintjobManager
+
+class ListPaintjobsCommand : BaseCommand {
+ override fun runCommand(args: String, context: BaseCommand.CommandContext): BaseCommand.CommandResult {
+ val normalPjs = MagicPaintjobManager.getPaintjobs()
+ val shinyPjs = MagicPaintjobManager.getPaintjobs(includeShiny = true) - normalPjs
+
+ Console.showMessage("Regular Paintjobs")
+ for (pj in normalPjs) {
+ Console.showMessage("\t$pj")
+ }
+
+ Console.showMessage("Shiny Paintjobs")
+ for (pj in shinyPjs) {
+ Console.showMessage("\t$pj")
+ }
+
+ return BaseCommand.CommandResult.SUCCESS
+ }
+}
\ No newline at end of file
diff --git a/src/org/magiclib/paintjobs/rulecmd/DoesFleetHaveShiny.kt b/src/org/magiclib/paintjobs/rulecmd/DoesFleetHaveShiny.kt
new file mode 100644
index 00000000..ec645179
--- /dev/null
+++ b/src/org/magiclib/paintjobs/rulecmd/DoesFleetHaveShiny.kt
@@ -0,0 +1,24 @@
+package org.magiclib.paintjobs.rulecmd
+
+import com.fs.starfarer.api.campaign.CampaignFleetAPI
+import com.fs.starfarer.api.campaign.InteractionDialogAPI
+import com.fs.starfarer.api.campaign.rules.MemoryAPI
+import com.fs.starfarer.api.impl.campaign.rulecmd.BaseCommandPlugin
+import com.fs.starfarer.api.util.Misc
+import org.magiclib.paintjobs.MagicPaintjobManager
+
+
+class DoesFleetHaveShiny : BaseCommandPlugin() {
+ override fun execute(
+ ruleId: String?,
+ dialog: InteractionDialogAPI?,
+ params: MutableList?,
+ memoryMap: MutableMap?
+ ): Boolean {
+ val fleet = dialog?.interactionTarget as? CampaignFleetAPI
+
+ return fleet?.fleetData?.membersListCopy
+ ?.map { MagicPaintjobManager.getCurrentShipPaintjob(it) }
+ ?.any { it?.isShiny == true } == true
+ }
+}
diff --git a/src/org/magiclib/subsystems/MagicSubsystem.java b/src/org/magiclib/subsystems/MagicSubsystem.java
index f9e62df0..d5dd718c 100644
--- a/src/org/magiclib/subsystems/MagicSubsystem.java
+++ b/src/org/magiclib/subsystems/MagicSubsystem.java
@@ -1012,6 +1012,7 @@ public void drawHUDBar(ViewportAPI viewport, Vector2f rootLoc, Vector2f barLoc,
ship,
getBarFill(),
nameText,
+ getHUDColor(),
getExtraInfoText(),
getExtraInfoColor(),
stateText,
diff --git a/src/org/magiclib/util/MagicCampaign.java b/src/org/magiclib/util/MagicCampaign.java
index ef34e3a7..391639b0 100644
--- a/src/org/magiclib/util/MagicCampaign.java
+++ b/src/org/magiclib/util/MagicCampaign.java
@@ -157,7 +157,7 @@ public static ShipVariantAPI loadVariant(String path) {
// todo: check if order matters
if (sMods != null) {
for (int k = 0; k < sMods.length(); k++) {
- String sModId = hullMods.getString(k);
+ String sModId = sMods.getString(k);
variant.addPermaMod(sModId, true);
// variant.addPermaMod(sModId);
variant.addMod(sModId);
@@ -165,7 +165,7 @@ public static ShipVariantAPI loadVariant(String path) {
}
if (permaMods != null) {
for (int j = 0; j < permaMods.length(); j++) {
- String permaModId = hullMods.getString(j);
+ String permaModId = permaMods.getString(j);
variant.addPermaMod(permaModId);
if (!variant.getHullMods().contains(permaModId)) {
variant.addMod(permaModId);
diff --git a/src/org/magiclib/util/MagicUI.java b/src/org/magiclib/util/MagicUI.java
index 5180aa5e..ca37ea54 100644
--- a/src/org/magiclib/util/MagicUI.java
+++ b/src/org/magiclib/util/MagicUI.java
@@ -58,9 +58,11 @@ public class MagicUI {
try {
LazyFont fontdraw = LazyFont.loadFont("graphics/fonts/victor14.fnt");
TODRAW14 = fontdraw.createText();
+ TODRAW14.setBlendSrc(GL11.GL_ONE);
fontdraw = LazyFont.loadFont("graphics/fonts/victor10.fnt");
TODRAW10 = fontdraw.createText();
+ TODRAW10.setBlendSrc(GL11.GL_ONE);
if (UI_SCALING != 1) {
TODRAW14.setFontSize(TODRAW14.getFontSize() * UI_SCALING);