Fractal Softworks Forum

Please login or register.

Login with username, password and session length
Advanced search  

News:

Starsector 0.97a is out! (02/02/24); New blog post: Simulator Enhancements (03/13/24)

Author Topic: starfarer.api Hullmods/Skill modifications  (Read 1739 times)

Ranakastrasz

  • Admiral
  • *****
  • Posts: 702
  • Prince Corwin of Amber
    • View Profile
starfarer.api Hullmods/Skill modifications
« on: June 24, 2021, 09:01:23 AM »

I am trying to modify the range of several effects in the game, but am unsure how to do so.
The skill effects, as well as some hull mods and ship abilities appear to be inside the starfarer.api.zip, which is bizarre, and imitating that in my mod structure, with or without the zip appears to do nothing.

What am I doing wrong here?
Logged
I think is easy for Simba and Mufasa sing the Circle of Life when they're on the top of the food chain, I bet the zebras hate that song.

Cigarettes are a lot like hamsters. Perfectly harmless, until you put one in your mouth and light it on fire

SafariJohn

  • Admiral
  • *****
  • Posts: 3010
    • View Profile
Re: starfarer.api Hullmods/Skill modifications
« Reply #1 on: June 24, 2021, 09:24:39 AM »

Do your own versions of the effect classes, then reference them in the appropriate data locations.

- Ship ability scripts are referenced in the .system files in data/shipsystems
- Hull mod scripts are referenced in data/hullmods/hull_mods.csv
- Skill scripts are referenced in the .skill files in data/characters/skills
Logged

Ranakastrasz

  • Admiral
  • *****
  • Posts: 702
  • Prince Corwin of Amber
    • View Profile
Re: starfarer.api Hullmods/Skill modifications
« Reply #2 on: June 24, 2021, 08:21:22 PM »

After relocating the "Escort Package" Hullmod, it errored out based on the "MilitarizedSubsystems" reference. I tried an include for "com.fs.starfarer.api.impl.hullmods", the old package name, and also tried using that as a prefix for all "MilitarizedSubsystems" references in the code, but still errors out.


Edit: Actually, what is going on. What I did before, was make a copy, in the corresponding folder. Does starsector search all folders when compiling, and/or build relationships based on the packages? Not sufficiently familier with how Java works exactly, compared to other languages.

Before, I didn't change the package name, expecting it to overwrite the base files. Now, when I renamed it, and made a new reference, that should link it, but given that it was referencing the old package and class name, why didn't that work?

Specifically, It seems that renaming the packages results in it not seeing, presumably, the rest of those packages. So I am confused, and believe I misunderstood what you meant.

I actually WAS doing my own versions of the effect classes, and it wasn't working.
« Last Edit: June 24, 2021, 08:30:11 PM by Ranakastrasz »
Logged
I think is easy for Simba and Mufasa sing the Circle of Life when they're on the top of the food chain, I bet the zebras hate that song.

Cigarettes are a lot like hamsters. Perfectly harmless, until you put one in your mouth and light it on fire

Morrokain

  • Admiral
  • *****
  • Posts: 2143
  • Megalith Dreadnought - Archean Order
    • View Profile
Re: starfarer.api Hullmods/Skill modifications
« Reply #3 on: June 24, 2021, 09:25:49 PM »

Here is an example of me overriding a weapon effect:

1 - In your custom weapon file, add your new effect.

   "specClass":"beam",
   "id":"archean_riftcascade",
   "beamEffect":"data.scripts.weapons.ArcheusRiftCascadeEffect",
   "type":"ENERGY",
   "size":"LARGE",

 - The effect portion points to the new effect which has a new name. The rest is for location reference.

2 - Define the new effect. For instance, copy the old effect and paste it in a new location. I compile the script in a jar (which is added to the standard scripts by defining the relevant jar in mod_info.JSON -    "jars": ["Archean Order TC v0.9.1a.jar"],)

 - It doesn't have to be in a jar iirc, it just needs to be under the specified path. So, "data.scripts.weapons" could actually be linked to "data\scripts\weapons" in the base mod directory that is comprised of folders starting at the "data" level of the mod:

Spoiler
Base Directory:

Directory Level 2 after clicking on the "data" folder:

[close]

Although keep in mind that a jar is better as far as saving system resources goes - I believe.

3 - Then you just need to define the new weapon, etc.

If this isn't a mod, and is a personal edit, you can simply edit the core hullmod entry in hullmods.csv to point to the newly added path in the starsector-core directory where you have added the script with the changes. It just needs a different name and needs to be linked to the new path.

Specifically for the Escort/Assault Package, it gets tricky as they rely on Militarized Subsystems being installed and have a direct reference by Id. If you post your code I can assist further.

Anyway I wrote this in a big hurry so if it doesn't make sense let me know and I'll try and make it more clear. Good luck!
Logged

Ranakastrasz

  • Admiral
  • *****
  • Posts: 702
  • Prince Corwin of Amber
    • View Profile
Re: starfarer.api Hullmods/Skill modifications
« Reply #4 on: June 24, 2021, 09:48:33 PM »

Got it to work. Thanks for the help.
Logged
I think is easy for Simba and Mufasa sing the Circle of Life when they're on the top of the food chain, I bet the zebras hate that song.

Cigarettes are a lot like hamsters. Perfectly harmless, until you put one in your mouth and light it on fire

Morrokain

  • Admiral
  • *****
  • Posts: 2143
  • Megalith Dreadnought - Archean Order
    • View Profile
Re: starfarer.api Hullmods/Skill modifications
« Reply #5 on: June 24, 2021, 10:31:58 PM »

*thumbs up* Nice! Happy to help.
Logged

Ranakastrasz

  • Admiral
  • *****
  • Posts: 702
  • Prince Corwin of Amber
    • View Profile
Re: starfarer.api Hullmods/Skill modifications
« Reply #6 on: June 25, 2021, 09:32:36 AM »

The Minestrike Ship System is not working, and the error doesn't make sense.
This implies that "WeightedRandomPicker" isn't defined, but that was already an include, so shouldn't be an issue.

Quote
java.lang.RuntimeException: Error loading [data.shipsystems.scripts.MineStrikeStats]
   at com.fs.starfarer.loading.scripts.ScriptStore$3.run(Unknown Source)
   at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: File 'data/shipsystems/scripts/MineStrikeStats.java', Line 238, Column 9: Assignment conversion not possible from type "java.lang.Object" to type "org.lwjgl.util.vector.Vector2f"

Spoiler
package data.shipsystems.scripts;

import java.awt.Color;
import java.util.List;

import org.lwjgl.util.vector.Vector2f;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.MutableShipStatsAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipSystemAPI;
import com.fs.starfarer.api.combat.ShipSystemAPI.SystemState;
import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags;
import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
import com.fs.starfarer.api.input.InputEventAPI;
import com.fs.starfarer.api.util.Misc;
import com.fs.starfarer.api.util.WeightedRandomPicker;
import com.fs.starfarer.api.impl.combat.BaseShipSystemScript;
import com.fs.starfarer.api.impl.combat.MineStrikeStatsAIInfoProvider;

public class MineStrikeStats extends BaseShipSystemScript implements MineStrikeStatsAIInfoProvider {
   
   protected static float MINE_RANGE = 3000f;
   
   public static final float MIN_SPAWN_DIST = 150f;
   public static final float MIN_SPAWN_DIST_FRIGATE = 220f;
   
   public static final float LIVE_TIME = 5f;
   
   public static final Color JITTER_COLOR = new Color(255,155,255,75);
   public static final Color JITTER_UNDER_COLOR = new Color(255,155,255,155);

   
   public static float getRange(ShipAPI ship) {
      if (ship == null) return MINE_RANGE;
      return ship.getMutableStats().getSystemRangeBonus().computeEffective(MINE_RANGE);
   }
   
   public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
      ShipAPI ship = null;
      //boolean player = false;
      if (stats.getEntity() instanceof ShipAPI) {
         ship = (ShipAPI) stats.getEntity();
      } else {
         return;
      }
      
      
      float jitterLevel = effectLevel;
      if (state == State.OUT) {
         jitterLevel *= jitterLevel;
      }
      float maxRangeBonus = 25f;
      float jitterRangeBonus = jitterLevel * maxRangeBonus;
      if (state == State.OUT) {
      }
      
      ship.setJitterUnder(this, JITTER_UNDER_COLOR, jitterLevel, 11, 0f, 3f + jitterRangeBonus);
      ship.setJitter(this, JITTER_COLOR, jitterLevel, 4, 0f, 0 + jitterRangeBonus);
      
      if (state == State.IN) {
      } else if (effectLevel >= 1) {
         Vector2f target = ship.getMouseTarget();
         if (ship.getShipAI() != null && ship.getAIFlags().hasFlag(AIFlags.SYSTEM_TARGET_COORDS)){
            target = (Vector2f) ship.getAIFlags().getCustom(AIFlags.SYSTEM_TARGET_COORDS);
         }
         if (target != null) {
            float dist = Misc.getDistance(ship.getLocation(), target);
            float max = getMaxRange(ship) + ship.getCollisionRadius();
            if (dist > max) {
               float dir = Misc.getAngleInDegrees(ship.getLocation(), target);
               target = Misc.getUnitVectorAtDegreeAngle(dir);
               target.scale(max);
               Vector2f.add(target, ship.getLocation(), target);
            }
            
            target = findClearLocation(ship, target);
            
            if (target != null) {
               spawnMine(ship, target);
            }
         }
         
      } else if (state == State.OUT ) {
      }
   }
   
   
   public void unapply(MutableShipStatsAPI stats, String id) {
   }
   
   public void spawnMine(ShipAPI source, Vector2f mineLoc) {
      CombatEngineAPI engine = Global.getCombatEngine();
      Vector2f currLoc = Misc.getPointAtRadius(mineLoc, 30f + (float) Math.random() * 30f);
      //Vector2f currLoc = null;
      float start = (float) Math.random() * 360f;
      for (float angle = start; angle < start + 390; angle += 30f) {
         if (angle != start) {
            Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle);
            loc.scale(50f + (float) Math.random() * 30f);
            currLoc = Vector2f.add(mineLoc, loc, new Vector2f());
         }
         for (MissileAPI other : Global.getCombatEngine().getMissiles()) {
            if (!other.isMine()) continue;
            
            float dist = Misc.getDistance(currLoc, other.getLocation());
            if (dist < other.getCollisionRadius() + 40f) {
               currLoc = null;
               break;
            }
         }
         if (currLoc != null) {
            break;
         }
      }
      if (currLoc == null) {
         currLoc = Misc.getPointAtRadius(mineLoc, 30f + (float) Math.random() * 30f);
      }
      
      
      
      //Vector2f currLoc = mineLoc;
      MissileAPI mine = (MissileAPI) engine.spawnProjectile(source, null,
                                               "minelayer2",
                                               currLoc,
                                               (float) Math.random() * 360f, null);
      if (source != null) {
         Global.getCombatEngine().applyDamageModifiersToSpawnedProjectileWithNullWeapon(
                                 source, WeaponType.MISSILE, false, mine.getDamage());
//         float extraDamageMult = source.getMutableStats().getMissileWeaponDamageMult().getModifiedValue();
//         mine.getDamage().setMultiplier(mine.getDamage().getMultiplier() * extraDamageMult);
      }
      
      
      float fadeInTime = 0.5f;
      mine.getVelocity().scale(0);
      mine.fadeOutThenIn(fadeInTime);
      
      Global.getCombatEngine().addPlugin(createMissileJitterPlugin(mine, fadeInTime));
      
      //mine.setFlightTime((float) Math.random());
      float liveTime = LIVE_TIME;
      //liveTime = 0.01f;
      mine.setFlightTime(mine.getMaxFlightTime() - liveTime);
      
      Global.getSoundPlayer().playSound("mine_teleport", 1f, 1f, mine.getLocation(), mine.getVelocity());
   }
   
   protected EveryFrameCombatPlugin createMissileJitterPlugin(final MissileAPI mine, final float fadeInTime) {
      return new BaseEveryFrameCombatPlugin() {
         float elapsed = 0f;
         @Override
         public void advance(float amount, List<InputEventAPI> events) {
            if (Global.getCombatEngine().isPaused()) return;
         
            elapsed += amount;
            
            float jitterLevel = mine.getCurrentBaseAlpha();
            if (jitterLevel < 0.5f) {
               jitterLevel *= 2f;
            } else {
               jitterLevel = (1f - jitterLevel) * 2f;
            }
            
            float jitterRange = 1f - mine.getCurrentBaseAlpha();
            //jitterRange = (float) Math.sqrt(jitterRange);
            float maxRangeBonus = 50f;
            float jitterRangeBonus = jitterRange * maxRangeBonus;
            Color c = JITTER_UNDER_COLOR;
            c = Misc.setAlpha(c, 70);
            //mine.setJitter(this, c, jitterLevel, 15, jitterRangeBonus * 0.1f, jitterRangeBonus);
            mine.setJitter(this, c, jitterLevel, 15, jitterRangeBonus * 0, jitterRangeBonus);
            
            if (jitterLevel >= 1 || elapsed > fadeInTime) {
               Global.getCombatEngine().removePlugin(this);
            }
         }
      };
   }
   
   
   protected float getMaxRange(ShipAPI ship) {
      return getMineRange(ship);
   }

   
   @Override
   public String getInfoText(ShipSystemAPI system, ShipAPI ship) {
      if (system.isOutOfAmmo()) return null;
      if (system.getState() != SystemState.IDLE) return null;
      
      Vector2f target = ship.getMouseTarget();
      if (target != null) {
         float dist = Misc.getDistance(ship.getLocation(), target);
         float max = getMaxRange(ship) + ship.getCollisionRadius();
         if (dist > max) {
            return "OUT OF RANGE";
         } else {
            return "READY";
         }
      }
      return null;
   }

   
   @Override
   public boolean isUsable(ShipSystemAPI system, ShipAPI ship) {
      return ship.getMouseTarget() != null;
   }
   
   
   private Vector2f findClearLocation(ShipAPI ship, Vector2f dest) {
      if (isLocationClear(dest)) return dest;
      
      float incr = 50f;

      WeightedRandomPicker<Vector2f> tested = new WeightedRandomPicker<Vector2f>();
      for (float distIndex = 1; distIndex <= 32f; distIndex *= 2f) {
         float start = (float) Math.random() * 360f;
         for (float angle = start; angle < start + 360; angle += 60f) {
            Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle);
            loc.scale(incr * distIndex);
            Vector2f.add(dest, loc, loc);
            tested.add(loc);
            if (isLocationClear(loc)) {
               return loc;
            }
         }
      }
      
      if (tested.isEmpty()) return dest; // shouldn't happen
      
      return tested.pick();
   }
   
   private boolean isLocationClear(Vector2f loc) {
      for (ShipAPI other : Global.getCombatEngine().getShips()) {
         if (other.isShuttlePod()) continue;
         if (other.isFighter()) continue;
         
//         Vector2f otherLoc = other.getLocation();
//         float otherR = other.getCollisionRadius();
         
         Vector2f otherLoc = other.getShieldCenterEvenIfNoShield();
         float otherR = other.getShieldRadiusEvenIfNoShield();
         
         
//         float dist = Misc.getDistance(loc, other.getLocation());
//         float r = other.getCollisionRadius();
         float dist = Misc.getDistance(loc, otherLoc);
         float r = otherR;
         //r = Math.min(r, Misc.getTargetingRadius(loc, other, false) + r * 0.25f);
         float checkDist = MIN_SPAWN_DIST;
         if (other.isFrigate()) checkDist = MIN_SPAWN_DIST_FRIGATE;
         if (dist < r + checkDist) {
            return false;
         }
      }
      for (CombatEntityAPI other : Global.getCombatEngine().getAsteroids()) {
         float dist = Misc.getDistance(loc, other.getLocation());
         if (dist < other.getCollisionRadius() + MIN_SPAWN_DIST) {
            return false;
         }
      }
      
      return true;
   }


   public float getFuseTime() {
      return 3f;
   }


   public float getMineRange(ShipAPI ship) {
      return getRange(ship);
      //return MINE_RANGE;
   }

   
}







[close]

While I'm at it, is there an easy way to flag a file to prevent it from being included, because I would rather not having to keep moving them to a dummy folder.
« Last Edit: June 25, 2021, 09:49:05 AM by Ranakastrasz »
Logged
I think is easy for Simba and Mufasa sing the Circle of Life when they're on the top of the food chain, I bet the zebras hate that song.

Cigarettes are a lot like hamsters. Perfectly harmless, until you put one in your mouth and light it on fire

Ranakastrasz

  • Admiral
  • *****
  • Posts: 702
  • Prince Corwin of Amber
    • View Profile
Re: starfarer.api Hullmods/Skill modifications
« Reply #7 on: June 25, 2021, 08:46:58 PM »

Spoiler

[close]
My modification to skills seems to have resulted in duplicated tooltips, and presumably duplicated effects as well.
I believe this means it is applying both the old and the new, but the ".skill" file is the same location and ID and everything, and should be redirecting to the new .java file.
Logged
I think is easy for Simba and Mufasa sing the Circle of Life when they're on the top of the food chain, I bet the zebras hate that song.

Cigarettes are a lot like hamsters. Perfectly harmless, until you put one in your mouth and light it on fire

Morrokain

  • Admiral
  • *****
  • Posts: 2143
  • Megalith Dreadnought - Archean Order
    • View Profile
Re: starfarer.api Hullmods/Skill modifications
« Reply #8 on: June 26, 2021, 08:38:09 AM »

Try casting the pick to a vector using:
Code
 return (Vector2f) tested.pick()

I don't think there is a way to prevent files from being loaded.

For your skills, you can prevent the duplication by replacing the skill file using a mod_info.JSON I *think* that would be all you need to have the "mod" selectable:

Code
  "replace":[
     "data/characters/skills/carrier_group.skill",
]

Logged