I've been tearing my hair out for about three weeks trying to figure out why I can't make (what I thought was) a pretty simple setup work, and I've come to the conclusion that there's something very strange and fundamental going on in the core game that's preventing me from achieving this. Whether it is a bug or not I have no clue, but I would very much like it to be looked into either way, as it makes zero sense. Regardless, that's why I've stuck it here in Bug Reports. If a moderator would rather it be in the Modded subforum, that's fine by me, as long as it gets addressed, or at the very least explained.
Situation/objective:
I have a decorative weapon with multiple animation frames on a ship. The ship also has two unique hullmods: one built-in and hidden, the other modular and visible. The built-in hullmod does all the work. If the player tries to remove the modular hullmod, the built-in one notices this as a trigger, and "does something", before manually re-installing the modular hullmod. The result is a "cyclic" function that lets the player toggle through a series of options by repeatedly removing the modular hullmod. Presently, that series of options is the series of animation frames for the decorative weapon. (This is all cosmetic for now.)
Problem:
No matter which way I slice this damn system, I cannot get the deco weapon to change animation frame in the way I want it. I can forcibly set it to a particular value if I just stick a hard number in the script, but the moment I put in a saved number attached to the ship ID (fleetMemberId actually), it refuses to change.
Worse, I've set up a logger so I can see if the saved value is incrementing... And it goddamn IS! For reasons beyond my comprehension, this line...
wpn.getAnimation().setFrame(SAVED_DATA.get(shipId));
... does not work.
For full context, here's the whole of the meat of the script (this is the built-in hullmod). "dara_arrow_paintcycle_trigger" is the modular hullmod that the player removes to cycle the setup.
@Override
public void applyEffectsAfterShipCreation(ShipAPI ship, String id) {
String shipId = ship.getFleetMemberId();
if (SAVED_DATA.get(shipId) == null) {
SAVED_DATA.put(shipId, 0);
log.info("Reset frame to #0");
}
if (!ship.getVariant().hasHullMod("dara_arrow_paintcycle_trigger")) {
for (WeaponAPI wpn : ship.getAllWeapons()) {
if (wpn.getId().contains("dara_arrow_paint")) {
int currFrame = SAVED_DATA.get(shipId);
log.info("Retrieved frame #" + currFrame);
if (currFrame + 1 > 12) {
currFrame = 0;
} else {
currFrame += 1;
}
SAVED_DATA.put(shipId, currFrame);
log.info("Incremented to and saved frame #" + SAVED_DATA.get(shipId));
wpn.getAnimation().setFrame(SAVED_DATA.get(shipId));
ship.getVariant().addMod("dara_arrow_paintcycle_trigger");
runOnce = false;
break;
}
}
}
}
As you can see, I've got multiple log outputs here, which have very clearly been showing the saved value incrementing, live, as I try to cycle the hullmod from the refit screen:
98010 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Reset frame to #0
98010 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Retrieved frame #0
98010 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Incremented to and saved frame #1
98977 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Retrieved frame #1
98977 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Incremented to and saved frame #2
113244 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Retrieved frame #2
113245 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Incremented to and saved frame #3
114526 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Retrieved frame #3
114527 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Incremented to and saved frame #4
127248 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Retrieved frame #4
127248 [Thread-4] INFO data.scripts.hullmods.dara_RedArrowPaint - Incremented to and saved frame #5
And yet, in the refit screen, the animation frame of the weapon does not change.
YES the deco weapon has the right frames setup and is visible and all that jazz. I've tried multiple ways to ensure this, everything from manually giving the setFrame() call values for each one, to just making the animation have a non-zero frame rate and watching it go through each one from the refit screen.
NO the frame does not update in combat. It refuses to work anywhere... Except for the codex. If you repeatedly click the ship's entry in the codex, you can see the image of the ship cycling through the animation frames exactly as I want it to elsewhere. What the hell!?
The worst bit about all of this? I KNOW that you CAN make a system just like this that manually changes weapon animation frame on a cyclic basis, because I've already done it once. I'm not annoyed because it looks like this is impossible: I'm annoyed because I know it IS possible, and I cannot understand why I'm unable to make it work a second time.
I'm at my wit's end with this damn thing, tried about five different methods to make it work, and I'm |this close| to giving up. Help is sorely, desperately wanted.
1. If you set it to frame 0, 1, 2, etc. by fiat... that works as intended, correct?
Yes.
2. If that's working, then we're dealing with an issue of saved data.
Do you have a data-source where you're storing that data when the Campaign gets loaded? This SAVED_DATA appears to be the place. How's that set up? From what you're saying thus far, it sounds like maybe you're trying to get that number to increment, but write access to that data might be one-way.
SAVED_DATA is a Hashmap.
private Map<String, Integer> SAVED_DATA = new HashMap<>();
Access is definitely read and write. The log outputs prove that.
3. There is a lot in that code that could be improved.
Like, if the FleetMemberAPI doesn't have the Hull Mod, it doesn't need to be in SAVED_DATA at all (if anything, it should be in a globally-accessible HashSet so it never gets past the first check again).
The code extract in the OP is from the hullmod in question. It only applies to those ships using this system.
You should not use a break statement; that is unnecessary and might be problematic unless a ship may have more than one dara_arrow_paint (as the code's written, it looks pretty obvious that you're expecting one, and only one, to be installed). In general, only use break when, and only when, you really absolutely have to and know exactly what loop(s) you may be exiting as a result.
The break probably isn't necessary, it's mostly there as a safeguard against I-don't-know-what. What are the issues with having one there though?
You can use a globally-accessible HashMap to store <FleetMemberAPI,currentFrameState>.
This is pretty similar to what we have already, except we're storing the FleetMemberAPI ID as a string, rather than the whole API instance.
Yes.
That's cool. Didn't know we could mess with it there :)
The break probably isn't necessary, it's mostly there as a safeguard against I-don't-know-what. What are the issues with having one there though?
It's probably safe there... but a break can, well, break things in weird ways, because of how it skips to the next operation. I avoid it in favor of <boolean check, continue if true / false> or return instead, for things like this, where we're more worried about safety than speed. I can count the number of times I've used break outside of switches without running out of fingers and toes, honestly.
private Map<String, Integer> SAVED_DATA = new HashMap<>();
So, you're storing this in the Hull Mod's code?
I'd suggest another class, instantiated at runtime, like the modPlugin, where you know it's instantiated and, if it has some data pulled in from the save-file to populate that HashMap that will get accessed, it won't get GC'd.
Another possibility is that the FleetMemberAPI ID's not stable; maybe it's getting re-built as a result of other things?
From what you've said thus far, it appears that the big problem's not that wpn.getAnimation().setFrame() doesn't work in that context, it's just not getting the right integer.
Here, try this before giving up.
First, write a non-transient campaignPlugin to store the stuff outside the Hull Mod, etc.
package data.scripts;
public class my_neato_CampaignPlugin extends BaseCampaignPlugin {
public static Map<String,Integer> myFrameSet = new HashMap<>();
public static List<String> isSpecial = new ArrayList<>();
public static List<String> notSpecial = new ArrayList<>();
@Override
public String getId() {
return "my_neato_CampaignPlugin"; // make sure to change this for your mod
}
// This plugin is not marked transient, because it saves data in the savefile.
// Because of that, a savegame that ran with this plugin would not load properly
// after this mod is disabled.
@Override
public boolean isTransient() {
return false;
}
}
Ok, the above covers having a data source that should get stored with Campaign states, IIRC. It also provides a hook for your code. So, something like this:
@Override
public void applyEffectsAfterShipCreation(ShipAPI ship, String id) {
String fleetMemberId = ship.getFleetMemberId();
//Use an additional invisible Hull Mod's presence to halt useless operations.
if (!ship.getVariant().hasHullMod("check_for_this_hullmod")) {
my_neato_CampaignPlugin.notSpecial.put(fleetMemberId);
return;
} else {
my_neato_CampaignPlugin.isSpecial.put(fleetMemberId);
}
//Only do stuff if we have that special invisible Hull Mod.
if(my_neato_CampaignPlugin.isSpecial.contains(fleetMemberId)){
//Ship is missing the visible Hull Mod that's used as a logic controller
if (!ship.getVariant().hasHullMod("dara_arrow_paintcycle_trigger")) {
boolean stop = false;
for (WeaponAPI wpn : ship.getAllWeapons()) {
if(stop) continue;//Safely ignore rest of loop
if (wpn.getId().equals("dara_arrow_paint")) {
int currFrame = my_neato_CampaignPlugin.myFrameSet.get(fleetMemberId);
log.info("Retrieved frame #" + currFrame);
if (currFrame + 1 > 12) {
currFrame = 0;
} else {
currFrame += 1;
}
my_neato_CampaignPlugin.myFrameSet.put(fleetMemberId, currFrame);
log.info("Incremented to and saved frame #" + currFrame);
wpn.getAnimation().setFrame(currFrame);
ship.getVariant().addMod("dara_arrow_paintcycle_trigger");
}
}
}
}
}