Fractal Softworks Forum

Please login or register.

Login with username, password and session length

Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Topics - lyravega

Pages: [1]
1
Mods / [0.96a] Experimental Hull Modifications 0.8.6a
« on: March 18, 2022, 12:08:29 AM »
FOREWARNING
The 'Experimental' in the title is not just for novelty. Please use "Save Copy" before using this mod; in other words, back your save up.
The goal of this mod is providing extreme customization; balance isn't on any priority list. Extreme cases will be adjusted but that's pretty much it.

Experimental Hull Modifications
Requires LunaLib & LazyLib

Just some hull modifications. By some, I mean 40+ or something. However, most are mutually exclusive, so the usable list is rather short actually. On a new game or a load, all hull modifications and slot shunts will be added to player's known list. Any port you visit will have a new submarket where you can grab slot shunts from.

Hull Modifications:
• Experimental Hull Modifications: Provides a base to all other hull modifications in this mod
• Over-Engineered: Provides 20% OP bonus and some slot-points to the ship when built-in
• Logistics Overhaul: Strips the ship of all combat capabilities in favour of logistics
• Auxiliary Generators: Provides some slot-points in exchange for ordnance points
• AI Switch: Turns automated ships into normal, or normal ships into automated ones

Hull Modification Categories:
• Systems: Most vanilla systems are turned into hull modifications
• Retrofits: Alters the weapon slots of a ship all together
• Cosmetics: Fully customizable hull modifications to change shield and engine colours. Customization is done through the mod settings menu
• Activators: Uses slot shunts to provide the ship with various enhancements by altering the slot they're installed on

Slot Shunt Categories: These are utilities that can be equipped on weapon slots. Each require its own activator to do what it's supposed to do
• Adapters: Turns a bigger slot into smaller versions (3 large, 1 medium types)
• Converters: Turns a smaller slot into a bigger one by using slot points (1 medium, 1 small types)
• Diverters: Turns a slot inactive and yield slot points to be used by above (1 small type)
• Capacitors: Enhances the total flux capacity of the ship (1 small type)
• Dissipators: Enhances the total flux dissipation of the ship (1 small type)
• Launch Tubes: Turns a large slot into a fighter hangar (1 large type)

FAQ
Where to find these Hull Modifications?
All of these hull modifications will be taught to the player on a new game or on a load. There's no hunting or buying them, at least for now! You might need to select the new "Experimental" category and/or the new subcategories if you've installed the mod on an existing game.

Categories (click to enlarge)
[close]
Where to find these Slot Shunts?
There is a new ability called "Experimental Engineering". You may need to put the ability on your ability hotbar first. While this ability is turned on, when you enter the refit tab, enough of these will be added automatically, and upon leaving the refit tab, unused ones will be removed. An alternative option adds a submarket for these instead, which will be visible on any port while the ability is turned on. Keep this ability active or toggle it on/off as needed.

The ability (click to enlarge)


The market (click to enlarge)
[close]
[close]

(Step Down Adapter, Mass Energy Conversion hullmods in action)

(click to open a larger version)

Download here
PS: I'm 99% more active on Discord.
Change Log
0.8.6a
Refactored and overhauled most of the stuff under the hood; initial changes begun on the previous version, which are now all completed. Most of the 0.8.x versions didn't offer much in terms of gameplay, but with all these out of the way, parts of the mod that needs attention may get some. Hopefully.

- Fixed phase cloak checks of certain hullmods reporting incorrectly and blocking installation
- Fixed "Logistics Overhaul" not ignoring the hidden modular hull modifications in its checks
- Fixed an issue on base hullmod installation that added fixed built-in dmods back to the ship
- Fixed experimental hull modifications creating ghost categories in mission mode even though they were hidden
- Fixed undo button not getting disabled visually when it was supposed to
- Changed system retrofits so that they will be hidden if the system is not found, instead of crashing the game on install
- Changed engine cosmetic mods to apply the campaign engine changes immediately on install and/or when settings are saved
- Changed ship/module tracking completely, which comes with a few non-gameplay benefits:
   - All of the ships in the fleet will be tracked when the refit tab is open
   - "Play Drill Sounds - All" option will do so when a hullmod is installed on / removed from any ship
   - Modules will not spawn unnecessary trackers or get confused by duplicates and instead use their own
   - Officer and autofit menus will not cause the trackers to detect false-positive changes on the ship
- Combined some logging settings, suppressed most messages for default setting
[close]
0.8.5
- Fixed a fatal issue caused by a change in 0.8.4 that lead to slot not found crashes
- Changed "Logistics Overhaul" so that it cannot be installed on module-ships (for now)
[close]
0.8.4
- Fixed an issue with ship restoration that lagged the game if the ship had any activated slot shunts
- Fixed launch tube description
[close]
0.8.3
- Fixed a rare crash happened on load related to a listener
- Fixed Over-Engineered failing to refresh the ship display for any existing inert converters that it activated
- Fixed an issue that caused "Hull Restoration" skill to be more effective when "Quality Captains" mod was active
- Added OP cost to Over-Engineered (the OP cost matters only if "Progressive S-Mods" mod is installed)
- Added total flux capacity and dissipation bonuses (if any) to Mutable Shunt Activator's tooltip
- Added a new hullmod "Launch Tube Activator"; Mutable Shunt Activator no longer activates launch tubes
- Added a new hullmod "Logistics Overhaul"; strips the ship bare and repurposes it for pure logistics use
- Added "Hide Adapters/Converters" options; if enabled, the slot shunts will be hidden upon activation
- Added "Cosmetics Only" option; only cosmetic hull modifications will be available in the mod picker
- Added a "Debug Settings" section; enable/disable logging for certain items, display extra information on base hullmod's tooltip
- Changed "Play Drill Sounds" option; is now a selection with 'Experimental' (default), 'All' and 'None' options
- Changed how mutually exclusive mods behave; installation is not blocked anymore as the newer mod will remove the older one
- Changed slot retrofits to prevent installation and removal if there are wings or inert slot shunts present on the ship to prevent an issue
- Changed the OP bonus of Over-Engineered (back) to 20% (from 15%)
- Removed all experimental hull modifications from Missions; mission refit panel mod picker will no longer show any of these
- Removed all experimental slot shunt blueprints from known lists as it is redundant due to the ability

If you are using "Quality Captains", make sure it's up-to-date as there was an issue. Author of "Quality Captains" mod Dal has already made a change with "1.5.3" to ensure compatibility between the mods. Thanks Dal!
[close]
0.8.2
- Added a new setting for slot shunt availability, both options* rely on the ability
- Added a new setting to control deployment point penalty for the hull modifications
- Unused slot points from hull modifications will no longer increase deployment point cost
- Missile Slot Retrofit suppresses built-in HBI if present
- Adjusted some icons and text colours, fixed a few tooltips
- Fixed a vanilla issue with the weapon groups
- Prevented a potential problem with a listener

* "Always" (new, default) option makes slot shunts available whenever refit tab is opened
* "Submarket" (old) option makes slot shunts available only when docked at a port
[close]
0.8.1
A new hull modification which turns automated ships to normal, or normal ships to automated. Its tooltip is dynamic and will tell you about bonuses / penalties. Having the skill "Automated Ships" is required.

Other than that, there are a few "balance" changes, and fixes. First thing that's thrown out of the window with this mod is balance but I'll still try to achieve some semblance of "balance" whenever I can. Even though it's probably impossible!

- Added a new hullmod "AI Switch"; turns automated ships into normal, and normal into automated
- Added energy version of HBI; built-in HBI is replaced with HEI when Mass Energy Retrofit is installed
- Over-Engineered slot point bonus changed to 1/2/3/5* (from 1/2/4/6), OP bonus lowered to 15% (from 20%)
- Slot points gained through Over-Engineered and Auxillary Generators increase DP by 1 per slot point
- Mass Missile Retrofit OP cost reduced to 6/12/18/30 (from 8/16/24/40). It now raises missile weapon OP cost by 2/4/8
- Fixed weapon checks ignoring activated shunts on the adapted slots which allowed adapter activator removal
- Fixed some incorrect text in the settings
- Updated some tooltips
- Restored the unmodification

* The game will load fine, but you may be in a slot point deficit. No penalties for this state, just a heads-up!
[close]
0.8.0
Big version bump due to accomplishing another milestone; implementing fully customizable engines, and as a bonus, implementing LunaLib and taking advantage of its options menu for mods.

Make sure to delete the old mod folder as a different folder will be used from now on. Also, EHM now requires LunaLib (and LazyLib) to function. Old IDs are still in use for savegame compatibility.

Changes/Fixes:
- LunaLib integration; removed old .json files and moved necessary stuff to the new options menu
- Renamed some shield cosmetic hullmods to "Low-Tech", "High-Tech", "Midline" and "Crimson" shields
- Converted remaining shield hullmods to fully customizable* "Red", "Green" and "Blue" shields
- Added 3 fully customizable* engine cosmetic hullmods; "Red", "Green" and "Blue" engines
- Added full shield and engine customization options through the new options menu
- Added "Auxilary Generators" hullmod; provides 1/1/2/2 slot points at the cost of 10/10/20/20 OP
- (D) is removed, or if 'showExperimentalFlavour' is enabled, replaced with (E) from hull class names
- Fixed shunt market ability toggle not properly persisting through reloads
- Fixed value reduction on ships that persisted till a reload
- Hid the removal tool from .csv for now

* Shield customization is rather simple, however engine customization offers a lot with flexibility. All three customizable engines are tweaked like "Low-Tech" engines by default, however you can customize them like [REDACTED] ones as well if you so desire. Experiment as you wish!

Customizable cosmetic hullmods have a yellow triangle in the upper right corner of their icons, and their names can be changed within game as well. Most settings are self-explanatory, but some might require some experimentation to have an idea what they do.

To start customizing your shields/engines, simply open LunaLib's settings menu by pressing "F2" on the campaign layer. After that all you need to do is install the hullmod on a ship. When you save the new values, (most) changes will be reflected in the game immediately.
[close]
0.7.3
- Added automatic clean-up for the slot shunts remaining in cargo holds after the shunt market interaction
- Added workarounds* to prevent possible crashes related to slot not found errors
- Fixed an issue with ship restoration that made refit tab unresponsive for a few seconds
- Fixed weapon checking; mass retrofits can now be installed on Invictus and Retribution (and alike)
- Added relevant tags to hullmods to prevent hidden ones from dropping as loot
- Updated out-dated slot shunt tooltips
- Improved Version Checker support

* These workarounds unfortunately cause a few "visual" issues:
- Ships that can be restored to their base versions will have their visuals restored immediately
- Restoration is still required and can be used to remove the d-mods, restoration cost remains the same
- Ships will be marked as (D)amaged even without any damage, but they will be identical to pristine ones
- The value loss due to being (D)amaged is negated, d-mods and actual damage still reduce value as normal
[close]
0.7.2
Fixes:
- "Slot not found" crash related to d-hulls
- "Slot not found" crash related to mod ships using different slot numbering convention

Changes:
- Added an ability to control the visibility of the the shunt market
- Mutable Shunt Activator now costs 2/4/6/10 OP instead of being free
- Flux shunts (dissipator and capacitors) costs are removed (they weren't using any after activation)
- Flux shunts now grant 1.5 OP points worth of flux dissipation & capacity (+15 dissipation, +300 capacity)
- Flux shunts' multiplicative bonus to total is halved (reduced to 0.01)
[close]
0.7.1
Hotfix:
• Fixed crashing due to trying to register non-experimental hull modifications

Game related:
• Added a submarket for experimental weapon slot shunts
• Changed how player faction is determined (for Nexerelin)
• Fixed system slots interfering with ship weapon checks
• Over-Engineered now grants 1 slot points to frigates
• Fixed wrong tooltip for Over-Engineered's +OP% bonus
[close]
0.6.3
Hotfix:
• Fixed main hull modification not adding on load (oopsie)
• Halved mutable shunt bonuses for now
• Fixed a fatal issue
• Fixed activated shunts showing up in weapon groups

General:
• Added missing icons, sprites and whatnot to anything that required one
• Expanded the options in "ehm_settings.json" a little bit and added more entries to "ehm_localization.json"
• Moved back to the old tracking system; should eliminate tracking related crashes
• Some unique mod-ships should be tracked by the "new" tracking system

Slot Shunts:
• Expanded on the "Adapter" idea, added "Diverters", "Converters", "Capacitors", "Dissipators" and "Launch Tubes"
• "Diverters" are used to divert power from a weapon slot to another, yielding slot points
• "Converters" are used to make a weapon slot bigger, using available slot points
• "Capacitors" boosts the total flux capacity of the ship
• "Dissipators" boosts the total flux dissipation of the ship
• "Launch Tubes" adds new fighter hangars to the ship

Hull Modifications:
• Renamed "Torpedo Engines" to "Crimson Engines"
• Added a new hull modification category: "Activators", renamed and moved "Step Down Adapter"
• Added "Experimental Hull Unmodification" under all EHM categories; removes the base hull modification
• Added "Mass Missile Retrofit" modification; much more expensive than the others
• Added "Mass Small Universal Retrofit" modification; only affects small slots, cheaper than the others
• Added "Over-Engineered" under "Retrofits"; has no effect till it gets built-in, then it provides extra OP and slot points
• Added "Converter/Diverter Activator" modification; "Converters" use the slot points generated by "Diverters"
• Added "Mutable Shunt Activator" modification; Activates "Capacitors", "Dissipators" and "Launch Tubes"
[close]
0.2.0
• Phase Cloak subsystem will be hidden for now
• Added new JSON files ("ehm_localization.json" and "ehm_settings.json") under the customization folder

"ehm_localization.json"
• A few things about the hullMod tooltips can be altered to some degree

"ehm_settings.json"
• Cosmetic shield hullMods can now be renamed and their colours can be adjusted
• Flavour text that is applied on the ship manufacturer & descriptions can be enabled/disabled
[close]
0.1.7
• Fixed conflicts with Progressive S-Mods
• Added Version Checker support
• Altered the blueprint teacher to not teach hidden hullmods (which are visible through some mods)
• None of the experimental hullmods can be built-in
• Drone systems now regen a drone every 10s
• Missile Autoforge now has a cooldown of 45s instead of being a single-time use system
[close]
[close]
Many thanks to Lukas04 for the LunaLib!

2
This is an alternative way to the one I've written a while ago. The main differences is, it ditches the tracker scripts and just uses a script to refresh the refit screen, and pretty much everything is bundled in one class.

Let me paste the whole thing before I go on. This time I'll put the whole thing in spoiler, but chop the methods I'm talking about up for reference:
the whole thing
GitHub
Code
package data.hullmods;

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.fs.starfarer.api.EveryFrameScript;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.campaign.CampaignFleetAPI;
import com.fs.starfarer.api.campaign.CampaignUIAPI.CoreUITradeMode;
import com.fs.starfarer.api.campaign.CoreUITabId;
import com.fs.starfarer.api.campaign.econ.MarketAPI;
import com.fs.starfarer.api.combat.HullModFleetEffect;
import com.fs.starfarer.api.combat.MutableShipStatsAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipAPI.HullSize;
import com.fs.starfarer.api.combat.ShipVariantAPI;
import com.fs.starfarer.api.fleet.FleetMemberAPI;

import org.apache.log4j.Logger;

import data.hullmods.ehm_ec._ehm_ec_base;
import data.hullmods.ehm_sc._ehm_sc_base;
import data.hullmods.ehm_sr._ehm_sr_base;
import data.hullmods.ehm_wr._ehm_wr_base;

/**
 * Serves as a requirement for all experimental hull modifications, and provides hullMod
 * tracking to the ship.
 * </p> Depending on the {@link #trackOnSync} boolean, will either initialize hullMod
 * tracking through {@link data.scripts.shipTrackerScript shipTrackers} or by utilizing
 * the {@link #onFleetSync()} method. Both have their downsides, but both also do the same.
 * @category Base Hull Modification
 * @author lyravega
 */
public class ehm_base extends _ehm_base implements HullModFleetEffect {
private static final boolean trackOnSync = true; // if false, scripts initialized from the parent will be used for tracking
private static final boolean log = true;
private static final Logger logger = Logger.getLogger("lyr");
private static ShipAPI sheep = null;

//#region IMPLEMENTATION (HullModFleetEffect)
@Override
public void advanceInCampaign(CampaignFleetAPI fleet) {}

@Override
public boolean withAdvanceInCampaign() { return false; }

@Override
public boolean withOnFleetSync() { return trackOnSync; }

// @Override
// public void onFleetSync(CampaignFleetAPI fleet) {}
//#endregion
// END OF IMPLEMENTATION (HullModFleetEffect)

//#region TRACKING
@Override
public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String hullModSpecId) {
ShipVariantAPI variant = stats.getVariant();

variant.setHullSpecAPI(ehm_hullSpecClone(variant));
}

@Override
public void applyEffectsAfterShipCreation(ShipAPI ship, String id) {
if (ship == null) return;

CoreUITabId tab = Global.getSector().getCampaignUI().getCurrentCoreTab();
if (tab == null || !tab.equals(CoreUITabId.REFIT)) return;

if (trackOnSync) {
sheep = ship;
} else {
shipTrackerScript(ship).setVariant(ship.getVariant()); // setVariant() is necessary to reflect the changes on the "refit ship"
}
}

@Override
public void onFleetSync(CampaignFleetAPI fleet) {
if (!fleet.isPlayerFleet()) return;
if (sheep == null) return;

CoreUITabId tab = Global.getSector().getCampaignUI().getCurrentCoreTab();
if (tab == null || !tab.equals(CoreUITabId.REFIT)) return;

updateFleetMaps(fleet);
if (sheep != null) updateHullMods(sheep);
}

private static Map<String, Set<String>> hullModMap;
private static Map<String, FleetMemberAPI> fleetMemberMap;

public static void buildFleetMaps() {
if (!trackOnSync) return;

hullModMap = new HashMap<String, Set<String>>();
fleetMemberMap = new HashMap<String, FleetMemberAPI>();

for (FleetMemberAPI member : Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy()) {
hullModMap.put(member.getId(), new HashSet<String>(member.getVariant().getHullMods()));
fleetMemberMap.put(member.getId(), member);
}
}

private static void updateFleetMaps(CampaignFleetAPI fleet) {
List<FleetMemberAPI> fleetMembers = fleet.getFleetData().getMembersListCopy();
String memberId;

for (FleetMemberAPI member : fleetMemberMap.values()) {
if (fleetMembers.contains(member)) continue;

memberId = member.getId();
hullModMap.remove(memberId);
if (log) logger.info("FT: Unregistering ST-"+memberId);
} fleetMemberMap.keySet().retainAll(hullModMap.keySet());

for (FleetMemberAPI member : fleetMembers) {
if (fleetMemberMap.values().contains(member)) continue;

memberId = member.getId();
hullModMap.put(memberId, new HashSet<String>(member.getVariant().getHullMods()));
fleetMemberMap.put(memberId, member);
if (log) logger.info("FT: Registering ST-"+memberId);
}

if (!fleetMemberMap.containsKey(sheep.getFleetMemberId())) sheep = null;
}

private static void updateHullMods(ShipAPI ship) {
Set<String> savedHullMods = hullModMap.get(ship.getFleetMemberId());
Collection<String> currentHullMods = ship.getVariant().getHullMods();

if (savedHullMods.size() == currentHullMods.size()) return;

Set<String> _savedHullMods = new HashSet<String>(savedHullMods);

if (savedHullMods.size() < currentHullMods.size()) {
for (String newHullModId : currentHullMods) {
if (savedHullMods.contains(newHullModId)) continue;

onInstalled(newHullModId, ship);
savedHullMods.add(newHullModId);
}
} else /*if (savedHullMods.size() > currentHullMods.size())*/ {
for (String removedHullModId : _savedHullMods) {
if (currentHullMods.contains(removedHullModId)) continue;

onRemoved(removedHullModId, ship);
savedHullMods.remove(removedHullModId);
}
}
}

private static void onInstalled(String newHullModId, ShipAPI ship) {
// ShipVariantAPI refitVariant = ship.getVariant();
// ShipVariantAPI realVariant = fleetMemberMap.get(ship.getFleetMemberId()).getVariant();
boolean playSound = false;
boolean refresh = false;

String hullModType = newHullModId.substring(0, 7); // all affixes (not tags) are fixed to 0-7
switch (hullModType) { // any weaponSlot changes require refresh
case ehm.affix.adapterRetrofit: break; // handled through hullMod methods
case ehm.affix.systemRetrofit: playSound = true; break;
case ehm.affix.weaponRetrofit: playSound = true; refresh = true; break;
case ehm.affix.shieldCosmetic: playSound = true; break;
case ehm.affix.engineCosmetic: playSound = true; break;
default: break;
}

if (log) logger.info("ST-"+ship.getFleetMemberId()+": New hull modification '"+newHullModId+"'");
if (refresh) refreshRefit(playSound);
else if (playSound) Global.getSoundPlayer().playUISound("drill", 1.0f, 0.75f);
}

private static void onRemoved(String removedHullModId, ShipAPI ship) {
ShipVariantAPI refitVariant = ship.getVariant();
// ShipVariantAPI realVariant = fleetMemberMap.get(ship.getFleetMemberId()).getVariant();
boolean playSound = false;
boolean refresh = false;

String hullModType = removedHullModId.substring(0, 7);
switch (hullModType) { // any weaponSlot changes and cheap removal methods require refresh
case ehm.affix.adapterRetrofit: break; // handled through hullMod methods
case ehm.affix.systemRetrofit: _ehm_sr_base.ehm_systemRestore(refitVariant); playSound = true; break;
case ehm.affix.weaponRetrofit: _ehm_wr_base.ehm_weaponSlotRestore(refitVariant); playSound = true; refresh = true; break;
case ehm.affix.shieldCosmetic: _ehm_sc_base.ehm_restoreShield(refitVariant); playSound = true; break;
case ehm.affix.engineCosmetic: _ehm_ec_base.ehm_restoreEngineSlots(refitVariant); playSound = true; refresh = true; break;
default: break;
}

if (log) logger.info("ST-"+ship.getFleetMemberId()+": Removed hull modification '"+removedHullModId+"'");
if (refresh) refreshRefit(playSound);
else if (playSound) Global.getSoundPlayer().playUISound("drill", 1.0f, 0.75f);
}
//#endregion
// END OF TRACKING

//#region SCRIPTS
private static class refreshRefitScript implements EveryFrameScript {
private boolean isDone = false;
private boolean playSound = false;
private float frameCount = 0f;
private static Robot robot;

static {
try {
robot = new Robot();
} catch (AWTException e) {
e.printStackTrace();
}
}

public refreshRefitScript(boolean playSound) {
this.playSound = playSound;
Global.getSector().addTransientScript(this);
}

@Override
public void advance(float amount) {
CoreUITabId tab = Global.getSector().getCampaignUI().getCurrentCoreTab();
if (tab == null || !tab.equals(CoreUITabId.REFIT)) { isDone = true; return; }

frameCount++;
if (frameCount < 5) {
robot.keyPress(KeyEvent.VK_ENTER);
} else {
robot.keyPress(KeyEvent.VK_R);
robot.keyRelease(KeyEvent.VK_R);
robot.keyRelease(KeyEvent.VK_ENTER);
if (log) logger.info("RR: Refreshed refit tab");
if (playSound) Global.getSoundPlayer().playUISound("drill", 1.0f, 0.75f);
isDone = true;
return;
}
}

@Override
public boolean runWhilePaused() {
return true;
}

@Override
public boolean isDone() {
return isDone;
}
}

private static refreshRefitScript refreshRefitScript;

protected static void refreshRefit(boolean playSound) {
refreshRefitScript = null;

for(EveryFrameScript script : Global.getSector().getTransientScripts()) {
if(script instanceof refreshRefitScript) {
refreshRefitScript = (refreshRefitScript) script;
}
}

if (refreshRefitScript == null) {
refreshRefitScript = new refreshRefitScript(playSound);
}
}
//#endregion
// END OF SCRIPTS

@Override
protected String ehm_unapplicableReason(ShipAPI ship) {
if (ship == null) return ehm.excuses.noShip;

return null;
}

@Override
protected String ehm_cannotBeInstalledNowReason(ShipAPI ship, MarketAPI marketOrNull, CoreUITradeMode mode) {
if (ehm_hasRetrofitTag(ship, ehm.tag.allRetrofit, hullModSpecId)) return ehm.excuses.hasAnyRetrofit;

return null;
}
}
[close]

The method called 'buildFleetMaps()' initializes and fills two maps called 'hullModMap' and 'fleetMemberMap'. This method needs to be called externally, preferably when the game is loaded through the plug-in. Or whatever it does needs to be built in somewhere in the code that'll execute once, and then the check that executes that part needs to be set true. So, using the plugin to call this method is preferable.

The 'fleetMemberMap' is simply the fleetMembers converted into a map form for ease of usability (trying to find if something exists in an Arraylist is unproductive, when you can grab the memberId and just check, in my experience), and the 'hullModMap' is similar to a member map, but points to the hullMods of the members. Other similar maps for weapons and such can also be constructed, too. I don't check if they have a specific hullmod so that they'll be added to those maps, which I find unnecessary as we're working with statics anyway.
Code
	private static Map<String, Set<String>> hullModMap;
private static Map<String, FleetMemberAPI> fleetMemberMap;

public static void buildFleetMaps() {
if (!trackOnSync) return;

hullModMap = new HashMap<String, Set<String>>();
fleetMemberMap = new HashMap<String, FleetMemberAPI>();

for (FleetMemberAPI member : Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy()) {
hullModMap.put(member.getId(), new HashSet<String>(member.getVariant().getHullMods()));
fleetMemberMap.put(member.getId(), member);
}
}
Another method 'updateFleetMaps()' keeps track of the existing (fleet) members; add new members in, removes old members, and updates these two maps accordingly. Aside from that, it drops the reference to 'sheep' which is necessary after a fleet wipe for example. The former was a builder, and this is what'll be used to keep it maintained throughout the session, so to speak.

While removing a member, having two maps with the same keys comes in handy for avoiding concurrent modification errors. Just remove it from the unused one, then use 'retainAll()' to update the other. Subsets of maps are actually quite handy to use, though I wish these stuff were as easy to use as Lua tables...
Code
	private static void updateFleetMaps(CampaignFleetAPI fleet) {
List<FleetMemberAPI> fleetMembers = fleet.getFleetData().getMembersListCopy();
String memberId;

for (FleetMemberAPI member : fleetMemberMap.values()) {
if (fleetMembers.contains(member)) continue;

memberId = member.getId();
hullModMap.remove(memberId);
if (log) logger.info("FT: Unregistering ST-"+memberId);
} fleetMemberMap.keySet().retainAll(hullModMap.keySet());

for (FleetMemberAPI member : fleetMembers) {
if (fleetMemberMap.values().contains(member)) continue;

memberId = member.getId();
hullModMap.put(memberId, new HashSet<String>(member.getVariant().getHullMods()));
fleetMemberMap.put(memberId, member);
if (log) logger.info("FT: Registering ST-"+memberId);
}

if (!fleetMemberMap.containsKey(sheep.getFleetMemberId())) sheep = null;
}
The method 'updateHullMods()' does something similar, but for the 'hullModMap'. You might have noticed that the hullMods in the map is actually a new set, because if you work on the collection directly, you'll manipulate the variant directly, so by creating a new set, we dropped the reference. It's not necessary, it's just a precaution at the cost of little bit more memory usage.

Also, there is a copy of the same set called '_savedHullMods'. It is there to avoid concurrent modification error. You can either do that, or create a new set, add the added/removed ones to it, then use 'removeAll()' to update the real one after the loop, naturally. I just took the cheap way out.
Code
	private static void updateHullMods(ShipAPI ship) {
Set<String> savedHullMods = hullModMap.get(ship.getFleetMemberId());
Collection<String> currentHullMods = ship.getVariant().getHullMods();

if (savedHullMods.size() == currentHullMods.size()) return;

Set<String> _savedHullMods = new HashSet<String>(savedHullMods);

if (savedHullMods.size() < currentHullMods.size()) {
for (String newHullModId : currentHullMods) {
if (savedHullMods.contains(newHullModId)) continue;

onInstalled(newHullModId, ship);
savedHullMods.add(newHullModId);
}
} else /*if (savedHullMods.size() > currentHullMods.size())*/ {
for (String removedHullModId : _savedHullMods) {
if (currentHullMods.contains(removedHullModId)) continue;

onRemoved(removedHullModId, ship);
savedHullMods.remove(removedHullModId);
}
}
}
Both these two are called from 'onFleetSync()'. It is a method implementation, a method from the interface 'HullModFleetEffect', and triggers if the 'withOnFleetSync()' returns true. The same methods can be called from 'applyEffectsAfterShipCreation()' and the implementation can be ditched, but there's a reason I am using the 'onFleetSync()' and not the other. And the reason is, the amount of method calls when it is really needed to call the beyond the chaff-checks.
Code
	@Override
public void onFleetSync(CampaignFleetAPI fleet) {
if (!fleet.isPlayerFleet()) return;
if (sheep == null) return;

CoreUITabId tab = Global.getSector().getCampaignUI().getCurrentCoreTab();
if (tab == null || !tab.equals(CoreUITabId.REFIT)) return;

updateFleetMaps(fleet);
if (sheep != null) updateHullMods(sheep);
}
Both sync and apply methods have same tab-checks, and the method calls that reach beyond that point is about 66% less for the 'onFleetSync()'. On top of that, when a real change happens, sync is only called once while the other is called about 6 times. On the other hand, 'onFleetSync()' is called for every fleet, and is not restricted to any screen, however you can short-circuit the whole method just by checking if it's the player fleet. It's up to you anyway, I found using the sync instead the cleaner choice.

With all that said, 'applyEffectsAfterShipCreation()' is still just as important. In there, the value of 'sheep' is set to ship. The refit ship you are looking at is recreated whenever a change happens, whenever you open hullMod browser, whenever you swap ships, refresh the refit screen, etc... so by binding that to a static, we are allowing the other parts of the class to utilize it. Without it, other methods will have no clue about the refit ship.

The refit ship is the current ship you are modifying, but in the fleet data, it is still the old one till you commit the changes. We want to track a change as soon as it happens, to do extra stuff if necessary, and 'sheep' is the glue that bonds it all together. I also kept my older version of tracking in here, and bound it to a boolean. Just the part where sheep is set to the ship is necessary, the else part uses the tracking method that utilizes shipTracker scripts.
Code
	@Override 
public void applyEffectsAfterShipCreation(ShipAPI ship, String id) {
if (ship == null) return;

CoreUITabId tab = Global.getSector().getCampaignUI().getCurrentCoreTab();
if (tab == null || !tab.equals(CoreUITabId.REFIT)) return;

if (trackOnSync) {
sheep = ship;
} else {
shipTrackerScript(ship).setVariant(ship.getVariant()); // setVariant() is necessary to reflect the changes on the "refit ship"
}
}
From the 'updateHullMods()' method, two others may be called depending on the situation, 'onInstalled()' and 'onRemoved()'. It is from these two that other stuff can be done. And they'll happen as soon as a change on the refit ship occurs, which is important if something needs to be done before committing, or it needs to be committed through a refresh and such. It's pretty much 1:1 with some internal changes to play sound and such directly from here, just a big fat string parser to do/call whatever you need.
Code
	private static void onInstalled(String newHullModId, ShipAPI ship) {
// ShipVariantAPI refitVariant = ship.getVariant();
// ShipVariantAPI realVariant = fleetMemberMap.get(ship.getFleetMemberId()).getVariant();
boolean playSound = false;
boolean refresh = false;

String hullModType = newHullModId.substring(0, 7); // all affixes (not tags) are fixed to 0-7
switch (hullModType) { // any weaponSlot changes require refresh
case ehm.affix.adapterRetrofit: break; // handled through hullMod methods
case ehm.affix.systemRetrofit: playSound = true; break;
case ehm.affix.weaponRetrofit: playSound = true; refresh = true; break;
case ehm.affix.shieldCosmetic: playSound = true; break;
case ehm.affix.engineCosmetic: playSound = true; break;
default: break;
}

if (log) logger.info("ST-"+ship.getFleetMemberId()+": New hull modification '"+newHullModId+"'");
if (refresh) refreshRefit(playSound);
else if (playSound) Global.getSoundPlayer().playUISound("drill", 1.0f, 0.75f);
}

private static void onRemoved(String removedHullModId, ShipAPI ship) {
ShipVariantAPI refitVariant = ship.getVariant();
// ShipVariantAPI realVariant = fleetMemberMap.get(ship.getFleetMemberId()).getVariant();
boolean playSound = false;
boolean refresh = false;

String hullModType = removedHullModId.substring(0, 7);
switch (hullModType) { // any weaponSlot changes and cheap removal methods require refresh
case ehm.affix.adapterRetrofit: break; // handled through hullMod methods
case ehm.affix.systemRetrofit: _ehm_sr_base.ehm_systemRestore(refitVariant); playSound = true; break;
case ehm.affix.weaponRetrofit: _ehm_wr_base.ehm_weaponSlotRestore(refitVariant); playSound = true; refresh = true; break;
case ehm.affix.shieldCosmetic: _ehm_sc_base.ehm_restoreShield(refitVariant); playSound = true; break;
case ehm.affix.engineCosmetic: _ehm_ec_base.ehm_restoreEngineSlots(refitVariant); playSound = true; refresh = true; break;
default: break;
}

if (log) logger.info("ST-"+ship.getFleetMemberId()+": Removed hull modification '"+removedHullModId+"'");
if (refresh) refreshRefit(playSound);
else if (playSound) Global.getSoundPlayer().playUISound("drill", 1.0f, 0.75f);
}
If a screen refresh is necessary, now the script to refresh the script with its initializer method is bundled within this class. And compared to the previous version that I've posted, it has a few minor improvements. Of course it doesn't need to be bundled with the whole class, but accessing everything from the same place, and being able to use the same static stuff and such is useful in a few cases.

The primary reason of offloading the robot to a script is, scripts (at the very least, these ones/types) run on another thread. You can put the robot in directly, but the game will choke till the robot is done. Using a script, it is avoided.
Code
	//#region SCRIPTS
private static class refreshRefitScript implements EveryFrameScript {
private boolean isDone = false;
private boolean playSound = false;
private float frameCount = 0f;
private static Robot robot;

static {
try {
robot = new Robot();
} catch (AWTException e) {
e.printStackTrace();
}
}

public refreshRefitScript(boolean playSound) {
this.playSound = playSound;
Global.getSector().addTransientScript(this);
}

@Override
public void advance(float amount) {
CoreUITabId tab = Global.getSector().getCampaignUI().getCurrentCoreTab();
if (tab == null || !tab.equals(CoreUITabId.REFIT)) { isDone = true; return; }

frameCount++;
if (frameCount < 5) {
robot.keyPress(KeyEvent.VK_ENTER);
} else {
robot.keyPress(KeyEvent.VK_R);
robot.keyRelease(KeyEvent.VK_R);
robot.keyRelease(KeyEvent.VK_ENTER);
if (log) logger.info("RR: Refreshed refit tab");
if (playSound) Global.getSoundPlayer().playUISound("drill", 1.0f, 0.75f);
isDone = true;
return;
}
}

@Override
public boolean runWhilePaused() {
return true;
}

@Override
public boolean isDone() {
return isDone;
}
}

private static refreshRefitScript refreshRefitScript;

protected static void refreshRefit(boolean playSound) {
refreshRefitScript = null;

for(EveryFrameScript script : Global.getSector().getTransientScripts()) {
if(script instanceof refreshRefitScript) {
refreshRefitScript = (refreshRefitScript) script;
}
}

if (refreshRefitScript == null) {
refreshRefitScript = new refreshRefitScript(playSound);
}
}
//#endregion
// END OF SCRIPTS
The rest are some of my junk, inherited stuff, and some statics that I didn't show, but is there in the initial 'whole thing' part anyway. This method does the same thing with my other tracker scripts, excluding one thing however: tracking module-ships, like SWP-Cathedral. The script-version can do that, but everytime it spawns a new tracker as... it SWP-Cathedral is weird. Everytime you switch to a module, modules get a new name for example, as if the whole module is recreated everytime you look at it. Additional checks and stuff can be added to this one to make it 100% module-friendly as well, but for now I decided not to go that way.

Now, I've set my eyes on two other ways to do this: Listeners, and hacking the UI. Why? Why not =) I had fun writing this (both the scripts, this one, and the topic), at the very least! Anyway, hopefully this might be useful to someone. Or, as I have said in the other topic as well, maybe Alex will add an onRemove() method with some access to refit UI to make it refresh, or something =)

3
Alternative way

Hello everyone. Here is a guide to implement a hullmod tracker, from which jury-rigged 'onRemove()' functions can be called, such as a function to refresh the refit screen. Before proceeding however, there are a couple of things that needs to be explained I believe. All are to my knowledge, what I assume.

First of all, the ships you are looking at in the refit screen are cloned/fake/virtual/refit ships (for convenience, I will be using the last term). These refit ships represent the originals, however the originals stay the same and as used as an 'undo' point till the changes are committed; when another ship is selected, refit screen is reopened and/or UI tab is switched to something else.

If an external script is implemented (external here in this context means a global script that checks the ships/fleet members, and initialized through an event such as 'onGameLoad()'), these refit ships cannot be tracked, and as such, the changes cannot be monitored through the refit screen. I have tried doing it that way, only to realize the above fact, which isn't a total loss I guess. Harder solution is, locating these refit ships in the game - which I haven't been able to, but the easier solution is initializing these scripts through something that is equipped on the ship, such as a hullmod. The irony... =)

The use case of such scripts is limited to applying an effect only once. As far as I can tell, hullModSpecs act like transient scripts, getting invoked several times per frame while you are in the refit screen, and using an external script will help with executing something only once. Best use case scenario is, refreshing the refit screen, and in my case, undoing some stuff that I mess around with the ships. 

Now, below is a piece of code for a hullModSpec base that has some methods to initialize some scripts (with some redundant code that makes sure there are no loose scripts flying around (hopefully)). I won't be using the spoiler tags as it seems to compress the code part to a few lines for some reason, so apologies for walls of code:
Code:  shipTracker initialization
	protected shipTrackerScript shipTracker;

/**
* Creates and assigns {@link #shipTracker} and {@link #fleetTracker}, then returns the
* {@link shipTrackerScript} that is unique to the ship. The overloads should be used
* for proper access. Scripts remain alive as long as the current tab is refit. The
* reference to the script MUST be dropped otherwise it will keep living on in the memory.
* @param variant of the ship to track
* @param memberId of the ship to track
* @return a {@link shipTrackerScript} script
* @see Overloads: {@link #shipTrackerScript()} and {@link #shipTrackerScript()}
*/
private shipTrackerScript shipTrackerScript(ShipVariantAPI variant, String memberId) {
for(EveryFrameScript script : Global.getSector().getScripts()) {
if(script instanceof shipTrackerScript) {
shipTrackerScript temp = (shipTrackerScript) script;
if (!temp.getMemberId().equals(memberId)) continue;

shipTracker = (shipTrackerScript) script; break;
}
}

fleetTracker = fleetTrackerScript();

return (shipTracker == null) ? new shipTrackerScript(variant, memberId, fleetTracker) : shipTracker;
}

/**
* Creates and assigns {@link #shipTracker} and {@link #fleetTracker}, then returns the
* {@link shipTrackerScript} that is unique to the ship. Scripts remain alive as long as
* the current tab is refit. The reference to the script MUST be dropped otherwise it
* will keep living on in the memory.
* @param stats of the ship to track
* @return a {@link shipTrackerScript} script
* @see Overload: {@link #shipTrackerScript()}
*/
protected shipTrackerScript shipTrackerScript(MutableShipStatsAPI stats) { // this isn't used, but is here nonetheless
if (stats == null) return null; shipTracker = null;

ShipVariantAPI variant = stats.getVariant();
String memberId = (stats.getFleetMember() != null) ? stats.getFleetMember().getId() : null; // this can be null

return (memberId != null) ? shipTrackerScript(variant, memberId) : null;
}

/**
* Creates and assigns {@link #shipTracker} and {@link #fleetTracker}, then returns the
* {@link shipTrackerScript} that is unique to the ship. Scripts remain alive as long as
* the current tab is refit. The reference to the script MUST be dropped otherwise it
* will keep living on in the memory.
* @param ship to track
* @return a {@link shipTrackerScript} script
* @see Overload: {@link #shipTrackerScript()}
*/
protected shipTrackerScript shipTrackerScript(ShipAPI ship) {
if (ship == null) return null; shipTracker = null;

ShipVariantAPI variant = ship.getVariant();
String memberId = ship.getFleetMemberId(); // fleet member can be null, but this never is

return shipTrackerScript(variant, memberId);
}

As said above, these methods are located in my base hullModSpec implementation from which I extend my hullMods from. These methods are used to create/initialize three scripts: 'shipTrackerScript', 'fleetTrackerScript' and 'refreshRefitScript'. For now, I will be talking about the first two. Lets start with 'shipTrackerScript'.

As you can see in the methods above, we have 2 overloads that redirect to a main method. The safest one is the one that uses 'ship', as in some cases Fleet Member might be null for 'stats'. 'ShipAPI' method 'getFleetMemberId()' never returns null even if the fleet member is null at that point, from what I have seen.

Part of the code that tries to find the script is redundant, as there shouldn't be a script at that point with the way I use the scripts; they are killed as soon as the tab is changed from refit to something else. If there is a need to keep the script alive, then it might serve a purpose. I keep them there to make absolutely sure there isn't another script running for the ship in question.

A 'fleetTrackerScript' is initialized before the 'shipTrackerScript', though as you'll see later on, it doesn't serve much of a purpose, and was initially designed to kill any loose scripts if there are any. Another memory-leak precaution that I later adapted to serve as a resource pool for shipTrackers.

Before I go on any further, here is the code for the 'shipTrackerScript':
Code: shipTrackerScript.java
package data.scripts;

import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.fs.starfarer.api.EveryFrameScriptWithCleanup;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.campaign.CoreUITabId;
import com.fs.starfarer.api.combat.ShipVariantAPI;

import org.apache.log4j.Logger;

import data.hullmods._ehm_base.ehm;
import data.hullmods.ehm_ec._ehm_ec_base;
import data.hullmods.ehm_sc._ehm_sc_base;
import data.hullmods.ehm_sr._ehm_sr_base;
import data.hullmods.ehm_wr._ehm_wr_base;

public class shipTrackerScript implements EveryFrameScriptWithCleanup {
// private fleetTrackerScript fleetTracker = null;
private ShipVariantAPI variant = null;
private String memberId = null;
private Set<String> hullMods = new HashSet<String>();
private boolean isDone = false;
private boolean refresh = false;
private boolean playSound = false;
private float frameCount = 0f;
private Robot robot = null;
private Logger logger = null;

//#region CONSTRUCTORS & ACCESSORS
public void setVariant(ShipVariantAPI variant) { // this can be moved to initialize
this.variant = variant;
}

public shipTrackerScript(ShipVariantAPI variant, String memberId, fleetTrackerScript fleetTracker) {
this.variant = variant;
this.memberId = memberId;

// this.fleetTracker = fleetTracker;
this.robot = fleetTracker.robot;
this.logger = fleetTracker.logger;
fleetTracker.addshipTracker(memberId, this);

for (String hullModId : variant.getHullMods()) { if (!hullModId.startsWith(ehm.affix.allRetrofit)) continue;
if (hullMods.contains(hullModId)) continue;

hullMods.add(hullModId);
}

Global.getSector().addScript(this);

logger.info("ST-"+memberId+": Initial hull modifications '"+hullMods.toString()+"'");
}

public String getMemberId() {
return this.memberId;
}

public void refresh() {
this.refresh = true;
}

public void kill() {
this.isDone = true;
}
//#endregion
// END OF CONSTRUCTORS & ACCESSORS

@Override
public void advance(float amount) {
CoreUITabId tab = Global.getSector().getCampaignUI().getCurrentCoreTab();
if (tab == null || !tab.equals(CoreUITabId.REFIT)) { logger.info("ST-"+memberId+": Stopping ship tracking"); isDone = true; return; }

Set<String> newHullMods = new HashSet<String>();
Set<String> removedHullMods = new HashSet<String>();

for (String hullModId : variant.getHullMods()) { if (!hullModId.startsWith(ehm.affix.allRetrofit)) continue;
if (hullMods.contains(hullModId)) continue;

logger.info("ST-"+memberId+": New hull modification '"+hullModId+"'");

newHullMods.add(hullModId);
}

for (Iterator<String> i = hullMods.iterator(); i.hasNext();) { String hullModId = i.next();
if (variant.hasHullMod(hullModId)) continue;

logger.info("ST-"+memberId+": Removed hull modification '"+hullModId+"'");

removedHullMods.add(hullModId);
}

if (!newHullMods.isEmpty()) {
for (Iterator<String> i = newHullMods.iterator(); i.hasNext();) {
String hullModId = i.next();
String hullModType = hullModId.substring(0, 7); // all affixes (not tags) are fixed to 0-7
switch (hullModType) { // any weaponSlot changes require refresh
case ehm.affix.adapterRetrofit: break; // handled through hullMod methods
case ehm.affix.systemRetrofit: playSound = true; break;
case ehm.affix.weaponRetrofit: playSound = true; refresh = true; break;
case ehm.affix.shieldCosmetic: playSound = true; break;
case ehm.affix.engineCosmetic: playSound = true; break;
default: break;
}
} hullMods.addAll(newHullMods); newHullMods.clear();
}

if (!removedHullMods.isEmpty()) {
for (Iterator<String> i = removedHullMods.iterator(); i.hasNext();) {
String hullModId = i.next();
String hullModType = hullModId.substring(0, 7);
switch (hullModType) { // any weaponSlot changes and cheap removal methods require refresh
case ehm.affix.adapterRetrofit: break; // handled through hullMod methods
case ehm.affix.systemRetrofit: _ehm_sr_base.ehm_systemRestore(variant); playSound = true; break;
case ehm.affix.weaponRetrofit: _ehm_wr_base.ehm_weaponSlotRestore(variant); playSound = true; refresh = true; break;
case ehm.affix.shieldCosmetic: _ehm_sc_base.ehm_restoreShield(variant); playSound = true; break;
case ehm.affix.engineCosmetic: _ehm_ec_base.ehm_restoreEngineSlots(variant); playSound = true;  refresh = true; break;
default: break;
}
} hullMods.removeAll(removedHullMods); removedHullMods.clear();
}

if (refresh) { frameCount++;
if (frameCount < 5) {
robot.keyPress(KeyEvent.VK_ENTER);
} else {
robot.keyPress(KeyEvent.VK_R);
robot.keyRelease(KeyEvent.VK_R);
robot.keyRelease(KeyEvent.VK_ENTER);
refresh = false;
frameCount = 0f;
logger.info("ST-"+memberId+": Refreshed refit tab");
}
}

if (playSound) {
Global.getSoundPlayer().playUISound("drill", 1.0f, 0.75f);

playSound = false;
}
}

@Override
public boolean runWhilePaused() {
return true;
}

@Override
public boolean isDone() {
return isDone;
}

@Override
public void cleanup() {
this.isDone = true;
}
}
We have several methods to call, and among these, 'setVariant()' is extremely important. It is what allows us to detect the changes on the refit screen on the spot. It will be explained more later, for now all I can say is that it serves as a glue/link between your real ship, and the refit one.

The other methods are pretty much self-explanatory; 'kill()' to kill a script remotely, 'refresh()' to force a refresh remotely (though, it isn't used - the idea later evolved into a static 'refreshRefitScript'), 'getMemberId()' to... get member id, and the 'initialize()', which I will go into a bit of detail.

In initialization, 'variant', 'memberId' and 'fleetTracker' is passed, and relevant fields are set. As you can see, I tried to make 'fleetTracker' more of a resource pool for shipTrackers, using the same logger, and same robot. Robot is what we need to refresh the screen, and creating a new robot for every shipTracker seemed inefficient (just like my... code, heyooo! :D ). Another important thing is, the initial hullMods are recorded, which is necessary - otherwise every mod will be considered a new one in the 'advance()'. As I only want to track my own hullmods, there is a check in that loop, and any hullmod whose id doesn't start with a certain affix, I discard.

In the 'advance()', the initial tab check is pretty important, because it's there that the script will be killed. It is pretty straightforward: If it is the refit tab, do your thing, otherwise, die. The rest has all kinds of loops, the first loop compares the current hullmods ('variant.getHullMods()') to the recorded ones ('hullMods'), and if there's a new one, adds it to the list. If you need to keep track of certain hullmods only, this is where you need to separate the chaff from the rest, just like in the initialization. The new mods are added to their own set 'newHullMods', as well as the main one 'hullMods'.

On the next loop, the recorded 'hullMods' are checked; are they still equipped on the ship? If 'variant.hasHullMod(hullModId)' returns false, then it is removed from the 'hullMods' and added to 'removedHullMods' set.

The next loop goes over 'newHullMods' and this is where you can do stuff that will only happen once. In a way, items in this loop can be used for 'onInstalled()'. For example, if the installed hullMod's id starts with 'ehm.affix.systemRetrofit', then I set 'refresh' to true, which will be used later. Other methods and things can be done here, too. I use a switch structure just to keep it tidy, manipulate the 'hullModId' in any way you see fit.

And on the next loop, after 'onInstalled()' stuff is over, we go over the 'removedHullMods' set, and do something similar. This time, the loop is used for 'onRemoved()'. A similar example; if the removed hullMod's id starts with 'ehm.affix.systemRetrofit', then I not only set 'refresh' to true, but also call an external method (which could very well be in this file, but I didn't) that does a few extra things on the ship in question.

And finally, we arrive at the refresh area. If refresh is set to true, then we count frames, the robot presses ENTER several times (not necessarily needs to press it several times, just one is enough), then on 5th frame it sends the remaining keystrokes to refresh the screen. Essentially, the robot auto-presses ENTER(x4) then R. We also set refresh to false, reset the counter, so that the robot is ready for another refresh. Oh yeah, I also play a custom drill sound if it is set to true, it is a custom drill sound, and doesn't exist in the game. You can use the weapon-install sounds or something for that, or just remove it but it adds a tiny touch in my opinion.

4
Modding / Mod Fluff - is this too much?
« on: February 03, 2022, 02:59:57 PM »
Hi! I'm writing some useless fluff for my mod, was wondering if this is too much. There may be typos too. Anyhow, here it is:

Quote
Design Type: Experimental

This design utilizes experimental hull modifications created by a spacer who has been living in a junkyard for pretty much his entire life. His 'treasure' hoard is full of franken-ships that somehow fly by using cannibalized parts from other ships that would be deemed incompatible. Benefits of such modifications are unclear as they do not provide a certain advantage over the stock designs. However the level of customization and flexibility they offer is certainly unparalleled.

Spoiler
[close]

5
Modding / Hullmod modding... (hullmodding?) tag questions!
« on: February 01, 2022, 05:54:56 AM »
I had a few questions regarding hullmods, specificly the tags. There are a few, while some are self-explanatory, some I have no idea about. Anyone know anything about these?

-merc : I'm puzzled about this one. Is this short for 'merchant', making these mods eligible to be sold on markets or something like that? What controls that, the faction data?
-special : Not sure about this one either.
-offensive : ?
-defensive : ?
-engines : ?

I'm basicly trying to create some hullmods that'll only be sold, salvaged or looted, not used by factions (if that is possible), in other words only there for the players to use in their fleets. Are the tags merc & special the ones I'm looking for?

6
My game is located under 'D:\Program Files\Starsector\'.

If I nuke the screenshots folder in there, then try to take a screenshot, the game will crash. Doesn't matter if you create another folder with the same name. I believe as it won't have sufficient permissions to write into this folder, the OS is blocking it.

Simple fix is, reinstalling the game. Though, displaying an error message instead of crashing would be ideal, in my opinion =)

7
General Discussion / Ship pack mods that you'd suggest?
« on: January 24, 2022, 01:46:57 AM »
Hello everyone, how's it going? Sorry for cluttering the forums with this question, but can you recommend a ship pack that extends the basegame with a vanilla-like style? No factions if possible, none or minimal amount of new weapons, new hullmods are somewhat fine, mostly just new ships to toy around with. I've tried a few of them, but something felt off, and most I've tried was nearly entirely new with a faction and all. After that I simply gave up. Any suggestions would be appreciated :D Have a nice day!

8
General Discussion / Campaign Terrain/Movement Feedback
« on: January 21, 2022, 07:43:30 AM »
Going from point A to point B is a chore in this game. Let me elaborate. Any kind of control impairment is something I absolutely hate in any game. Loss of control, sluggish controls, slow effects, etc... I absolutely hate. And this game is full of these.

• Sustained Burn
Sustained Burn by design adds sluggishness to the controls,
However since it trades precision control with speed I'm somewhat ok with it

• Nebula
Negatively affect your speed with its severity depending on your fleet size
To negate its effects as much as possible, have to fly with a smaller fleet / grab relevant skills, fill your fleed with tugs (maybe). Some solutions invite more hostiles

• 'Hyperclouds'
Same thing as nebulae, however in contrast, covers a big portion of the map
Again, either fly with a smaller fleet, or manually avoid the clouds - which is impractical due to how much hyperspace is occupied by these

• Asteroid fields
You have a chance to collide with one, which not only slows you down, but also throws you off course (especially with sustained burn)
Going slow is safe, however going slow is the problem

• Hyperstorms
Acts like a 'reverse asteroid'; catapulting you forward in somewhat uncontrollable way, and also damaging your ships
Going slow is again safe, and again going slow itself is the problem

• Slipstreams
Supposed to be a 'hyperspace highways' I think, though I've yet to see one that's going to a direction that I'm headed towards
In order to cross one, you need to turn sustained burn off, and depending on the current's speed, you might even need to do an emergency burn to get out without getting way off-course

Now, why have I listed all these? They're the things that I've mentioned earlier; the things I absolutely hate. I don't mind them that much when they are a rare occurrence, like in the system maps, but when they're what feels like 90% of the map in hyperspace, going from point A to point B becomes a chore.

You need bigger fleets to take on bigger enemies, and as you add more combat ships, you also need to add more logistics ships. In essence you get slower and slower. Sure, you can add like 10 tugs to carry your fleet, but then you'll have to resupply like every month to keep up with the drain. You'll have to deal either with the logistics, or the slow movement, one way or another. There are a few things that help with these penalties, mitigating their effects to a degree. However, considering how hyperspace is full of these terrain types, small mitigations are not enough in my opinion.

There are a lot of stuff that unnecessarily limits the pace you travel at, and a few things that are supposed to help you in your travels like the slipstreams or storm boosts you have no control over. As I've mentioned while talking about slipstream above, I've yet to see one that goes to somewhere I'm headed to, and like 99% of the time, it's just another obstacle that I need to avoid, and nothing more. Adds nothing to the gameplay for me, just does the opposite.

I'm aware that there are mods to get rid of hyperspace clouds, or even add a warp function to make getting from A to B faster. However, as far as vanilla experience goes, even at 2x speed the game gets extremely boring in transit. It still is boring in 3x speed.

Those all were the symptoms that I've experienced, and how they all made me feel. Now here are a few suggestions that might make the whole thing better:
• A bigger base map, more distance between the systems, less clouds in between, and even less storms. The whole hyperspace feels extremely concentrated right now, diluting it would help immensely so to speak.
• Tighter control over sustained burn, and / or less control loss when colliding with an asteroid or a storm. Less of an impact when you don't slow down, so to speak.
• A skill to enter the slipstreams, or a toggle to avoid them when you are not going to use them. They look cool, but they don't play nice in my opinion. Maybe somehow manually initiating them to get to wherever you are going faster? They're rare anyway, so it's not a big issue.
• More tools to deal with navigational hazards! For example, a utility ship similar to tug, or a hullmod to reduce the effects of navigational hazards to a degree, just like the 'Navigation' skill, perhaps stacking with a diminishing return. Mitigation is fine as long as they're not encountered on a constant basis!

These are just a few suggestions that might make the vanilla experience better in my opinion. Anyhow, thanks for reading, have a nice day :)

9
Modding / Faster Save / Load / Boot Times ('settings.json' options)
« on: January 19, 2022, 07:31:23 PM »
This isn't really a mod, it's just a tweak to the vanilla 'settings.json' options that allows faster save / load / boot times. You can download this and use it as another mod, modify your vanilla 'settings.json' file (not recommended), or add it to / merge it with another mod (recommended).

Code: settings.json
{
    # Smaller files, faster saving/loading
    # Default: false
    "compressSaveGameData":true,

    # Caches compiled scripts in saves/cache, faster startup time after first run
    # Default: false
    "enableScriptCaching":true,
}
Adding the changes to / merging them another mod
• Navigate to your (chosen) mod's '\data\config' folder (if the folder doesn't exist, create it)
• Copy the 'settings.json' file I've provided to this folder OR create a file named 'settings.json', and paste the snippet I've put above in it
• If there already is a 'settings.json' file that the mod provides, paste the snippet (without braces) above in the mod's existing one (within the braces)

Some mods that (may) already have a 'settings.json' file: 'Transponder Off' & 'QoL Pack'. Aside from these two, the faster save / load times pair very well with 'Autosave', especially when you turn the autosave feature on. You can put the changes above in one of these mods' 'settings.json' file, so to speak.
[close]
Why is it faster? Shouldn't it be slower?
Think of it this way: Lets say you can eat a big marshmallow that doesn't even fit into your mouth in a minute (not sure why you'd want to do that, but as far as analogy goes, the best I could come up with). If you apply some force and make it smaller, you could do it faster. It wouldn't be easier to chew, but you can eat it in a shorter time as less bites will be necessary.

Another analogy I could come up with is, compressed lossless files vs uncompressed files. Imagine downloading two images, one of each type. After the decompression, both are exactly the same, yet one takes a second to download and view, while the other could take much longer.

You may think it should be slower as your computer also needs to decompress it, but loading compressed data, decompressing and processing it is faster than simply loading and processing it, as you'll have to spend more cycles on the loading part, in a way.
[close]

10
Suggestions / Regarding to Combat Pass of 0.65.2a (In Development)
« on: January 12, 2015, 12:04:39 AM »
Unlimited Ballistic Ammo & Regenerating Missile Ammo
Personally, I don't like these changes one bit. And here is why. But before that, you need to understand how I play, and you'll most likely see why I don't like the changes at all. I play as a bounty hunter, at most 2D+1F fleet, rather small so to speak. I am able to take on pretty much anything in one go, except fleets with capital ships, which requires me to fight a battle of attrition.

Attrition you ask? If the enemy capital ship has a lot of ballistic/missile weapons, and just a few energy weapons, the battles sometimes drag on a lot. First thing I do is get rid of any escorts, then let the capital ship run out of ammo. When the enemy capital ship is out of ammo, most of the time it is forced to retreat, where I keep pushing on and eventually they have just a little bit of CR left, while I still have loads. Malfunctions here and there on the enemy ships, while mine are working fine. And eventually the enemy succumbs to the pressure.

And enemy missile boats. Have you seen a lot of these, and them firing at the same time? Lower your shields for a second, and you are dead. Overload, and you are dead. Keep your distance, be patient, and enemy runs out of missiles, which is an opening you've been waiting for. Also, I've run out of ammo more than a few times, which forced me to retreat or call in reinforcements. Contrary to what people thing, you can run out of ammo.

I can talk about many other examples that changes gameplay just because some weapons have limited ammo. But these examples have one common point: small fleets trying to take on bigger ones. Small ships trying to take on bigger ones. So my suggestions are: don't remove the ammo limit; don't make missiles regenerate. Instead, if you really want to adapt these, add two hull modifications instead; one for each, which would manufacture ammunition in combat. But make ammo limit still count for something, even if these modifications are present on the ship.

Flux-to-energy damage
I also don't like this change. Whenever I am using energy weapons, I often try to stay on high flux zone in order to receive a substantial damage bonus. One wild volley hitting my shields would mean overload for me, but that is the price to pay. More than 50% of the times that I am exchanging fire, I am well over 50-60% flux in order to get a bonus to keep the pressure on the enemy.

The main reason
The main reason I don't like these changes is, it takes away what makes each type of weapon unique (flux bonus for energy weapons, limited ammo for ballistics & missiles). I really think it is a mistake. We can always get a mod that "reverts" these changes, but still, please consider what you are doing.

11
Bug Reports & Support / AI Behaviour
« on: February 19, 2012, 08:05:39 AM »
First of all, thank you for adding strafe-only keys.

-AI Retreat (bug/exploit)
I'm playing in Campaign mode. I am going lonewolf till I raise some money, so I decided to use a Medusa-class destroyer, with additional agility added to it. The problem is, some enemy fleets using ammunition based weapons (missiles, mostly) tend to retreat after they run out of ammunition. I noticed this when I was engaging a bigger fleet (where capture points are enabled on the map), then tested it again. When I tested it, I just avoided them & got rid of the missiles, and after they run out of ammunition, they retreated - some ships even surrendered.

The problem is, they also have other weapons rather than just missiles, and they can swarm me if they want. As far as I can tell, Pirates invest heavily in missiles, and you can grab their ships for free by doing this, without even getting a single-point armor damage.

-AI Caution
The other problem is, AI tends to raise shields if you have a weapon that can reach them. While this sounds awesome, the speed lost due to raised shields can be devastating for them. Since I'm using a single ship, all I have to do is face my enemies for a while, and they raise shields, and sometimes try to move away from my reach, interrupting a potential swarming chance.

Pages: [1]