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)

Pages: 1 ... 7 8 [9] 10

Author Topic: The Radioactive Code Dump  (Read 80456 times)

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #120 on: October 15, 2015, 07:37:42 AM »

Yet another missile AI, this time with all the knobs and switches you need to customize it anyway you want. No Java skill needed, but it require to be compiled.
Customizable features:
  • ECCM dependent target leading.
  • Different target selection priorities.
  • Option to reengage a new target when the current one got killed.
  • Controllable vanilla-like waving.
  • Oversteering behavior that tries to orient the velocity toward the leading point instead of just pointing the missile at it.
Tested on a hundred missiles at once with no noticeable performance impact.

Spoiler
Code: Java
//By Tartiflette, highly customizable Missile AI.
package data.scripts.ai;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.GuidedMissileAI;
import com.fs.starfarer.api.combat.MissileAIPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipCommand;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.lazywizard.lazylib.CollectionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;

public class CustomMissileAI implements MissileAIPlugin, GuidedMissileAI {
         
   
    //////////////////////
    //     SETTINGS     //
    //////////////////////
   
    //Angle with the target beyond which the missile turn around without accelerating. Avoid endless circling.
    //  Set to a negative value to disable
    private final float OVERSHOT_ANGLE=60;
   
    //Time to complete a wave in seconds.
    private final float WAVE_TIME=2;
   
    //Angle of the waving in degree (divided by 3 with ECCM). Set to a negative value to avoid all waving.
    private final float WAVE_AMPLITUDE=15;
   
    //Damping of the turn speed when closing on the desired aim. The smaller the snappier.
    private final float DAMPING=0.1f;
   
    //Does the missile try to correct it's velocity vector as fast as possible or just point to the desired direction and drift a bit?
    //  Can create strange results with large waving
    //  Require a projectile with a decent turn rate and around twice that in turn acceleration compared to their top speed
    //  Useful for slow torpedoes with low forward acceleration, or ultra precise anti-fighter missiles.     
    private final boolean OVERSTEER=false;  //REQUIRE NO OVERSHOOT ANGLE!
   
    //Does the missile switch its target if it has been destroyed?
    private final boolean TARGET_SWITCH=true;
   
    //Does the missile find a random target or aways tries to hit the ship's one?     
    //   0: No random target seeking,
    //       If the launching ship has a valid target, the missile will pursue that one.
    //       If there is no target selected, it will check for an unselected cursor target.
    //       If there is none, it will pursue its closest valid threat.   
    //   1: Local random target,
    //       If the ship has a target selected, the missile will pick a random valid threat around that one.
    //       If the ship has none, the missile will pursue a random valid threat around the cursor, or itself in AI control.   
    //   2: Full random,
    //       The missile will always seek a random valid threat around itself.
    private final Integer RANDOM_TARGET=0;
   
    //Both targeting behavior can be false for a missile that always get the ship's target or the closest one
    //Prioritize hitting fighters and drones (if false, the missile will still be able to target fighters but not drones)
    private final boolean ANTI_FIGHTER=false;    //INCOMPATIBLE WITH ASSAULT
   
    //Target the biggest threats first
    private final boolean ASSAULT=true;  //INCOMPATIBLE WITH ANTI-FIGHTER
   
    //range in which the missile seek a target in game units.
    private final float MAX_SEARCH_RANGE = 1500;
   
    //range under which the missile start to get progressively more precise in game units.
    private float PRECISION_RANGE=500;
   
    //Is the missile lead the target or tailchase it?
    private final boolean LEADING=true;
   
    //Leading loss without ECCM hullmod. The higher, the less accurate the leading calculation will be.
    //   1: perfect leading with and without ECCM
    //   2: half precision without ECCM
    //   3: a third as precise without ECCM. Default
    //   4, 5, 6 etc : 1/4th, 1/5th, 1/6th etc precision.
    private float ECCM=3;   //A VALUE BELOW 1 WILL PREVENT THE MISSILE FROM EVER HITTING ITS TARGET!
   
   
    //////////////////////
    //    VARIABLES     //
    //////////////////////
   
    //max speed of the missile after modifiers.
    private final float MAX_SPEED;
    //Max range of the missile after modifiers.
    private final float MAX_RANGE;
    //Random starting offset for the waving.
    private final float OFFSET;
    private CombatEngineAPI engine;
    private final MissileAPI MISSILE;
    private CombatEntityAPI target;
    private Vector2f lead = new Vector2f();
    private boolean launch=true;
    private float timer=0, check=0f;

    //////////////////////
    //  DATA COLLECTING //
    //////////////////////
   
    public CustomMissileAI(MissileAPI missile, ShipAPI launchingShip) {
        this.MISSILE = missile;
        MAX_SPEED = missile.getMaxSpeed();
        MAX_RANGE = missile.getWeapon().getRange();
        if (missile.getSource().getVariant().getHullMods().contains("eccm")){
            ECCM=1;
        }       
        //calculate the precision range factor
        PRECISION_RANGE=(float)Math.pow((2*PRECISION_RANGE),2);
        OFFSET=(float)(Math.random()*Math.PI*2);
    }
   
    //////////////////////
    //   MAIN AI LOOP   //
    //////////////////////
   
    @Override
    public void advance(float amount) {
       
        if (engine != Global.getCombatEngine()) {
            this.engine = Global.getCombatEngine();
        }
       
        //skip the AI if the game is paused, the missile is engineless or fading
        if (Global.getCombatEngine().isPaused() || MISSILE.isFading() || MISSILE.isFizzling()) {return;}
       
        //assigning a target if there is none or it got destroyed
        if (target == null
                || target.getOwner()==MISSILE.getOwner()
                || (TARGET_SWITCH && (target instanceof ShipAPI && ((ShipAPI) target).isHulk())
                                  || !engine.isEntityInPlay(target)
                   )
                ){
            setTarget(assignTarget(MISSILE));
            //forced acceleration by default
            MISSILE.giveCommand(ShipCommand.ACCELERATE);
            return;
        }
       
        timer+=amount;
        //finding lead point to aim to       
        if(launch || timer>=check){
            launch=false;
            timer -=check;
            //set the next check time
            check = Math.min(
                    0.25f,
                    Math.max(
                            0.03f,
                            MathUtils.getDistanceSquared(MISSILE, target)/PRECISION_RANGE)
            );
            if(LEADING){
                //best intercepting point
                lead = AIUtils.getBestInterceptPoint(
                        MISSILE.getLocation(),
                        MAX_SPEED*ECCM, //if eccm is intalled the point is accurate, otherwise it's placed closer to the target (almost tailchasing)
                        target.getLocation(),
                        target.getVelocity()
                );               
                //null pointer protection
                if (lead == null) {
                    lead = target.getLocation();
                }
            } else {
                lead = target.getLocation();
            }
        }
       
        //best velocity vector angle for interception
        float correctAngle = VectorUtils.getAngle(
                        MISSILE.getLocation(),
                        lead
                );
       
        if (OVERSTEER){
            //velocity angle correction
            float offCourseAngle = MathUtils.getShortestRotation(
                    VectorUtils.getFacing(MISSILE.getVelocity()),
                    correctAngle
                    );

            float correction = MathUtils.getShortestRotation(               
                    correctAngle,
                    VectorUtils.getFacing(MISSILE.getVelocity())+180
                    )
                    * 0.5f * //oversteer
                    (float)((Math.sin(Math.PI/90*(Math.min(Math.abs(offCourseAngle),45))))); //damping when the correction isn't important

            //modified optimal facing to correct the velocity vector angle as soon as possible
            correctAngle = correctAngle+correction;
        }
       
        if(WAVE_AMPLITUDE>0){           
            //waving
            float multiplier=1;
            if(ECCM<=1){
                multiplier=0.3f;
            }
            correctAngle+=multiplier*WAVE_AMPLITUDE*check*Math.cos(OFFSET+MISSILE.getElapsed()*(2*Math.PI/WAVE_TIME));
        }
       
        //target angle for interception       
        float aimAngle = MathUtils.getShortestRotation( MISSILE.getFacing(), correctAngle);
       
        if(OVERSHOT_ANGLE<=0 || Math.abs(aimAngle)<OVERSHOT_ANGLE){
            MISSILE.giveCommand(ShipCommand.ACCELERATE); 
        }
       
        if (aimAngle < 0) {
            MISSILE.giveCommand(ShipCommand.TURN_RIGHT);
        } else {
            MISSILE.giveCommand(ShipCommand.TURN_LEFT);
        } 
       
        // Damp angular velocity if the missile aim is getting close to the targeted angle
        if (Math.abs(aimAngle) < Math.abs(MISSILE.getAngularVelocity()) * DAMPING) {
            MISSILE.setAngularVelocity(aimAngle / DAMPING);
        }
    }
   
    //////////////////////
    //    TARGETING     //
    //////////////////////
   
    public CombatEntityAPI assignTarget(MissileAPI missile){
       
        ShipAPI theTarget=null;       
        ShipAPI source = missile.getSource();       
        ShipAPI currentTarget;
       
        //check for a target from its source
        if(source != null
                && source.getShipTarget() != null
                && source.getShipTarget() instanceof ShipAPI
                && source.getShipTarget().getOwner() != missile.getOwner()
                ){
            currentTarget=source.getShipTarget();
        } else {
            currentTarget=null;
        }
       
        //random target selection
        if (RANDOM_TARGET>0){ 
            //random mode 1: the missile will look for a target around itsef
            Vector2f location = missile.getLocation();   
            //random mode 2: if its source has a target selected, it will look for random one around that point
            if( RANDOM_TARGET<2){                                     
                if(currentTarget != null
                        && currentTarget.isAlive()
                        && MathUtils.isWithinRange(missile, currentTarget, MAX_RANGE)
                        ){
                    location = currentTarget.getLocation();
                } else if (source != null
                        && source.getMouseTarget()!=null){
                    location=source.getMouseTarget();
                }
            }
            //fetch the right kind of target
            if(ANTI_FIGHTER){
                theTarget = getRandomFighterTarget(location);
            } else if(ASSAULT){
                theTarget = getRandomLargeTarget(location);
            } else {
                theTarget = getAnyTarget(location);
            }   
        //non random targeting   
        } else {
            if(source!=null){
                //ship target first
                if(currentTarget!=null
                        && currentTarget.isAlive()
                        && currentTarget.getOwner()!=missile.getOwner()
                        && !(ANTI_FIGHTER && !(currentTarget.isDrone() && currentTarget.isFighter()))
                        && !(ASSAULT && (currentTarget.isDrone() || currentTarget.isFighter()))
                        ){
                    theTarget=currentTarget;               
                } else {
                    //or cursor target if there isn't one
                    List<ShipAPI> mouseTargets = CombatUtils.getShipsWithinRange(source.getMouseTarget(), 100f);
                    if (!mouseTargets.isEmpty()) {
                        Collections.sort(mouseTargets, new CollectionUtils.SortEntitiesByDistance(source.getMouseTarget()));
                        for (ShipAPI tmp : mouseTargets) {
                            if (tmp.isAlive()
                                    && tmp.getOwner() != missile.getOwner()
                                    && !(ANTI_FIGHTER && !(tmp.isDrone() && tmp.isFighter()))
                                    && !(ASSAULT && (tmp.isDrone() || tmp.isFighter() || tmp.isFrigate()))
                                    ) {
                                theTarget=tmp;
                                break;
                            }
                        }
                    }               
                }
            }
            //still no valid target? lets try the closest one
            //most of the time a ship will have a target so that doesn't need to be perfect.
            if(theTarget==null){
                List<ShipAPI> closeTargets = AIUtils.getNearbyEnemies(missile, MAX_SEARCH_RANGE);
                if (!closeTargets.isEmpty()) {
                    Collections.sort(closeTargets, new CollectionUtils.SortEntitiesByDistance(missile.getLocation()));
                    if (ASSAULT){   //assault missiles will somewhat prioritize toward bigger threats even if there is a closer small one, and avoid fighters and drones.
                        for (ShipAPI tmp : closeTargets) {
                            if (tmp.isAlive()
                                    && tmp.getOwner() != missile.getOwner()
                                    ) {
                                if (tmp.isCapital() || tmp.isCruiser()){
                                    theTarget=tmp;
                                    break;
                                } else if (tmp.isDestroyer() && Math.random()>0.5){
                                    theTarget=tmp;
                                    break;
                                } else if (tmp.isDestroyer() && Math.random()>0.75){
                                    theTarget=tmp;
                                    break;
                                } else if (!tmp.isDrone() && !tmp.isFighter() && Math.random()>0.95){
                                    theTarget=tmp;
                                    break;
                                }
                            }
                        }
                    }else if(ANTI_FIGHTER){    //anti-fighter missile will target the closest drone or fighter
                        for (ShipAPI tmp : closeTargets) {
                            if (tmp.isAlive()
                                    && tmp.getOwner() != missile.getOwner()
                                    && (tmp.isDrone() || tmp.isFighter())
                                    ) {
                                theTarget=tmp;
                                break;
                            }
                        }
                    }else{  //non assault, non anti-fighter missile target the closest non-drone ship
                        for (ShipAPI tmp : closeTargets) {
                            if (tmp.isAlive()
                                    && tmp.getOwner() != missile.getOwner()
                                    && !tmp.isDrone()
                                    ) { 
                                theTarget=tmp;
                                break;
                            }
                        }
                    }
                }
            }
        }       
        return theTarget;
    }
   
    //Random picker for fighters and drones
    public ShipAPI getRandomFighterTarget(Vector2f location){
        ShipAPI select=null;
        Map<Integer, ShipAPI> PRIORITYLIST = new HashMap<>();
        Map<Integer, ShipAPI> OTHERSLIST = new HashMap<>();
        int i=1, u=1;
        List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
        if (!potentialTargets.isEmpty()) {
            for (ShipAPI tmp : potentialTargets) {
                if (tmp.isAlive()
                        && tmp.getOwner() != MISSILE.getOwner()
                        ) {
                    if (tmp.isFighter() || tmp.isDrone()){
                        PRIORITYLIST.put(i, tmp);
                        i++;
                    } else {                           
                        OTHERSLIST.put(u, tmp);
                        u++;
                    }
                }
            }
            if (!PRIORITYLIST.isEmpty()){
                int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
                select=PRIORITYLIST.get(chooser);
            } else if (!OTHERSLIST.isEmpty()){                   
                int chooser=Math.round((float)Math.random()*(u-1)+0.5f);
                select=OTHERSLIST.get(chooser);
            }
        }
        return select;
    }
   
    //Random target selection strongly weighted toward bigger threats in range
    public ShipAPI getRandomLargeTarget(Vector2f location){
        ShipAPI select=null;
        Map<Integer, ShipAPI> PRIORITY1 = new HashMap<>();
        Map<Integer, ShipAPI> PRIORITY2 = new HashMap<>();
        Map<Integer, ShipAPI> PRIORITY3 = new HashMap<>();
        Map<Integer, ShipAPI> PRIORITY4 = new HashMap<>();
        Map<Integer, ShipAPI> OTHERSLIST = new HashMap<>();
        int i=1, u=1, v=1, x=1, y=1;
        List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
        if (!potentialTargets.isEmpty()) {
            for (ShipAPI tmp : potentialTargets) {
                if (tmp.isAlive()
                        && tmp.getOwner() != MISSILE.getOwner()
                        && !tmp.isDrone()
                        ) {
                    if (tmp.isCapital()){
                        PRIORITY1.put(i, tmp);
                        i++;
                        PRIORITY2.put(u, tmp);
                        u++;
                        PRIORITY3.put(x, tmp);
                        x++;
                        PRIORITY4.put(v, tmp);
                        v++;
                        OTHERSLIST.put(y, tmp);
                        y++;
                    } else if (tmp.isCruiser()){
                        PRIORITY2.put(u, tmp);
                        u++;
                        PRIORITY3.put(x, tmp);
                        x++;
                        PRIORITY4.put(v, tmp);
                        v++;
                        OTHERSLIST.put(y, tmp);
                        y++;
                    } else if (tmp.isDestroyer()){
                        PRIORITY3.put(x, tmp);
                        x++;
                        PRIORITY4.put(v, tmp);
                        v++;
                        OTHERSLIST.put(y, tmp);
                        y++;
                    } else if (tmp.isFrigate()){
                        PRIORITY4.put(v, tmp);
                        v++;
                        OTHERSLIST.put(y, tmp);
                        y++;
                    } else {
                        OTHERSLIST.put(y, tmp);
                        y++;
                    }
                }
            }
            if (!PRIORITY1.isEmpty() && Math.random()>0.8f){
                int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
                select=PRIORITY1.get(chooser);
            } else if (!PRIORITY2.isEmpty() && Math.random()>0.8f){
                int chooser=Math.round((float)Math.random()*(u-1)+0.5f);
                select=PRIORITY2.get(chooser);
            } else if (!PRIORITY3.isEmpty() && Math.random()>0.8f){
                int chooser=Math.round((float)Math.random()*(x-1)+0.5f);
                select=PRIORITY3.get(chooser);
            } else if (!PRIORITY4.isEmpty() && Math.random()>0.8f){
                int chooser=Math.round((float)Math.random()*(v-1)+0.5f);
                select=PRIORITY4.get(chooser);
            } else if (!OTHERSLIST.isEmpty()){                   
                int chooser=Math.round((float)Math.random()*(y-1)+0.5f);
                select=OTHERSLIST.get(chooser);
            }
        }
        return select;
    }

    //Pure random target picker
    public ShipAPI getAnyTarget(Vector2f location){
        ShipAPI select=null;
        Map<Integer, ShipAPI> TARGETLIST = new HashMap<>();
        int i=1;
        List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
        if (!potentialTargets.isEmpty()) {
            for (ShipAPI tmp : potentialTargets) {
                if (tmp.isAlive()
                        && tmp.getOwner() != MISSILE.getOwner()
                        && !tmp.isDrone()
                        ){
                    TARGETLIST.put(i, tmp);
                    i++;                       
                }
            }
            if (!TARGETLIST.isEmpty()){
                int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
                select=TARGETLIST.get(chooser);
            }
        }
        return select;
    }
   
    @Override
    public CombatEntityAPI getTarget() {
        return target;
    }

    @Override
    public void setTarget(CombatEntityAPI target) {
        this.target = target;
    }
   
    public void init(CombatEngineAPI engine) {}
}
[close]
Logged
 

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #121 on: December 03, 2015, 02:29:36 AM »

A few mods add hulmods that are incompatible with vanilla ones the same way augmented engine can't be installed on top of unstable injector. The little problem there is that while you can make your hullmod incompatible with the vanilla one, it's not possible to do the other way around without modifying the vanilla hullmod (which is bad, nobody should do that!) The common solution we used was to instantly remove the modded hullmod if it get installed next to a conflicting hullmod, but without any feedback that can be puzzling from a player perspective.
This should make things clearer:



That "fake" hullmod will be installed in place of the conflicting one, then automatically removed when installing anything else (or can be removed manually if you so desire).

hull_mods.csv entry:
Code
HULLMOD CONFLICT,IncompatibleHullmodWarning,,TRUE,0,0,0,0,data.hullmods.IncompatibleHullmodWarning,"%s, the hullmod you tried to install conflicts with this hull or another previously installed hullmod.",graphics/icons/intel/investigation.png

IncompatibleHullmodWarning.java :
Code: Java
package data.hullmods;

import com.fs.starfarer.api.combat.ShipAPI;

public class IncompatibleHullmodWarning extends BaseHullMod {   
   
    @Override
    public String getDescriptionParam(int index, ShipAPI.HullSize hullSize) {
        if (index == 0) return "WARNING";
       
        return null;
    }
}

and the part you have to add to your hull-mod script:
Code: Java
package data.hullmods;

import com.fs.starfarer.api.combat.ShipAPI;
import java.util.HashSet;
import java.util.Set;

public class YourIncompatibleHullMod extends BaseHullMod {   
    private static final Set<String> BLOCKED_HULLMODS = new HashSet<>();
    static
    {
        // These hullmods will automatically be removed
        BLOCKED_HULLMODS.add("augmentedengines");
        BLOCKED_HULLMODS.add("auxiliarythrusters");
        BLOCKED_HULLMODS.add("unstable_injector");
        BLOCKED_HULLMODS.add("drive_shunt");     
    }
    private float check=0;
    private String ERROR="IncompatibleHullmodWarning";

    @Override
    public void applyEffectsAfterShipCreation(ShipAPI ship, String id){
       
        if (check>0) {     
            check-=1;
            if (check<1){
            ship.getVariant().removeMod(ERROR);   
            }
        }
       
        for (String tmp : BLOCKED_HULLMODS) {
            if (ship.getVariant().getHullMods().contains(tmp)) {               
                ship.getVariant().removeMod(tmp);     
                ship.getVariant().addMod(ERROR);
                check=3;
            }
        }
    }
}
Logged
 

shuul

  • Ensign
  • *
  • Posts: 42
    • View Profile
Re: The Radioactive Code Dump
« Reply #122 on: December 03, 2015, 02:55:51 AM »

This thread is a gift, have no idea how long it will take me to understand all of this, but your code looks just damn sexy. Thanks Tartiflette!
Logged

Deathfly

  • Modders' Vanguard
  • Commander
  • ***
  • Posts: 245
  • Murdered by T. H. Morgan
    • View Profile
Re: The Radioactive Code Dump
« Reply #123 on: January 14, 2016, 05:11:10 AM »

A small update of the CollisionUtilsEX that will make beam collision with the shield "egdes"...well, less accurate to match vanilla beam do.
(And leave a param in there in case if you need those more accurate detection.)

Another change is the getCollisionPointOnCircumference will return the first intersection point of segment regardless whether segStart is inside the Circumference or not. And the original getCollisionPointOnCircumference was replaced by getCollisionPointOnCircle, which will return segStart directly if the segStart is inside the Circle and was used for shield collision check.

Spoiler
Code: java
// by Deathfly and Tartiflette
package data.scripts.util;

import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.ShieldAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import java.awt.geom.Line2D;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lwjgl.util.vector.Vector2f;

public class CollisionUtilsEX {

    /**
     * return the collision point of segment segStart to segEnd and a ship (will
     * consider shield).
     *
     * @return the collision point of segment segStart to segEnd and a ship
     * (will consider shield). if segment can not hit the ship, will return
     * null.
     * @param segStart if segStart hit the ship hull or shield, will return
     * segStart.
     * @param accurateShieldEdgeTest use an additional test to check if the
     * segment hit the shield on edge. Set to false if you want a vanilla like
     * behaviour.
     */
    public static Vector2f getShipCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship, boolean accurateShieldEdgeTest) {

        // if target can not be hit, return null
        if (ship.getCollisionClass() == CollisionClass.NONE) {
            return null;
        }
        ShieldAPI shield = ship.getShield();

        // Check hit point when shield is off.
        if (shield == null || shield.isOff()) {
            return CollisionUtils.getCollisionPoint(segStart, segEnd, ship);
        } // If ship's shield is on, thing goes complicated...
        else {
            Vector2f circleCenter = shield.getLocation();
            float circleRadius = shield.getRadius();
            // calculate the shield collision point
            Vector2f tmp1 = getCollisionPointOnCircle(segStart, segEnd, circleCenter, circleRadius);
            if (tmp1 != null) {
                // OK! hit the shield in face
                if (shield.isWithinArc(tmp1)) {
                    return tmp1;
                } else {

                    boolean hit = false;
                    Vector2f tmp = new Vector2f(segEnd);

                    //the beam cannot go farther than it's max range or the hull
                    Vector2f hullHit = CollisionUtils.getCollisionPoint(segStart, segEnd, ship);
                    if (hullHit != null) {
                        tmp = hullHit;
                        hit = true;
                    }

                    // if the hit come outside the shield's arc but it hit the shield's "edge", find that point.
                    if (accurateShieldEdgeTest) {
                        Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc() / 2));
                        Vector2f tmp2 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge1);
                        if (tmp2 != null) {
                            tmp = tmp2;
                            hit = true;
                        }
                        Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc() / 2));
                        Vector2f tmp3 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge2);
                        if (tmp3 != null) {
                            tmp = tmp3;
                            hit = true;
                        }
                    }

                    // return null if segment not hit anything.
                    return hit ? tmp : null;
                }
            }
        }
        return null;
    }

    // Just a compatible method.
    /**
     * return the collision point of segment segStart to segEnd and a ship (will
     * consider shield).
     *
     * @return the collision point of segment segStart to segEnd and a ship
     * (will consider shield). if segment can not hit the ship, will return
     * null.
     * @param segStart if segStart hit the ship hull or shield, will return
     * segStart.
     */
    public static Vector2f getShipCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship) {
        return getShipCollisionPoint(segStart, segEnd, ship, false);
    }

    /**
     * return the first intersection point of the segment segStart to segEnd and
     * the circle.
     *
     * @return the first intersection point of segment segStart to segEnd and
     * circumference. if segStart is outside the circle and segment can not
     * intersection with the circumference, will return null.
     * @param segStart if segStart is inside the circle, will return segStart.
     */
    public static Vector2f getCollisionPointOnCircle(Vector2f segStart, Vector2f segEnd, Vector2f circleCenter, float circleRadius) {

        Vector2f startToEnd = Vector2f.sub(segEnd, segStart, null);
        Vector2f startToCenter = Vector2f.sub(circleCenter, segStart, null);
        double ptLineDistSq = (float) Line2D.ptLineDistSq(segStart.x, segStart.y, segEnd.x, segEnd.y, circleCenter.x, circleCenter.y);

        float circleRadiusSq = circleRadius * circleRadius;

        // if lineStart is within the circle, return it directly
        if (startToCenter.lengthSquared() < circleRadiusSq) {
            return segStart;
        }

        // if lineStart is outside the circle and segment can not reach the circumference, return null
        if (ptLineDistSq > circleRadiusSq || startToCenter.length() - circleRadius > startToEnd.length()) {
            return null;
        }

        // calculate the intersection point.
        startToEnd.normalise(startToEnd);
        double dist = Vector2f.dot(startToCenter, startToEnd) - Math.sqrt(circleRadiusSq - ptLineDistSq);
        startToEnd.scale((float) dist);
        return Vector2f.add(segStart, startToEnd, null);
    }

    /**
     * return the first intersection point of the segment segStart to segEnd and
     * the circumference.
     *
     * @return the first intersection point of segment segStart to segEnd and
     * circumference. if segment can not intersection with the circumference,
     * will return null.
     */
    public static Vector2f getCollisionPointOnCircumference(Vector2f segStart, Vector2f segEnd, Vector2f circleCenter, float circleRadius) {

        Vector2f startToEnd = Vector2f.sub(segEnd, segStart, null);
        Vector2f startToCenter = Vector2f.sub(circleCenter, segStart, null);
        double ptLineDistSq = (float) Line2D.ptLineDistSq(segStart.x, segStart.y, segEnd.x, segEnd.y, circleCenter.x, circleCenter.y);
        float circleRadiusSq = circleRadius * circleRadius;
        boolean CoS = false;
        if (startToCenter.lengthSquared() < circleRadiusSq) {
            CoS = true;
        }

        // if lineStart is outside the circle and segment can not reach the circumference, return null
        if (ptLineDistSq > circleRadiusSq || startToCenter.length() - circleRadius > startToEnd.length()) {
            return null;
        }

        // calculate the intersection point.
        startToEnd.normalise(startToEnd);
        double dist;
        if (CoS) {
            dist = Vector2f.dot(startToCenter, startToEnd) + Math.sqrt(circleRadiusSq - ptLineDistSq);
            if (dist < startToEnd.length()) {
                return null;
            }
        } else {
            dist = Vector2f.dot(startToCenter, startToEnd) - Math.sqrt(circleRadiusSq - ptLineDistSq);
        }
        startToEnd.scale((float) dist);
        return Vector2f.add(segStart, startToEnd, null);
    }

    /**
     * SHOULD ONLY BE USED WHEN YOU ONLY NEED CHECK FOR SHIELD COLLISION POINT!
     * if you need the check for a ship hit (considering it's shield), use
     * getShipCollisionPoint instead.
     *
     * @return the collision point of segment segStart to segEnd and ship's
     * shield. if the segment can not hit the shield or the ship has no shield,
     * return null.
     * @param ignoreHull if ignoreHull = flase and the segment hit the ship's
     * hull first, return null.
     * @param segStart if segStart hit the shield, will return segStart.
     * @param accurateShieldEdgeTest use an additional test to check if the
     * segment hit the shield on edge. Set to false if you want a vanilla like
     * behaviour.
     */
    public static Vector2f getShieldCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship, boolean ignoreHull, boolean accurateShieldEdgeTest) {
        // if target not shielded, return null
        ShieldAPI shield = ship.getShield();
        if (ship.getCollisionClass() == CollisionClass.NONE || shield == null || shield.isOff()) {
            return null;
        }
        Vector2f circleCenter = shield.getLocation();
        float circleRadius = shield.getRadius();
        // calculate the shield collision point
        Vector2f tmp1 = getCollisionPointOnCircle(segStart, segEnd, circleCenter, circleRadius);
        if (tmp1 != null) {
            // OK! hit the shield in face
            if (shield.isWithinArc(tmp1)) {
                return tmp1;
            } else {
                // if the hit come outside the shield's arc but it hit the shield's "edge", find that point.                

                Vector2f tmp = new Vector2f(segEnd);
                boolean hit = false;
                if (accurateShieldEdgeTest) {
                    Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc() / 2));
                    Vector2f tmp2 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge1);
                    if (tmp2 != null) {
                        tmp = tmp2;
                        hit = true;
                    }

                    Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc() / 2));
                    Vector2f tmp3 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge2);
                    if (tmp3 != null) {
                        tmp = tmp3;
                        hit = true;
                    }
                }
                // If we don't ignore hull hit, check if there is one...
                if (!ignoreHull && CollisionUtils.getCollisionPoint(segStart, tmp, ship) != null) {
                    return null;
                }
                // return null if do not hit shield.
                return hit ? tmp : null;
            }
        }
        return null;
    }

    // Just a compatible method.
    /**
     * SHOULD ONLY BE USED WHEN YOU ONLY NEED CHECK FOR SHIELD COLLISION POINT!
     * if you need the check for a ship hit (considering it's shield), use
     * getShipCollisionPoint instead.
     *
     * @return the collision point of segment segStart to segEnd and ship's
     * shield. if the segment can not hit the shield or the ship has no shield,
     * return null.
     * @param ignoreHull if ignoreHull = flase and the segment hit the ship's
     * hull first, return null.
     * @param segStart if segStart hit the shield, will return segStart.
     */
    public static Vector2f getShieldCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship, boolean ignoreHull) {
        return getShieldCollisionPoint(segStart, segEnd, ship, ignoreHull, false);
    }
}
[close]
« Last Edit: January 17, 2016, 08:04:45 PM by Deathfly »
Logged
A not-so-noob functional geneticist
And a free bug hunter
Code and decode almost everythings with a genomics approach.
Served in Neutrino corporation as a long-term services and supports staff.

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #124 on: December 19, 2016, 04:56:46 AM »

It's been a long time since nothing has been posted here, but I may have something interesting to share:

Ever wanted to create custom effects that aren't particles or emp arcs? Or add some UI elements? But not really willing to fiddle with direct sprite rendering? Or like me tired of having to rewrite rendering code everywhere? Here is the solution with this SpriteRenderManager plugin! Now you can add almost any effect or rasterized UI element with a single line of code!



You can draw sprites at absolute coordinates, of attach them to an entity, or fix them to the UI. There is a single limitation for it: the sprites will only be drawn the next frame.

Spoiler
/*
 * By Tartiflette
 * Plugin managing direct sprite rendering to create new visual effect or add new UI elements with only one line of code.
 * Note that every sprite will be drawn one frame late.

sample use:

SpriteRenderManager.screenspaceRender(
        Global.getSettings().getSprite("misc", "graphics/fx/wormhole_ring_bright3.png"),
        SpriteRenderManager.positioning.FULLSCREEN_MAINTAIN_RATIO,
        new Vector2f(),
        null,
        new Vector2f(50,50),
        null,
        0,
        360,
        Color.blue,
        false,
        1,
        3,
        1
);
 */
package data.scripts.plugins;

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.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.ViewportAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.lazywizard.lazylib.VectorUtils;
import org.lwjgl.util.vector.Vector2f;

public class SpriteRenderManager extends BaseEveryFrameCombatPlugin {
   
    private static List<renderData> SINGLEFRAME = new ArrayList<>();
    private static List<battlespaceData> BATTLESPACE = new ArrayList<>();
    private static List<objectspaceData> OBJECTSPACE = new ArrayList<>();
    private static List<screenspaceData> SCREENSPACE = new ArrayList<>();
   
    @Override
    public void init(CombatEngineAPI engine) {
        //reinitialize the lists
        SINGLEFRAME.clear();
        BATTLESPACE.clear();
        OBJECTSPACE.clear();
        SCREENSPACE.clear();
    }
   
    //////////////////////////////
    //                          //
    //     PUBLIC METHODS       //
    //                          //
    //////////////////////////////
   
    /**
     * @param sprite
     * SpriteAPI to render.
     *
     * @param loc
     * Vector2f, center in world coordinates.
     *
     * @param size
     * Vector2f(width, height) in pixels.
     *
     * @param angle
     * float of the sprite's azimuth. 0 is pointing top.
     *
     * @param color
     * Color() override, also used for fading.
     *
     * @param additive
     * boolean for additive blending.
     */
   
    public static void singleFrameRender(SpriteAPI sprite, Vector2f loc, Vector2f size, float angle, Color color, boolean additive) {
        sprite.setSize(size.x, size.y);
        sprite.setAngle(angle);
        sprite.setColor(color);
        if(additive){
            sprite.setAdditiveBlend();
        }
        SINGLEFRAME.add(new renderData(sprite, loc));
    }     
   
    /**
     *
     * @param sprite
     * SpriteAPI to render.
     *
     * @param loc
     * Vector2f, center in world coordinates.
     *
     * @param vel
     * Vector2f() velocity of the sprite.
     *
     * @param size
     * Vector2f(width, height) in pixels.
     *
     * @param growth
     * Vector2f() change of size over time in pixels/sec. Can be negative, a sprite that completely shrunk will be removed.
     *
     * @param angle
     * float of the sprite's azimuth. 0 is pointing top.
     *
     * @param spin
     * float of the sprite's rotation, in degree/sec.
     *
     * @param color
     * Color() override, also used for fading.
     *
     * @param additive
     * boolean for additive blending.
     *
     * @param fadein
     * time in sec for fading in.
     *
     * @param full
     * time in sec at maximum opacity (clamped by color)
     *
     * @param fadeout
     * time in sec for fading out
     */
   
    public static void battlespaceRender(SpriteAPI sprite, Vector2f loc, Vector2f vel, Vector2f size, Vector2f growth, float angle, float spin, Color color, boolean additive, float fadein, float full, float fadeout) {
        sprite.setSize(size.x, size.y);
        sprite.setAngle(angle);
        sprite.setColor(color);
        if(additive){
            sprite.setAdditiveBlend();
        }       
        BATTLESPACE.add(new battlespaceData(sprite, loc, vel, growth, spin, fadein, fadein+full, fadein+full+fadeout, 0));
    }       
   
    /**
     *
     * @param sprite
     * SpriteAPI to render.
     *
     * @param anchor
     * CombatEntityAPI the sprite will follow.
     *
     * @param offset
     * Vector2f, offset from the anchor's center in world coordinates. If parent is true, it will be relative to the anchor's orientation.
     *
     * @param vel
     * Vector2f() velocity of the sprite relative to the anchor. If parent is true, it will be relative to the anchor's orientation.
     *
     * @param size
     * Vector2f(width, height) in pixels.
     *
     * @param growth
     * Vector2f() change of size over time in pixels/sec. Can be negative, a sprite that completely shrunk will be removed.
     *
     * @param angle
     * float of the sprite's azimuth. 0 is pointing front. If parent is true, 0 will match the anchor's orientation.
     *
     * @param spin
     * float of the sprite's rotation, in degree/sec. If parent is true, it will be relative to the anchor's orientation.
     *
     * @param parent
     * boolean, if true the sprite will also follow the anchor's orientation in addition to the position.
     *
     * @param color
     * Color() override, also used for fading.
     *
     * @param additive
     * boolean for additive blending.
     *
     * @param fadein
     * time in sec for fading in.
     *
     * @param full
     * time in sec at maximum opacity (clamped by color). If attached to a projectile that value can be longer than the maximum flight time, for example 99s.
     *
     * @param fadeout
     * time in sec for fading out. If attached to a projectile, the sprite will immediately start to fade if the anchor hit or fade.
     *
     * @param fadeOnDeath
     * if true the sprite will fadeout in case the anchor is removed, if false it will be instantly removed. Mostly useful if you want a long fadeout otherwise.
     */
   
    public static void objectspaceRender(SpriteAPI sprite, CombatEntityAPI anchor, Vector2f offset, Vector2f vel, Vector2f size, Vector2f growth, float angle, float spin, boolean parent, Color color, boolean additive, float fadein, float full, float fadeout, boolean fadeOnDeath) {
        sprite.setSize(size.x, size.y);
        if(parent){           
            sprite.setAngle(anchor.getFacing()+angle+90);
        } else {
            sprite.setAngle(angle+90);
        }
        sprite.setColor(color);
        if(additive){
            sprite.setAdditiveBlend();
        }
        Vector2f loc=new Vector2f(anchor.getLocation());
       
        OBJECTSPACE.add(new objectspaceData(sprite, anchor, loc, offset, vel, growth, angle, spin, parent, fadein, fadein+full, fadein+full+fadeout, fadeOnDeath, 0));
    }   
   
    /**
     *
     * @param sprite
     * SpriteAPI to render.
     *
     * @param pos
     * Positioning mode, set the point of reference, useful for UI elements. STRETCH_TO_FULLSCREEN will override the size, FULLSCREEN_MAINTAIN_RATIO will use the size as a reference and scale the sprite accordingly.
     *
     * @param loc
     * Vector2f, center in world coordinates. Ignore for fullscreen.
     *
     * @param vel
     * Vector2f() velocity of the sprite. Ignore for fullscreen.
     *
     * @param size
     * Vector2f(width, height) in pixels. size reference for FULLSCREEN_MAINTAIN_RATIO, ignore for STRETCH_TO_FULLSCREEN.
     *
     * @param growth
     * Vector2f() change of size over time in pixels/sec. Can be negative, a sprite that completely shrunk will be removed. Ignore for fullscreen.
     *
     * @param angle
     * float of the sprite's azimuth. 0 is pointing top. Ignore for fullscreen.
     *
     * @param spin
     * float of the sprite's rotation, in degree/sec. Ignore for fullscreen.
     *
     * @param color
     * Color() override, also used for fading.
     *
     * @param additive
     * boolean for additive blending.
     *
     * @param fadein
     * time in sec for fading in. Set to -1 for single frame render.
     *
     * @param full
     * time in sec at maximum opacity (clamped by color). Set to -1 for single frame render.
     *
     * @param fadeout
     * time in sec for fading out. Set to -1 for single frame render.
     */
   
    public static void screenspaceRender(SpriteAPI sprite, positioning pos, Vector2f loc, Vector2f vel, Vector2f size, Vector2f growth, float angle, float spin, Color color, boolean additive, float fadein, float full, float fadeout) {
        ViewportAPI screen = Global.getCombatEngine().getViewport();
       
        Vector2f ratio=size;
        Vector2f screenSize= new Vector2f(screen.getVisibleWidth(),screen.getVisibleHeight());
        if(pos == positioning.STRETCH_TO_FULLSCREEN){
            sprite.setSize(screenSize.x, screenSize.y);
        } else if(pos == positioning.FULLSCREEN_MAINTAIN_RATIO) {
            if(size.x/size.y > screenSize.x/screenSize.y){
                ratio = new Vector2f((size.x/size.y)/(screenSize.x/screenSize.y),1);
            } else {
                ratio = new Vector2f(1, (size.y/size.x)/(screenSize.y/screenSize.x));
                sprite.setSize(Global.getCombatEngine().getViewport().getVisibleWidth()*ratio.x,Global.getCombatEngine().getViewport().getVisibleHeight()*ratio.y);
            }
        } else {
            sprite.setSize(size.x*screen.getViewMult(), size.y*screen.getViewMult());
        }
        sprite.setAngle(angle);
        sprite.setColor(color);
        if(additive){
            sprite.setAdditiveBlend();
        }                 
       
        SCREENSPACE.add(new screenspaceData(sprite, pos, loc, vel, ratio, growth, spin, fadein, fadein+full, fadein+full+fadeout, 0));       
    } 
   
    //////////////////////////////
    //                          //
    //         MAIN LOOP        //
    //                          //
    //////////////////////////////   
   
   
    @Override
    public void renderInWorldCoords(ViewportAPI view){       
        CombatEngineAPI engine = Global.getCombatEngine();       
        if (engine == null){return;}       
       
        float amount=0;
        if(!engine.isPaused()){
            amount=engine.getElapsedInLastFrame();
        }
       
        if(!BATTLESPACE.isEmpty()){
            //iterate throught the BATTLESPACE data first:
            for(Iterator<battlespaceData> iter=BATTLESPACE.iterator(); iter.hasNext(); ){
                battlespaceData entry = iter.next();
               
                //add the time spent, that means sprites will never start at 0 exactly, but it simplifies a lot the logic
                entry.TIME+=amount;
                if(entry.TIME>entry.FADEOUT){
                    //remove expended ones
                    iter.remove();
                    continue;
                }
               
                //grow/shrink the sprite to a new size if needed
                if(entry.GROWTH!= null && entry.GROWTH!=new Vector2f()){
                    entry.SPRITE.setSize(entry.SPRITE.getWidth()+(entry.GROWTH.x*amount), entry.SPRITE.getHeight()+(entry.GROWTH.y*amount));
                    //check if the growth made the sprite too small
                    if(entry.SPRITE.getHeight()<=0 || entry.SPRITE.getWidth()<=0){                       
                        //remove sprites that completely shrunk
                        iter.remove();
                        continue;
                    }
                }
               
                //move the sprite to a new center if needed
                if(entry.VEL!= null && entry.VEL!=new Vector2f()){
                    Vector2f move = new Vector2f(entry.VEL);
                    move.scale(amount);
                    Vector2f.add(entry.LOC, move, entry.LOC);
                }

                //spin the sprite if needed
                if(entry.SPIN!=0){
                    entry.SPRITE.setAngle(entry.SPRITE.getAngle()+entry.SPIN*amount);
                }
               
                //fading stuff
                if(entry.TIME<entry.FADEIN){
                    entry.SPRITE.setAlphaMult(entry.TIME/entry.FADEIN);
                } else if(entry.TIME>entry.FULL){                   
                    entry.SPRITE.setAlphaMult(1-((entry.TIME-entry.FULL)/(entry.FADEOUT-entry.FULL)));
                } else {
                    entry.SPRITE.setAlphaMult(1);
                }
               
                //finally render that stuff
                render(new renderData(entry.SPRITE, entry.LOC));
            }
        }
       
        if(!OBJECTSPACE.isEmpty()){
            //then iterate throught the OBJECTSPACE data:
            for(Iterator<objectspaceData> iter=OBJECTSPACE.iterator(); iter.hasNext(); ){
                objectspaceData entry = iter.next();
               
                //check for possible removal on death
                if(!entry.DEATHFADE && engine.isEntityInPlay(entry.ANCHOR)){
                    iter.remove();
                    continue;
                }
               
                //check for projectile attachement fadeout
                if(entry.ANCHOR instanceof DamagingProjectileAPI){
                    //if the proj is fading or removed, offset the fadeout time to the current time
                    if (entry.TIME<entry.FULL && (
                            ((DamagingProjectileAPI)entry.ANCHOR).isFading()
                            || !engine.isEntityInPlay(entry.ANCHOR))
                            ){
                        entry.FADEOUT=(entry.FADEOUT-entry.FULL)+entry.TIME;
                    }
                }
               
                //add the time spent, that means sprites will never start at 0 exactly, but it simplifies a lot the logic
                entry.TIME+=amount;
                if(entry.TIME>entry.FADEOUT){
                    //remove expended ones
                    iter.remove();
                    continue;
                }               
               
                //grow/shrink the sprite to a new size if needed
                if(entry.GROWTH!= null && entry.GROWTH!=new Vector2f()){
                    entry.SPRITE.setSize(entry.SPRITE.getWidth()+(entry.GROWTH.x*amount), entry.SPRITE.getHeight()+(entry.GROWTH.y*amount));
                    //check if the growth made the sprite too small
                    if(entry.SPRITE.getHeight()<=0 || entry.SPRITE.getWidth()<=0){                       
                        //remove sprites that completely shrunk
                        iter.remove();
                        continue;
                    }
                }
               
                //adjust the offset if needed
                if(entry.VEL!= null && entry.VEL!=new Vector2f()){
                    Vector2f move = new Vector2f(entry.VEL);
                    move.scale(amount);
                    Vector2f.add(entry.OFFSET, move, entry.OFFSET);
                }
               
                //addjust the position and orientation
                Vector2f location = new Vector2f(entry.OFFSET); //base offset
               
                //for parenting, check if the anchor is present
                if(entry.PARENT && engine.isEntityInPlay(entry.ANCHOR)){ 
                    //if the sprite is parented, use the ANGLE to store the offset
                    if(entry.SPIN!=0){
                        entry.ANGLE+=entry.SPIN*amount;
                    }
                    entry.SPRITE.setAngle(entry.ANCHOR.getFacing()+90+entry.ANGLE);
                    //orient the offset with the facing
                    VectorUtils.rotate(location, entry.ANCHOR.getFacing(), location);
                } else {
                    //otherwise just orient the sprite
                    if(entry.SPIN!=0){
                        entry.SPRITE.setAngle(entry.SPRITE.getAngle()+entry.SPIN*amount);
                    }
                }
               
                //move the offset on the anchor
                if(engine.isEntityInPlay(entry.ANCHOR)){
                    Vector2f.add(location, entry.ANCHOR.getLocation(), location);
                    entry.LOCATION=entry.ANCHOR.getLocation();
                } else {
                    Vector2f.add(location, entry.LOCATION, location);
                }
               
                //fading stuff
                if(entry.TIME<entry.FADEIN){
                    entry.SPRITE.setAlphaMult(entry.TIME/entry.FADEIN);
                } else if(entry.TIME>entry.FULL){                   
                    entry.SPRITE.setAlphaMult(1-((entry.TIME-entry.FULL)/(entry.FADEOUT-entry.FULL)));
                } else {
                    entry.SPRITE.setAlphaMult(1);
                }
               
                //finally render that stuff
               
                render(new renderData(entry.SPRITE, location));
            }
        }
       
        if(!SCREENSPACE.isEmpty()){
            //iterate throught the BATTLESPACE data first:
           
            Vector2f center;
            ViewportAPI screen = Global.getCombatEngine().getViewport();
           
            for(Iterator<screenspaceData> iter=SCREENSPACE.iterator(); iter.hasNext(); ){
                screenspaceData entry = iter.next();
               
               
                if(entry.FADEOUT<0){
                    // SINGLE FRAME RENDERING
                    if(entry.POS == positioning.FULLSCREEN_MAINTAIN_RATIO){                   
                        center = new Vector2f(screen.getCenter());
                        entry.SPRITE.setSize(entry.SIZE.x*screen.getVisibleWidth(), entry.SIZE.y*screen.getVisibleHeight());
                    } else if(entry.POS == positioning.STRETCH_TO_FULLSCREEN){
                        center = new Vector2f(screen.getCenter());
                        entry.SPRITE.setSize(screen.getVisibleWidth(), screen.getVisibleHeight());
                    } else {
                        Vector2f refPoint=screen.getCenter();
                        switch (entry.POS){

                            case LOW_LEFT:
                                refPoint = new Vector2f(refPoint.x-(screen.getVisibleWidth()/2), refPoint.y-(screen.getVisibleHeight()/2));
                                break;

                            case LOW_RIGHT:
                                refPoint = new Vector2f(refPoint.x-(screen.getVisibleWidth()/2), refPoint.y+(screen.getVisibleHeight()/2));
                                break;

                            case UP_LEFT:
                                refPoint = new Vector2f(refPoint.x+(screen.getVisibleWidth()/2), refPoint.y-(screen.getVisibleHeight()/2));
                                break;

                            case UP_RIGHT:
                                refPoint = new Vector2f(refPoint.x+(screen.getVisibleWidth()/2), refPoint.y+(screen.getVisibleHeight()/2));
                                break;

                            default:
                        }               
                        center = new Vector2f(entry.LOC);
                        center.scale(screen.getViewMult());
                        Vector2f.add(center, refPoint, center);               
                    }

                    //finally render that stuff
                    render(new renderData(entry.SPRITE, center));
                    //and immediatelly remove
                    iter.remove();
                } else {
                    // TIMED RENDERING                   
                    //add the time spent, that means sprites will never start at 0 exactly, but it simplifies a lot the logic
                    entry.TIME+=amount;
                    if(entry.FADEOUT>0 && entry.TIME>entry.FADEOUT){
                        //remove expended ones
                        iter.remove();
                        continue;
                    }               

                    if(entry.POS == positioning.FULLSCREEN_MAINTAIN_RATIO){                   
                        center = new Vector2f(screen.getCenter());
                        entry.SPRITE.setSize(entry.SIZE.x*screen.getVisibleWidth(), entry.SIZE.y*screen.getVisibleHeight());
                    } else if(entry.POS == positioning.STRETCH_TO_FULLSCREEN){
                        center = new Vector2f(screen.getCenter());
                        entry.SPRITE.setSize(screen.getVisibleWidth(), screen.getVisibleHeight());
                    } else {
                        Vector2f refPoint=screen.getCenter();
                        switch (entry.POS){

                            case LOW_LEFT:
                                refPoint = new Vector2f(refPoint.x-(screen.getVisibleWidth()/2), refPoint.y-(screen.getVisibleHeight()/2));
                                break;

                            case LOW_RIGHT:
                                refPoint = new Vector2f(refPoint.x-(screen.getVisibleWidth()/2), refPoint.y+(screen.getVisibleHeight()/2));
                                break;

                            case UP_LEFT:
                                refPoint = new Vector2f(refPoint.x+(screen.getVisibleWidth()/2), refPoint.y-(screen.getVisibleHeight()/2));
                                break;

                            case UP_RIGHT:
                                refPoint = new Vector2f(refPoint.x+(screen.getVisibleWidth()/2), refPoint.y+(screen.getVisibleHeight()/2));
                                break;

                            default:
                        }                   

                        //move the sprite to a new center if needed
                        if(entry.VEL!= null && entry.VEL!=new Vector2f()){
                            Vector2f move = new Vector2f(entry.VEL);
                            move.scale(amount);
                            Vector2f.add(entry.LOC, move, entry.LOC);
                        }
                        center = new Vector2f(entry.LOC);
                        center.scale(screen.getViewMult());
                        Vector2f.add(center, refPoint, center);

                        //grow/shrink the sprite to a new size if needed
                        if(entry.GROWTH!= null && entry.GROWTH!=new Vector2f()){
                            entry.SIZE = new Vector2f(entry.SIZE.x+(entry.GROWTH.x*amount), entry.SIZE.y+(entry.GROWTH.y*amount));
                            //check if the growth made the sprite too small
                            if(entry.SIZE.x<=0 || entry.SIZE.y<=0){                       
                                //remove sprites that completely shrunk
                                iter.remove();
                                continue;
                            }
                        }
                        entry.SPRITE.setSize(entry.SIZE.x*screen.getViewMult(), entry.SIZE.y*screen.getViewMult());

                        //spin the sprite if needed
                        if(entry.SPIN!=0){
                            entry.SPRITE.setAngle(entry.SPRITE.getAngle()+entry.SPIN*amount);
                        }
                    }

                    //fading stuff
                    if(entry.TIME<entry.FADEIN){
                        entry.SPRITE.setAlphaMult(entry.TIME/entry.FADEIN);
                    } else if(entry.TIME>entry.FULL){                   
                        entry.SPRITE.setAlphaMult(1-((entry.TIME-entry.FULL)/(entry.FADEOUT-entry.FULL)));
                    } else {
                        entry.SPRITE.setAlphaMult(1);
                    }
                   
                    //finally render that stuff
                    render(new renderData(entry.SPRITE, center));
                    if(entry.FADEOUT<0){
                        iter.remove();
                    }
                }               
            }
        }
       
        //Single frame sprite rendering
        if(!SINGLEFRAME.isEmpty()){
            for(renderData d : SINGLEFRAME){
                render(d);
            }
            SINGLEFRAME.clear();
        }
    }
   
    //////////////////////////////
    //                          //
    //          RENDER          //
    //                          //
    //////////////////////////////
   
    private void render (renderData data){
        //where the magic happen
        SpriteAPI sprite = data.SPRITE; 
        sprite.renderAtCenter(data.LOC.x, data.LOC.y);
    }
   
    //////////////////////////////
    //                          //
    //      RENDER CLASSES      //
    //                          //
    //////////////////////////////   
   
    private static class renderData {   
        private final SpriteAPI SPRITE;
        private final Vector2f LOC;
       
        public renderData(SpriteAPI sprite, Vector2f loc) {
            this.SPRITE = sprite;
            this.LOC = loc;
        }
    }
   
    private static class battlespaceData {   
        private final SpriteAPI SPRITE;
        private Vector2f LOC;
        private final Vector2f VEL;
        private final Vector2f GROWTH;
        private final float SPIN;
        private final float FADEIN;
        private final float FULL; //fade in + full
        private final float FADEOUT; //full duration
        private float TIME;
       
        public battlespaceData(SpriteAPI sprite, Vector2f loc, Vector2f vel, Vector2f growth, float spin, float fadein, float full, float fadeout, float time) {
            this.SPRITE = sprite;
            this.LOC = loc;
            this.VEL = vel;
            this.GROWTH = growth;
            this.SPIN = spin;
            this.FADEIN = fadein;
            this.FULL = full;
            this.FADEOUT = fadeout;
            this.TIME = time;
        }
    }
   
    private static class objectspaceData {   
        private final SpriteAPI SPRITE;
        private final CombatEntityAPI ANCHOR;
        private Vector2f LOCATION;
        private Vector2f OFFSET;
        private final Vector2f VEL;
        private final Vector2f GROWTH;
        private float ANGLE;
        private final float SPIN;
        private final boolean PARENT;
        private final float FADEIN;
        private float FULL; //fade in + full
        private float FADEOUT; //full duration
        private final boolean DEATHFADE;
        private float TIME;
       
        public objectspaceData(SpriteAPI sprite, CombatEntityAPI anchor, Vector2f loc, Vector2f offset, Vector2f vel, Vector2f growth, float angle, float spin, boolean parent, float fadein, float full, float fadeout, boolean fade, float time) {
            this.SPRITE = sprite;
            this.ANCHOR = anchor;
            this.LOCATION = loc;
            this.OFFSET = offset;
            this.ANGLE = angle;
            this.VEL = vel;
            this.GROWTH = growth;
            this.SPIN = spin;
            this.PARENT = parent;
            this.FADEIN = fadein;
            this.FULL = full;
            this.FADEOUT = fadeout;
            this.DEATHFADE = fade;
            this.TIME = time;
        }
    }
       
    public static enum positioning{
        CENTER,
        LOW_LEFT,
        LOW_RIGHT,
        UP_LEFT,
        UP_RIGHT,
        STRETCH_TO_FULLSCREEN,
        FULLSCREEN_MAINTAIN_RATIO,
    }
   
    private static class screenspaceData {   
        private final SpriteAPI SPRITE;
        private final positioning POS;
        private Vector2f LOC;
        private final Vector2f VEL;
        private Vector2f SIZE;
        private final Vector2f GROWTH;
        private final float SPIN;
        private final float FADEIN;
        private final float FULL; //fade in + full
        private final float FADEOUT; //full duration
        private float TIME;
       
        public screenspaceData(SpriteAPI sprite, positioning position, Vector2f loc, Vector2f vel, Vector2f size, Vector2f growth, float spin, float fadein, float full, float fadeout, float time) {
            this.SPRITE = sprite;
            this.POS = position;
            this.LOC = loc;
            this.VEL = vel;
            this.SIZE = size;
            this.GROWTH = growth;
            this.SPIN = spin;
            this.FADEIN = fadein;
            this.FULL = full;
            this.FADEOUT = fadeout;
            this.TIME = time;
        }
    }   
}
[close]

« Last Edit: January 13, 2017, 03:38:53 AM by Tartiflette »
Logged
 

Alex

  • Administrator
  • Admiral
  • *****
  • Posts: 23947
    • View Profile
Re: The Radioactive Code Dump
« Reply #125 on: December 19, 2016, 10:04:26 AM »

Hey, that's *really* slick.


Which led to me looking at some of the other code, and I noticed what looks like a problem. This kind of stuff:
Code: java
int chooser=Math.round((float)Math.random()*(i-1)+0.5f);  

If I'm reading the code right, it's assuming that "(float)Math.random()" will produce results in the range [0, 1). But it'll actually - very rarely - return a 1, because of the cast. This will happen when Math.random() returns a double value that's closer to 1 than it is to the highest floating point value that's less than 1.

Since in your code there, i seems to be "size of map being selected from plus one", this code should do to generate the map key:
int chooser = (int)(Math.random() * (i - 1)) + 1;

Or just this:
int chooser = new Random().nextInt(i - 1) + 1;

Fortunately, you're getting from a map rather than an array, so it won't result in an out-of-bounds exception, but will just occasionally return null when it had a valid set of targets to choose from.
Logged

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #126 on: December 19, 2016, 11:11:44 AM »

Thanks, I knew there had to be a simpler way to do it but never bothered checking, will change the many instances where I have that piece of code. ::) Btw since you are here, I was about to ask in the modding questions if there is a way to get a script to run last within a frame? That would solve the one frame delay from the sprite render manager. I'm pretty sure there isn't but never hurt to ask.
Logged
 

Alex

  • Administrator
  • Admiral
  • *****
  • Posts: 23947
    • View Profile
Re: The Radioactive Code Dump
« Reply #127 on: December 19, 2016, 12:22:26 PM »

Thanks, I knew there had to be a simpler way to do it but never bothered checking, will change the many instances where I have that piece of code. ::) Btw since you are here, I was about to ask in the modding questions if there is a way to get a script to run last within a frame? That would solve the one frame delay from the sprite render manager. I'm pretty sure there isn't but never hurt to ask.

Yeah, you're right, there isn't.
Logged

Machine

  • Commander
  • ***
  • Posts: 114
    • View Profile
Re: The Radioactive Code Dump
« Reply #128 on: February 17, 2017, 10:07:31 PM »

I'm posting an improved version of the Hard flux generation for Beam Weapons BeamEffectPlugin, as it was better than my own creation (for one thing it made me notice that I was doing dps damage per frame), but unlike what I did, it achieved its goal by adding bonus damage to shields.

This script solves that issue by modifying directly the target ships flux stats, removing the fraction of flux generated by the beam meant to be hard flux, and re-adding it as such.

However it is still not flawless, since decreaseFlux, does not discriminate between soft or hard flux, the script might just
replace the hard flux that was generated, and so in the very worst case scenario, the net result could be no overall hard flux generation from the beam weapon.
Although during testing I have not seen that happen yet, but I have to admit that the hard flux to soft flux generation, on a test ship with no dissipation, was not a perfect 50%, as the script would want you to believe, instead it favored a little bit more soft flux generation.

Spoiler
Code
package data.scripts.weapons;

import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.combat.BeamEffectPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.WeaponAPI;

public class TSC_HardFluxBeamEffect implements BeamEffectPlugin
{
    public void advance(float amount, CombatEngineAPI engine, BeamAPI beam)
    {
        //Time per Frame
        float frameTime = engine.getElapsedInLastFrame();
//Hard Flux to total Flux generation
        float hFluxRatio = 0.5f;
        //Gets the beam's target
        CombatEntityAPI target = beam.getDamageTarget();
       
        //Checks if a target exist, if it is a Ship, and if shields are being hit.     
        if (target != null && target instanceof ShipAPI && target.getShield() != null && target.getShield().isWithinArc(beam.getTo()))
        {
            //Gets the beam's DPS 
            WeaponAPI weapon = beam.getWeapon();
            float dps = weapon.getDerivedStats().getDps();
           
            //Gets the target ship's shield efficiency
            ShipAPI ship = (ShipAPI)target;
            float absorption = ship.getShield().getFluxPerPointOfDamage();
 
//Modifies the target's flux stats removing part of the beam's soft flux replacing it with hard flux
            //(damage/second)*(flux/damage)*(second/frame) = (flux/frame)
            ship.getFluxTracker().decreaseFlux((dps*absorption*frameTime*(hFluxRatio)));
            ship.getFluxTracker().increaseFlux((dps*absorption*frameTime*(hFluxRatio)),true);
        }
    }
}
[close]
Logged

bananana

  • Commander
  • ***
  • Posts: 226
    • View Profile
Re: The Radioactive Code Dump
« Reply #129 on: January 18, 2018, 03:27:54 PM »

sup, FS.
wrote a bit of code, hope someone will find it useful.
also maybe someone could point out if i did something obviously wrong/inefficient way?

this bit turns beam weapon into a mass relay thingy.
i.e. as long as weapon fires, any fighter ship near beam will be accelerated towards the beam target.
compilation is required.
Spoiler
package data.scripts;

import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lwjgl.util.vector.Vector2f;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import java.util.List;
import com.fs.starfarer.api.util.Misc;
import java.util.ListIterator;

public class TF_BEAM_ACC implements BeamEffectPlugin {

   float delay = 0.05f;
   
   //@Override
    //@SuppressWarnings("AssignmentToMethodParameter")
   
   
   private IntervalUtil fireInterval = new IntervalUtil(delay, delay*3f);
   private boolean wasZero = true;
   
   public void advance(float amount, CombatEngineAPI engine, BeamAPI beam) {
      //CombatEntityAPI beamtarget = beam.getDamageTarget();
      if (//beamtarget instanceof CombatEntityAPI &&
            beam.getBrightness() >= 1f) {
         float dur = beam.getDamage().getDpsDuration();
         // needed because when the ship is in fast-time, dpsDuration will not be reset every frame as it should be
         if (!wasZero) dur = 0;
         wasZero = beam.getDamage().getDpsDuration() <= 0;
         fireInterval.advance(dur);
         
         if (fireInterval.intervalElapsed()) {   
         
               Vector2f dir = Vector2f.sub(beam.getTo(), beam.getFrom(), new Vector2f());

               if (dir.lengthSquared() > 0) dir.normalise();
               dir.scale(50f);
               Vector2f point = Vector2f.sub(beam.getTo(), dir, new Vector2f());

               float length = dir.lengthSquared();
               float area =600f;
               float empmainwidth = 10;
                        
               List<ShipAPI> targets = CombatUtils.getShipsWithinRange(MathUtils.getMidpoint(beam.getTo(), beam.getFrom()),(dir.lengthSquared()/2f));
               targets.addAll(CombatUtils.getShipsWithinRange(beam.getFrom(),area));

                ListIterator<ShipAPI> iter2 = targets.listIterator(targets.size());
                        while (iter2.hasPrevious()) {
                            CombatEntityAPI target = iter2.previous();
                            if (target instanceof ShipAPI) {
                                ShipAPI ship = (ShipAPI) target;
                                if ((ship.getCollisionClass() == CollisionClass.FIGHTER)&&(ship.getParentStation() == null)&&(area > MathUtils.getDistance(ship.getLocation(),Misc.closestPointOnLineToPoint(beam.getTo(),beam.getFrom(),ship.getLocation())))) {
                                   CombatUtils.applyForce(target, dir, 10000);
                           engine.spawnEmpArc(
                                 beam.getSource(),
                                 Misc.closestPointOnLineToPoint(beam.getTo(),beam.getFrom(),ship.getLocation()),
                                 beam.getSource(),
                                 target,
                                 DamageType.ENERGY,
                                 0, // damage
                                 0, // emp
                                 100000f, // max range
                                 "tachyon_lance_emp_impact",
                                 empmainwidth,
                                 beam.getFringeColor(),
                                 beam.getCoreColor()
                           );
                           iter2.remove();
                           continue;
                                } else {
                           iter2.remove();
                           continue;
                        }
                            }

                        }
               


         }
      }
   }   
}
[close]


feedback is welcome, especially negative feedback.
all free to use, NO credit will be welcome.


« Last Edit: January 19, 2018, 03:37:43 AM by passwalker »
Logged
Any and ALL sprites i ever posted on this forum are FREE to use. even if i'm using them myself. Don't ever, EVER ask for permission, or i will come to your home and EAT YOUR DOG!!!
i do NOT want to see my name appear in the credits section of any published mod and will consider it a personal insult.

Twogs

  • Lieutenant
  • **
  • Posts: 76
    • View Profile
Re: The Radioactive Code Dump
« Reply #130 on: May 05, 2018, 04:34:28 AM »

Does the AOE script still work?

Tried it and got a crash ...
Logged

xenoargh

  • Admiral
  • *****
  • Posts: 5078
  • naively breaking things!
    • View Profile
Re: The Radioactive Code Dump
« Reply #131 on: May 07, 2018, 07:04:29 PM »

Did you set up an IDE and compile it?  Most of my stuff won't play nice with Janino, I'm afraid.  That said... it's 5-year-old code... doubtless it needs some updates :)
Logged
Please check out my SS projects :)
Xeno's Mod Pack

xenoargh

  • Admiral
  • *****
  • Posts: 5078
  • naively breaking things!
    • View Profile
Re: The Radioactive Code Dump
« Reply #132 on: April 08, 2021, 02:11:17 PM »

Damage Listener Example

Code for manipulating incoming damage under various circumstances (and a new implementation of the concept behind my Form Shield).  This is mainly an example of how to use DamageListener, but is of interest to anybody interested in the CombatListenerUtil class in general (basically, by extending that, it should be possible to write some very interesting new code for combat).

Please note that this code includes a few references to things in Rebal that don't exist in publicly-released code atm, so you'll need to make minor modifications in places to use this.

Spoiler
Code
//What this does:  scales Beam and Energy damage w/ range, so that closer ranges mean more damage for these weapon types.
package data.scripts.plugins;

import com.fs.starfarer.api.GameState;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ViewportAPI;
import com.fs.starfarer.api.combat.listeners.DamageDealtModifier;
import com.fs.starfarer.api.input.InputEventAPI;

import org.lazywizard.lazylib.MathUtils;
import java.util.List;
import org.lwjgl.util.vector.Vector2f;

public class BeamDamageScalar implements EveryFrameCombatPlugin {
CombatEngineAPI engine;

public static float CurrentAmount;

@Override
public void init(CombatEngineAPI cEngine) {
engine = cEngine;
}

public static float beamDamageAmount(BeamAPI beam, WeaponAPI weapon, float dps){
if(weapon == null) return dps;
if(weapon.getShip() == null) return dps;
//Fixes Beams that have multiple barrels doing absurd damage here (looking at you, Guardian).
float damMul = Math.max((float) weapon.getSpec().getTurretFireOffsets().size(),1f);
damMul *= weapon.getShip().getMutableStats().getBeamWeaponDamageMult().getMult();

//The range is now used to give additional damage output, using how close we are to the target.
//Anything less than 1/3 range is at maximum damage, here at 3X damage (but easily adjusted).
float theRangeMult = beamDistMult(beam, weapon);

return (dps * theRangeMult) / damMul;
}

public static float beamDistMult(BeamAPI beam, WeaponAPI weapon){
if(weapon == null) return 1f;
float maxRange = weapon.getRange();
maxRange *= maxRange;
//The range is now used to give additional damage output, using how close we are to the target.
//Anything less than 1/3 range is at maximum damage, here at 4X damage (but easily adjusted).
return Math.min(Math.max((maxRange / MathUtils.getDistanceSquared(beam.getRayEndPrevFrame(),beam.getFrom())),1f),3f);
}

//Gets energy-weapon damage bonus.
public static float energyDamDistMult(WeaponAPI weapon, Vector2f point){
if(weapon == null) return 1f;
float maxRange = weapon.getRange();
maxRange *= maxRange;
//The range is now used to give additional damage output, using how close we are to the target.
//Anything less than 1/3 range is at maximum damage, here at 4X damage (but easily adjusted).
return Math.min(Math.max((maxRange / MathUtils.getDistanceSquared(point,weapon.getLocation())),1f),1.5f);
}

public static float damageAfterFormShield(ShipAPI ship, DamageAPI damage, float finalDamage){
float mulDamage = finalDamage;
switch(damage.getType()){
case KINETIC:
mulDamage *= 2f;
break;
case HIGH_EXPLOSIVE:
mulDamage *= 0.5f;
break;
case FRAGMENTATION:
//Note: this is actually a BUFF, since normal Frag damage vs. shields is 0.25!
//0.5 (50% damage) got everything murdered by Mining Lasers, lol!
mulDamage *= 0.3f;
break;
default:
break;
}

mulDamage *= 0.5f * captainSkill(ship);
return mulDamage;
}

public static float captainSkill(ShipAPI ship){
return ship.getMutableStats().getShieldDamageTakenMult().getMult();
}

@Override
    public void advance(float amount, List events){
if(engine == null || engine.isPaused() || Global.getCurrentState() != GameState.COMBAT) return;
CurrentAmount = amount;

for(ShipAPI ship : engine.getShips()){
if(ship.hasListenerOfClass(HardFluxBeamMod.class) == false){
ship.addListener(new HardFluxBeamMod(ship));
}
}

for(BeamAPI beam : engine.getBeams()){
if(beam.didDamageThisFrame()){
if(beam.getDamageTarget() instanceof ShipAPI){
ShipAPI target = (ShipAPI) beam.getDamageTarget();
if(target.getShield() == null) continue;
if(target.getShield().isOn()){
if(target.getVariant().hasHullMod("shields_formshield")){
float baseDamage = beam.getDamage().computeDamageDealt(amount);
baseDamage = beamDamageAmount(beam,beam.getWeapon(),baseDamage);
baseDamage = damageAfterFormShield(target,beam.getDamage(),baseDamage);
target.getFluxTracker().increaseFlux(baseDamage, true);
FormShieldPlugin.damagedMap.add(target);
}
}
}
}
}
    }

@Override
public void renderInWorldCoords(ViewportAPI vapi) {
}

@Override
public void renderInUICoords(ViewportAPI vapi) {
}

@Override
public void processInputPreCoreControls(float f, List<InputEventAPI> list) {
}

public static class HardFluxBeamMod implements DamageDealtModifier {
protected ShipAPI ship;
public HardFluxBeamMod(ShipAPI ship) {
this.ship = ship;
}

public String modifyDamageDealt(Object param,
CombatEntityAPI target, DamageAPI damage,
Vector2f point, boolean shieldHit) {
if(param == null || target == null || damage == null) return null;
if(target instanceof ShipAPI == false) return null;
ShipAPI sTarg = (ShipAPI) target;
WeaponAPI weapon = null;

boolean hasFormShield = false;
if(sTarg.getShield() != null){
if(sTarg.getShield().isOn()){
if(sTarg.getVariant().hasHullMod("shields_formshield")){
hasFormShield = true;
}
}
}


if(param instanceof BeamAPI){
BeamAPI beam = (BeamAPI) param;
weapon = beam.getWeapon();
if(hasFormShield){
damage.getModifier().modifyPercent("hardfluxbeam", 0f);
return null;
}
if(beam == null || weapon == null) return null;
float damageMul = beamDistMult(beam,weapon);
damage.getModifier().modifyPercent("hardfluxbeam", (damageMul) * 100f);
damage.setForceHardFlux(true);
} else if(param instanceof DamagingProjectileAPI){
DamagingProjectileAPI proj = (DamagingProjectileAPI) param;
weapon = proj.getWeapon();
boolean handleNullDamage = proj == null || weapon == null;
float finalDamage = proj.getDamageAmount();
float energyDamageBuff = 1f;
//Ignores EZ-Damage weapons, because you get into recursive loops, lol.
boolean ez_exception = handleNullDamage || proj.getProjectileSpecId().equals("ez_damage_shot");
if(proj.getDamageType().equals(DamageType.ENERGY) && !ez_exception && !handleNullDamage){
energyDamageBuff = energyDamDistMult(weapon,point);
damage.getModifier().modifyPercent("hardfluxbeam", (energyDamageBuff) * 100f);
}
if(hasFormShield){
finalDamage = damageAfterFormShield(sTarg, damage, finalDamage * energyDamageBuff);
sTarg.getFluxTracker().increaseFlux(finalDamage, true);
FormShieldPlugin.damagedMap.add(sTarg);
damage.setDamage(0f);
}
//Projectile explodes and is removed.
AAA_Weapon_FX_Plugin.explodingProjectiles.add(proj);
} else {
if(hasFormShield){
sTarg.getFluxTracker().increaseFlux(damage.getDamage()*0.5f*captainSkill(sTarg), true);
damage.getModifier().modifyPercent("hardfluxbeam", 0f);
return null;
}
}
return null;
}
}
}
}
[close]
« Last Edit: April 09, 2021, 07:19:12 PM by xenoargh »
Logged
Please check out my SS projects :)
Xeno's Mod Pack

xenoargh

  • Admiral
  • *****
  • Posts: 5078
  • naively breaking things!
    • View Profile
Re: The Radioactive Code Dump
« Reply #133 on: April 09, 2021, 07:18:40 PM »

Revised this work above.  There were a couple of exceptional circumstances it didn't cover, mainly involving damage of various odd types, like direct damage via code, etc., that needed fixing.
Logged
Please check out my SS projects :)
Xeno's Mod Pack

Anexgohan

  • Ensign
  • *
  • Posts: 48
    • View Profile
Re: The Radioactive Code Dump
« Reply #134 on: February 06, 2022, 12:33:56 AM »

Yet another missile AI, this time with all the knobs and switches you need to customize it anyway you want. No Java skill needed, but it require to be compiled.
Customizable features:
  • ECCM dependent target leading.
  • Different target selection priorities.
  • Option to reengage a new target when the current one got killed.
  • Controllable vanilla-like waving.
  • Oversteering behavior that tries to orient the velocity toward the leading point instead of just pointing the missile at it.
Tested on a hundred missiles at once with no noticeable performance impact.

Spoiler
Code: Java
//By Tartiflette, highly customizable Missile AI.
package data.scripts.ai;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.GuidedMissileAI;
import com.fs.starfarer.api.combat.MissileAIPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipCommand;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.lazywizard.lazylib.CollectionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;

public class CustomMissileAI implements MissileAIPlugin, GuidedMissileAI {
         
   
    //////////////////////
    //     SETTINGS     //
    //////////////////////
   
    //Angle with the target beyond which the missile turn around without accelerating. Avoid endless circling.
    //  Set to a negative value to disable
    private final float OVERSHOT_ANGLE=60;
   
    //Time to complete a wave in seconds.
    private final float WAVE_TIME=2;
   
    //Angle of the waving in degree (divided by 3 with ECCM). Set to a negative value to avoid all waving.
    private final float WAVE_AMPLITUDE=15;
   
    //Damping of the turn speed when closing on the desired aim. The smaller the snappier.
    private final float DAMPING=0.1f;
   
    //Does the missile try to correct it's velocity vector as fast as possible or just point to the desired direction and drift a bit?
    //  Can create strange results with large waving
    //  Require a projectile with a decent turn rate and around twice that in turn acceleration compared to their top speed
    //  Useful for slow torpedoes with low forward acceleration, or ultra precise anti-fighter missiles.     
    private final boolean OVERSTEER=false;  //REQUIRE NO OVERSHOOT ANGLE!
   
    //Does the missile switch its target if it has been destroyed?
    private final boolean TARGET_SWITCH=true;
   
    //Does the missile find a random target or aways tries to hit the ship's one?     
    //   0: No random target seeking,
    //       If the launching ship has a valid target, the missile will pursue that one.
    //       If there is no target selected, it will check for an unselected cursor target.
    //       If there is none, it will pursue its closest valid threat.   
    //   1: Local random target,
    //       If the ship has a target selected, the missile will pick a random valid threat around that one.
    //       If the ship has none, the missile will pursue a random valid threat around the cursor, or itself in AI control.   
    //   2: Full random,
    //       The missile will always seek a random valid threat around itself.
    private final Integer RANDOM_TARGET=0;
   
    //Both targeting behavior can be false for a missile that always get the ship's target or the closest one
    //Prioritize hitting fighters and drones (if false, the missile will still be able to target fighters but not drones)
    private final boolean ANTI_FIGHTER=false;    //INCOMPATIBLE WITH ASSAULT
   
    //Target the biggest threats first
    private final boolean ASSAULT=true;  //INCOMPATIBLE WITH ANTI-FIGHTER
   
    //range in which the missile seek a target in game units.
    private final float MAX_SEARCH_RANGE = 1500;
   
    //range under which the missile start to get progressively more precise in game units.
    private float PRECISION_RANGE=500;
   
    //Is the missile lead the target or tailchase it?
    private final boolean LEADING=true;
   
    //Leading loss without ECCM hullmod. The higher, the less accurate the leading calculation will be.
    //   1: perfect leading with and without ECCM
    //   2: half precision without ECCM
    //   3: a third as precise without ECCM. Default
    //   4, 5, 6 etc : 1/4th, 1/5th, 1/6th etc precision.
    private float ECCM=3;   //A VALUE BELOW 1 WILL PREVENT THE MISSILE FROM EVER HITTING ITS TARGET!
   
   
    //////////////////////
    //    VARIABLES     //
    //////////////////////
   
    //max speed of the missile after modifiers.
    private final float MAX_SPEED;
    //Max range of the missile after modifiers.
    private final float MAX_RANGE;
    //Random starting offset for the waving.
    private final float OFFSET;
    private CombatEngineAPI engine;
    private final MissileAPI MISSILE;
    private CombatEntityAPI target;
    private Vector2f lead = new Vector2f();
    private boolean launch=true;
    private float timer=0, check=0f;

    //////////////////////
    //  DATA COLLECTING //
    //////////////////////
   
    public CustomMissileAI(MissileAPI missile, ShipAPI launchingShip) {
        this.MISSILE = missile;
        MAX_SPEED = missile.getMaxSpeed();
        MAX_RANGE = missile.getWeapon().getRange();
        if (missile.getSource().getVariant().getHullMods().contains("eccm")){
            ECCM=1;
        }       
        //calculate the precision range factor
        PRECISION_RANGE=(float)Math.pow((2*PRECISION_RANGE),2);
        OFFSET=(float)(Math.random()*Math.PI*2);
    }
   
    //////////////////////
    //   MAIN AI LOOP   //
    //////////////////////
   
    @Override
    public void advance(float amount) {
       
        if (engine != Global.getCombatEngine()) {
            this.engine = Global.getCombatEngine();
        }
       
        //skip the AI if the game is paused, the missile is engineless or fading
        if (Global.getCombatEngine().isPaused() || MISSILE.isFading() || MISSILE.isFizzling()) {return;}
       
        //assigning a target if there is none or it got destroyed
        if (target == null
                || target.getOwner()==MISSILE.getOwner()
                || (TARGET_SWITCH && (target instanceof ShipAPI && ((ShipAPI) target).isHulk())
                                  || !engine.isEntityInPlay(target)
                   )
                ){
            setTarget(assignTarget(MISSILE));
            //forced acceleration by default
            MISSILE.giveCommand(ShipCommand.ACCELERATE);
            return;
        }
       
        timer+=amount;
        //finding lead point to aim to       
        if(launch || timer>=check){
            launch=false;
            timer -=check;
            //set the next check time
            check = Math.min(
                    0.25f,
                    Math.max(
                            0.03f,
                            MathUtils.getDistanceSquared(MISSILE, target)/PRECISION_RANGE)
            );
            if(LEADING){
                //best intercepting point
                lead = AIUtils.getBestInterceptPoint(
                        MISSILE.getLocation(),
                        MAX_SPEED*ECCM, //if eccm is intalled the point is accurate, otherwise it's placed closer to the target (almost tailchasing)
                        target.getLocation(),
                        target.getVelocity()
                );               
                //null pointer protection
                if (lead == null) {
                    lead = target.getLocation();
                }
            } else {
                lead = target.getLocation();
            }
        }
       
        //best velocity vector angle for interception
        float correctAngle = VectorUtils.getAngle(
                        MISSILE.getLocation(),
                        lead
                );
       
        if (OVERSTEER){
            //velocity angle correction
            float offCourseAngle = MathUtils.getShortestRotation(
                    VectorUtils.getFacing(MISSILE.getVelocity()),
                    correctAngle
                    );

            float correction = MathUtils.getShortestRotation(               
                    correctAngle,
                    VectorUtils.getFacing(MISSILE.getVelocity())+180
                    )
                    * 0.5f * //oversteer
                    (float)((Math.sin(Math.PI/90*(Math.min(Math.abs(offCourseAngle),45))))); //damping when the correction isn't important

            //modified optimal facing to correct the velocity vector angle as soon as possible
            correctAngle = correctAngle+correction;
        }
       
        if(WAVE_AMPLITUDE>0){           
            //waving
            float multiplier=1;
            if(ECCM<=1){
                multiplier=0.3f;
            }
            correctAngle+=multiplier*WAVE_AMPLITUDE*check*Math.cos(OFFSET+MISSILE.getElapsed()*(2*Math.PI/WAVE_TIME));
        }
       
        //target angle for interception       
        float aimAngle = MathUtils.getShortestRotation( MISSILE.getFacing(), correctAngle);
       
        if(OVERSHOT_ANGLE<=0 || Math.abs(aimAngle)<OVERSHOT_ANGLE){
            MISSILE.giveCommand(ShipCommand.ACCELERATE); 
        }
       
        if (aimAngle < 0) {
            MISSILE.giveCommand(ShipCommand.TURN_RIGHT);
        } else {
            MISSILE.giveCommand(ShipCommand.TURN_LEFT);
        } 
       
        // Damp angular velocity if the missile aim is getting close to the targeted angle
        if (Math.abs(aimAngle) < Math.abs(MISSILE.getAngularVelocity()) * DAMPING) {
            MISSILE.setAngularVelocity(aimAngle / DAMPING);
        }
    }
   
    //////////////////////
    //    TARGETING     //
    //////////////////////
   
    public CombatEntityAPI assignTarget(MissileAPI missile){
       
        ShipAPI theTarget=null;       
        ShipAPI source = missile.getSource();       
        ShipAPI currentTarget;
       
        //check for a target from its source
        if(source != null
                && source.getShipTarget() != null
                && source.getShipTarget() instanceof ShipAPI
                && source.getShipTarget().getOwner() != missile.getOwner()
                ){
            currentTarget=source.getShipTarget();
        } else {
            currentTarget=null;
        }
       
        //random target selection
        if (RANDOM_TARGET>0){ 
            //random mode 1: the missile will look for a target around itsef
            Vector2f location = missile.getLocation();   
            //random mode 2: if its source has a target selected, it will look for random one around that point
            if( RANDOM_TARGET<2){                                     
                if(currentTarget != null
                        && currentTarget.isAlive()
                        && MathUtils.isWithinRange(missile, currentTarget, MAX_RANGE)
                        ){
                    location = currentTarget.getLocation();
                } else if (source != null
                        && source.getMouseTarget()!=null){
                    location=source.getMouseTarget();
                }
            }
            //fetch the right kind of target
            if(ANTI_FIGHTER){
                theTarget = getRandomFighterTarget(location);
            } else if(ASSAULT){
                theTarget = getRandomLargeTarget(location);
            } else {
                theTarget = getAnyTarget(location);
            }   
        //non random targeting   
        } else {
            if(source!=null){
                //ship target first
                if(currentTarget!=null
                        && currentTarget.isAlive()
                        && currentTarget.getOwner()!=missile.getOwner()
                        && !(ANTI_FIGHTER && !(currentTarget.isDrone() && currentTarget.isFighter()))
                        && !(ASSAULT && (currentTarget.isDrone() || currentTarget.isFighter()))
                        ){
                    theTarget=currentTarget;               
                } else {
                    //or cursor target if there isn't one
                    List<ShipAPI> mouseTargets = CombatUtils.getShipsWithinRange(source.getMouseTarget(), 100f);
                    if (!mouseTargets.isEmpty()) {
                        Collections.sort(mouseTargets, new CollectionUtils.SortEntitiesByDistance(source.getMouseTarget()));
                        for (ShipAPI tmp : mouseTargets) {
                            if (tmp.isAlive()
                                    && tmp.getOwner() != missile.getOwner()
                                    && !(ANTI_FIGHTER && !(tmp.isDrone() && tmp.isFighter()))
                                    && !(ASSAULT && (tmp.isDrone() || tmp.isFighter() || tmp.isFrigate()))
                                    ) {
                                theTarget=tmp;
                                break;
                            }
                        }
                    }               
                }
            }
            //still no valid target? lets try the closest one
            //most of the time a ship will have a target so that doesn't need to be perfect.
            if(theTarget==null){
                List<ShipAPI> closeTargets = AIUtils.getNearbyEnemies(missile, MAX_SEARCH_RANGE);
                if (!closeTargets.isEmpty()) {
                    Collections.sort(closeTargets, new CollectionUtils.SortEntitiesByDistance(missile.getLocation()));
                    if (ASSAULT){   //assault missiles will somewhat prioritize toward bigger threats even if there is a closer small one, and avoid fighters and drones.
                        for (ShipAPI tmp : closeTargets) {
                            if (tmp.isAlive()
                                    && tmp.getOwner() != missile.getOwner()
                                    ) {
                                if (tmp.isCapital() || tmp.isCruiser()){
                                    theTarget=tmp;
                                    break;
                                } else if (tmp.isDestroyer() && Math.random()>0.5){
                                    theTarget=tmp;
                                    break;
                                } else if (tmp.isDestroyer() && Math.random()>0.75){
                                    theTarget=tmp;
                                    break;
                                } else if (!tmp.isDrone() && !tmp.isFighter() && Math.random()>0.95){
                                    theTarget=tmp;
                                    break;
                                }
                            }
                        }
                    }else if(ANTI_FIGHTER){    //anti-fighter missile will target the closest drone or fighter
                        for (ShipAPI tmp : closeTargets) {
                            if (tmp.isAlive()
                                    && tmp.getOwner() != missile.getOwner()
                                    && (tmp.isDrone() || tmp.isFighter())
                                    ) {
                                theTarget=tmp;
                                break;
                            }
                        }
                    }else{  //non assault, non anti-fighter missile target the closest non-drone ship
                        for (ShipAPI tmp : closeTargets) {
                            if (tmp.isAlive()
                                    && tmp.getOwner() != missile.getOwner()
                                    && !tmp.isDrone()
                                    ) { 
                                theTarget=tmp;
                                break;
                            }
                        }
                    }
                }
            }
        }       
        return theTarget;
    }
   
    //Random picker for fighters and drones
    public ShipAPI getRandomFighterTarget(Vector2f location){
        ShipAPI select=null;
        Map<Integer, ShipAPI> PRIORITYLIST = new HashMap<>();
        Map<Integer, ShipAPI> OTHERSLIST = new HashMap<>();
        int i=1, u=1;
        List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
        if (!potentialTargets.isEmpty()) {
            for (ShipAPI tmp : potentialTargets) {
                if (tmp.isAlive()
                        && tmp.getOwner() != MISSILE.getOwner()
                        ) {
                    if (tmp.isFighter() || tmp.isDrone()){
                        PRIORITYLIST.put(i, tmp);
                        i++;
                    } else {                           
                        OTHERSLIST.put(u, tmp);
                        u++;
                    }
                }
            }
            if (!PRIORITYLIST.isEmpty()){
                int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
                select=PRIORITYLIST.get(chooser);
            } else if (!OTHERSLIST.isEmpty()){                   
                int chooser=Math.round((float)Math.random()*(u-1)+0.5f);
                select=OTHERSLIST.get(chooser);
            }
        }
        return select;
    }
   
    //Random target selection strongly weighted toward bigger threats in range
    public ShipAPI getRandomLargeTarget(Vector2f location){
        ShipAPI select=null;
        Map<Integer, ShipAPI> PRIORITY1 = new HashMap<>();
        Map<Integer, ShipAPI> PRIORITY2 = new HashMap<>();
        Map<Integer, ShipAPI> PRIORITY3 = new HashMap<>();
        Map<Integer, ShipAPI> PRIORITY4 = new HashMap<>();
        Map<Integer, ShipAPI> OTHERSLIST = new HashMap<>();
        int i=1, u=1, v=1, x=1, y=1;
        List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
        if (!potentialTargets.isEmpty()) {
            for (ShipAPI tmp : potentialTargets) {
                if (tmp.isAlive()
                        && tmp.getOwner() != MISSILE.getOwner()
                        && !tmp.isDrone()
                        ) {
                    if (tmp.isCapital()){
                        PRIORITY1.put(i, tmp);
                        i++;
                        PRIORITY2.put(u, tmp);
                        u++;
                        PRIORITY3.put(x, tmp);
                        x++;
                        PRIORITY4.put(v, tmp);
                        v++;
                        OTHERSLIST.put(y, tmp);
                        y++;
                    } else if (tmp.isCruiser()){
                        PRIORITY2.put(u, tmp);
                        u++;
                        PRIORITY3.put(x, tmp);
                        x++;
                        PRIORITY4.put(v, tmp);
                        v++;
                        OTHERSLIST.put(y, tmp);
                        y++;
                    } else if (tmp.isDestroyer()){
                        PRIORITY3.put(x, tmp);
                        x++;
                        PRIORITY4.put(v, tmp);
                        v++;
                        OTHERSLIST.put(y, tmp);
                        y++;
                    } else if (tmp.isFrigate()){
                        PRIORITY4.put(v, tmp);
                        v++;
                        OTHERSLIST.put(y, tmp);
                        y++;
                    } else {
                        OTHERSLIST.put(y, tmp);
                        y++;
                    }
                }
            }
            if (!PRIORITY1.isEmpty() && Math.random()>0.8f){
                int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
                select=PRIORITY1.get(chooser);
            } else if (!PRIORITY2.isEmpty() && Math.random()>0.8f){
                int chooser=Math.round((float)Math.random()*(u-1)+0.5f);
                select=PRIORITY2.get(chooser);
            } else if (!PRIORITY3.isEmpty() && Math.random()>0.8f){
                int chooser=Math.round((float)Math.random()*(x-1)+0.5f);
                select=PRIORITY3.get(chooser);
            } else if (!PRIORITY4.isEmpty() && Math.random()>0.8f){
                int chooser=Math.round((float)Math.random()*(v-1)+0.5f);
                select=PRIORITY4.get(chooser);
            } else if (!OTHERSLIST.isEmpty()){                   
                int chooser=Math.round((float)Math.random()*(y-1)+0.5f);
                select=OTHERSLIST.get(chooser);
            }
        }
        return select;
    }

    //Pure random target picker
    public ShipAPI getAnyTarget(Vector2f location){
        ShipAPI select=null;
        Map<Integer, ShipAPI> TARGETLIST = new HashMap<>();
        int i=1;
        List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
        if (!potentialTargets.isEmpty()) {
            for (ShipAPI tmp : potentialTargets) {
                if (tmp.isAlive()
                        && tmp.getOwner() != MISSILE.getOwner()
                        && !tmp.isDrone()
                        ){
                    TARGETLIST.put(i, tmp);
                    i++;                       
                }
            }
            if (!TARGETLIST.isEmpty()){
                int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
                select=TARGETLIST.get(chooser);
            }
        }
        return select;
    }
   
    @Override
    public CombatEntityAPI getTarget() {
        return target;
    }

    @Override
    public void setTarget(CombatEntityAPI target) {
        this.target = target;
    }
   
    public void init(CombatEngineAPI engine) {}
}
[close]

Hey, everyone,
I was looking at your forum post here

I'm making some missiles for the game and would love to implement this for my missiles, sadly I have zero coding background could you give me any pointers if it's possible for someone like me to implement this as you said "no java skills needed"
thankyou for reading
Logged
Pages: 1 ... 7 8 [9] 10