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 ... 6 7 [8] 9 10

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

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #105 on: April 07, 2015, 05:10:50 PM »

A big one this time: Custom Muzzle flashes and animation plugin!

It started with this weapon for witch I wanted a muzzle animation independent from the barrel animation, but the glow just wouldn't cut it:
Spoiler
[close]

So I made a plugin that when it get it's MEMBERS Hashmap fed with WeaponAPIs by weapon scripts, it add muzzle flashes to them.

The plugin:
Code: Java
//By Tartiflette, allows for custom muzzle flashes and other weapons animations without damage decals troubles
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.ViewportAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.lazywizard.lazylib.MathUtils;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.glEnable;
import org.lwjgl.util.vector.Vector2f;

public class SCY_muzzleFlashesPlugin extends BaseEveryFrameCombatPlugin {
  
    //MEMBERS is the core of the script, storing both the weapons that have a flash rendered and the index of the current sprite used
    public static Map< WeaponAPI , Float > MEMBERS = new HashMap<>();
    //set the function to access MEMBERS from the weapons scripts    
    public static void addMember(WeaponAPI weapon, Float index) {
        MEMBERS.put(weapon, index);
    }
    public static void removeMember(WeaponAPI weapon) {
        MEMBERS.remove(weapon);
    }
    
    private List<WeaponAPI> toRemove = new ArrayList<>();
    
    //All the info needed, per muzzle type.
    //minigun MKI
    public final String MINIGUN_I_ID = "SCY_minigun_mki";
    //load the muzzle sprites map. Since it's a random muzzle I keep the index 0 free to skip some frame from time to time.
    private final Map<Integer, SpriteAPI> MINIGUN_I = new HashMap<>();    
    {
        MINIGUN_I.put(1, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle01"));  
        MINIGUN_I.put(2, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle02"));  
        MINIGUN_I.put(3, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle03"));  
        MINIGUN_I.put(4, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle04"));
        MINIGUN_I.put(5, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle05"));
        MINIGUN_I.put(6, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle06"));
    }
    //set the muzzle sprite size
    private final float minigun1Width = 28;
    private final float minigun1Height = 53;
    //set the offset from the weapon's center. T for turret, H for Hardpoint. I always keep my muzzle sprites on the weapon axis to only have to worry about the length offset.
    private final float minigun1TOffset=25;
    private final float minigun1HOffset=25;
    
    //KAcc MKII
    public final String KACC2_ID = "SCY_kacc_mkii";
    private final Map<Integer, SpriteAPI> KACC2 = new HashMap<>();
    {
        KACC2.put(0, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle00"));  
        KACC2.put(1, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle01"));
        KACC2.put(2, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle02"));
        KACC2.put(3, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle03"));
        KACC2.put(4, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle04"));
        KACC2.put(5, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle05"));
    }
    private final float kacc2Width = 34;
    private final float kacc2Height = 48;
    private final float kacc2TOffset = 11;
    private final float kacc2HOffset = 15;
    private final float kacc2Delay = 0.05f;
    
    @Override
    public void init(CombatEngineAPI engine) {
        //initialise all the data
        MEMBERS.clear();        
    }
    
    @Override
    public void renderInWorldCoords(ViewportAPI view)
    {
        CombatEngineAPI engine = Global.getCombatEngine();
        if (engine == null){return;}
        
        if (!MEMBERS.isEmpty())
        {
            float amount = (engine.isPaused() ? 0f : engine.getElapsedInLastFrame());
            
            //dig through the MEMBERS
            for (Iterator<Map.Entry< WeaponAPI , Float >> iter = MEMBERS.entrySet().iterator(); iter.hasNext(); ) {                
                Map.Entry< WeaponAPI , Float > entry = iter.next();  
                //Apply the right effect for the right type of weapon
                switch (entry.getKey().getId()) {
                    
                    case MINIGUN_I_ID:
                        //Check if the ship is in play
                        if (!engine.isEntityInPlay(entry.getKey().getShip())){
                            toRemove.add(entry.getKey());
                            break;
                        }
                        //random muzzle flash
                        //turret or hardpoint offset
                        float minigun1Offset;
                        if(entry.getKey().getSlot().isHardpoint()){
                            minigun1Offset=minigun1HOffset;
                        } else {
                            minigun1Offset=minigun1TOffset;
                        }
                        //change the muzzle sprite 8 out of 10 frames if the engine isn't paused
                        if (!engine.isPaused() && Math.random()>0.2f){
                            //place the new muzzle sprite index in the MEMBERS map
                            MEMBERS.put(entry.getKey(), (float)MathUtils.getRandomNumberInRange(0, 6));
                        }
                        //skip if the index is 0, to give accidents in the animation like the real thing
                        if (entry.getValue()!=0){
                            //call the render                            
                            render(entry.getKey(), MINIGUN_I.get((int)Math.round(entry.getValue())), minigun1Width, minigun1Height, minigun1Offset, true);                            
                        }                        
                        break;
                        
                    case KACC2_ID:
                        //animated muzzle flash
                        //turret or hardpoint offset
                        float kacc2Offset;
                        if(entry.getKey().getSlot().isHardpoint()){
                            kacc2Offset=kacc2HOffset;
                        } else {
                            kacc2Offset=kacc2TOffset;
                        }
                        int kacc2Frame;
                        //put the animation timer as the MEMBERS' value
                        //randomly keep the muzzle frame a bit longer
                        if (!engine.isPaused() && Math.random()>0.25){
                            MEMBERS.put(entry.getKey(), amount+entry.getValue());                      
                        }
                        //calculate the current frame from the animation's time
                        kacc2Frame = (int)Math.abs(entry.getValue()/kacc2Delay);
                        //add to the remove list and break if the animation is finished
                        if (kacc2Frame>KACC2.size()-1){
                            toRemove.add(entry.getKey());
                            break;
                        }  
                        //randomly flip the muzzle flash
                        float flip2 = 1;
                        if (Math.sin(100*entry.getValue())>0 ){flip2=-1;}
                        //call the renderer
                        render(entry.getKey(), KACC2.get(kacc2Frame), flip2*kacc2Width, kacc2Height, kacc2Offset, true);                          
                        break;
                }
            }
            //remove the weapons that needs to
            //can't be done from within the iterator or it will fail when members will be missing
            if (!toRemove.isEmpty()){
                for(WeaponAPI w : toRemove ){
                    MEMBERS.remove(w);
                }
                toRemove.clear();
            }
        }
    }
    
    private void render (WeaponAPI weapon, SpriteAPI sprite, float width, float height, float offset, boolean additive){
        //where the magic happen
        sprite.setAlphaMult(1);
        sprite.setSize(width, height);
        if (additive){
            sprite.setAdditiveBlend();
        }
        float aim = weapon.getCurrAngle();
        Vector2f loc =  MathUtils.getPointOnCircumference(weapon.getLocation(),offset,aim); //apply muzzle the offset
        sprite.setAngle(aim-90);
        sprite.renderAtCenter(loc.x, loc.y);

    }
}
I made two example of animation types: the first MINIGUN_ID pull random sprites from a list until the weapon's script removes it, the second ORION_ID play an animation and get removed at the end of it.
The animation call look like this:
Code: Java
//By Tartiflette
package data.scripts.weapons;

import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
import com.fs.starfarer.api.combat.WeaponAPI;
import data.scripts.plugins.SCY_muzzleFlashesPlugin;

public class SCY_addToMuzzlePlugin implements EveryFrameWeaponEffectPlugin{
    
    private boolean put=false;
    private boolean runOnce=false;
    private boolean hidden=false;
    
    @Override
    public void advance (float amount, CombatEngineAPI engine, WeaponAPI weapon) {
        
        if(!hidden && engine.isPaused()){return;}
        
        if(!runOnce){
            runOnce=true;
            //check if the mount is hidden
            if (weapon.getSlot().isHidden()){
                hidden=true;
                return;
            }
        }
        
        //assign the weapon for the muzzle flash plugin
        if (weapon.getChargeLevel()==1){
            //add the weapon to the MEMBERS map when it fires
            //"put" is to make sure it's added only once
            if (!put){
                put=true;            
                SCY_muzzleFlashesPlugin.addMember(weapon,0f);
            }
        } else if (weapon.getChargeLevel()== 0){
            //reinitialise "put"
            if (put){
                put=false;
            }
        }
    }
}
Simple, the weapon fires, it get added to MEMBERS and the plugin play the animation.
For the random flashes:
Code: Java
        if (!hidden){
            //assign the weapon for the muzzle flash plugin
            if (weapon.getChargeLevel()==1){
                //add the weapon to the MEMBERS map
                if (!put){
                    put=true;            
                    SCY_muzzleFlashesPlugin.addMember(weapon,0f);
                }
            } else if (weapon.getChargeLevel()== 0){
                //remove the weapon from the MEMBERS map
                if (put){
                    put=false;
                    SCY_muzzleFlashesPlugin.removeMember(weapon);
                }
            }
        }
    }
}
This one removes the weapon from MEMBERS when it stops firing.
Of course the sprites used need to be loaded in the settings:
Code
	"muzzle":{
"SCY_minigunMki_muzzle01":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_01.png",
"SCY_minigunMki_muzzle02":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_02.png",
"SCY_minigunMki_muzzle03":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_03.png",
"SCY_minigunMki_muzzle04":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_04.png",
"SCY_minigunMki_muzzle05":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_05.png",
"SCY_minigunMki_muzzle06":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_06.png",

"SCY_orionMkiii_muzzle01":"graphics/SCY/weapons/SCY_orionMkiii/SCY_orionMkiiiMuzzle_01.png",
"SCY_orionMkiii_muzzle02":"graphics/SCY/weapons/SCY_orionMkiii/SCY_orionMkiiiMuzzle_02.png",
"SCY_orionMkiii_muzzle03":"graphics/SCY/weapons/SCY_orionMkiii/SCY_orionMkiiiMuzzle_03.png",
},
And that's it, add the weapon to the MEMBERS via a everyframeWeaponEffect, set all the sprites in the options and the init and it should works fine as it is.
[EDIT] changed the weapons scripts to avoid being called on weapons in hidden mounts. Depending on the weapon this could or couldn't be necessary.
[EDIT2] corrected a second slight mistake that made the animated flashes not display properly if the game is paused.
[EDIT3] added separate turret offset and hardpoint offset, also changed one of the animation example by one that randomly hold the frame longer or flip it to create more variations.
[EDIT4] variable are now final initialized: having them in the init was useful to play with the offset values in debug mode without relaunching the game, but caused NPE on campaign map. Also converted the Sprites key to integer.
« Last Edit: June 05, 2015, 12:02:31 AM by Tartiflette »
Logged
 

xenoargh

  • Admiral
  • *****
  • Posts: 5078
  • naively breaking things!
    • View Profile
Re: The Radioactive Code Dump
« Reply #106 on: April 09, 2015, 10:04:52 PM »

Cool, there are lots of good uses for that :)  Thanks for sharing the framework!
Logged
Please check out my SS projects :)
Xeno's Mod Pack

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #107 on: April 10, 2015, 12:05:03 AM »

Lots of good uses indeed though as you can guess, these muzzles are drawn above everything else. Even explosions. That's why I used additive blending but still I have some problems with overlapping weapons, or fighters flying above the weapon but under the flash... That's why I avoid using it for small weapons (besides the small minigun 'cause I have no choice). With that little problem in mind, yes it could be customized for other useful stuff. From cool ship-system area effects, to custom ship explosions.
Logged
 

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #108 on: April 12, 2015, 03:28:47 AM »

Also putting here my simple missile and rocket AIs. I think they are easy to understand and customize. Also since they are "simple" they are quite fast on the CPU, and spamming missiles is much less an issue.

Code: Java
//By Tartiflette, simple and fast Missile AI that will lead it's target and avoid endless circling. Also Engage a new target if the current one get destroyed
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 com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;

public class SCY_simpleMissileAI implements MissileAIPlugin, GuidedMissileAI {
    
    private CombatEngineAPI engine;
    private final MissileAPI missile;
    private CombatEntityAPI target;
    private Vector2f lead = new Vector2f(0f,0f);
    
    // Distance under witch the missile cease to lead the target aim directly for it
    private final float closeRange = 750;
    // Max area the missile seek a target in if the launching ship don't have one
    private final float maxSearchRange = 1000;
    // Angle with the target beyond witch the missile will first turn around before accelerating again
    private final float overshoot = 45;
    //data
    private final float flightSpeed;
    private final static float damping = 0.1f;
    //delay between target actualisation
    private final IntervalUtil actualisation = new IntervalUtil(0.2f, 1f);
    private boolean launch=true;
    private boolean runOnce=false;
    private float eccm=3;

    //////////////////////
    //  DATA COLLECTING //
    //////////////////////
    
    public SCY_simpleMissileAI(MissileAPI missile, ShipAPI launchingShip) {
        this.missile = missile;
        flightSpeed = missile.getMaxSpeed();
    }
    
    //////////////////////
    //   MAIN AI LOOP   //
    //////////////////////
    
    @Override
    public void advance(float amount) {
        
        if (engine != Global.getCombatEngine()) {
            this.engine = Global.getCombatEngine();
        }        
        if(!runOnce){
            runOnce=true;            
            for (String s : missile.getSource().getVariant().getHullMods()) {
                if (s.equals("ECCMPackage")){
                    eccm=1;
                }
            }
        }
        
        //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 instanceof ShipAPI && ((ShipAPI) target).isHulk()) //comment out this line to remove target reengagement
                ){
            setTarget(assignTarget(missile));
            //forced acceleration by default
            missile.giveCommand(ShipCommand.ACCELERATE);
            return;
        }
      
        actualisation.advance(amount);
        //fiding lead point to aim to
        if(launch || actualisation.intervalElapsed()){
            launch=false;
            if (MathUtils.getDistance(missile, target) <= closeRange) {
                lead = target.getLocation();
            } else {
                lead = AIUtils.getBestInterceptPoint(missile.getLocation(), flightSpeed*eccm, target.getLocation(), target.getVelocity());
                lead = MathUtils.getRandomPointInCircle(lead,Math.max(0,MathUtils.getDistance(missile, target)-closeRange)); //comment out this line to remove the waving during the flight    
            }
        }
        
        if(lead == null) {
            return; //just in case to make sure a correct lead has been calculated
        }
          
        //aimAngle = angle between the missile facing and the lead direction
        float aimAngle = MathUtils.getShortestRotation(missile.getFacing(), VectorUtils.getAngle(missile.getLocation(), lead));

        //if the missile overshoot the target, turn around
        if (Math.abs(aimAngle) > overshoot) {
             if (aimAngle < 0) {
                 missile.giveCommand(ShipCommand.TURN_RIGHT);
             } else {
                 missile.giveCommand(ShipCommand.TURN_LEFT);
             }              
            
        } else {
            //if the lead is forward, turn the missile toward the lead accelerating
            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);
        }
    }
    
    //////////////////////
    //    TARGETTING    //
    //////////////////////
    
    public CombatEntityAPI assignTarget(MissileAPI missile)
    {
        ShipAPI currentTarget = missile.getSource().getShipTarget();
        
        if (currentTarget != null &&
                !currentTarget.isFighter() &&
                !currentTarget.isDrone() &&
                currentTarget.isAlive() &&
                currentTarget.getOwner()!=missile.getOwner()){
            //return the ship's target if it's valid
            return (CombatEntityAPI)currentTarget;            
        } else {
            //else return the closest enemy if in range
            ShipAPI theEnemy = AIUtils.getNearestEnemy(missile);
            if (theEnemy!=null && MathUtils.getDistance(missile,theEnemy)<=maxSearchRange){
                return AIUtils.getNearestEnemy(missile);
            } else {return null;}
        }
    }

    @Override
    public CombatEntityAPI getTarget() {
        return target;
    }

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

Code: Java
//By Tartiflette, simple and fast rocket AI that will try to attack a target in a frontal cone, and not reengage any if it misses.
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 org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;

public class SCY_simpleRocketAI implements MissileAIPlugin, GuidedMissileAI {
    
    private CombatEngineAPI engine;
    private final MissileAPI missile;
    private CombatEntityAPI target;
    private Vector2f lead = new Vector2f(0f,0f);
    private boolean overshoot = false;
    private boolean runOnce = false;
    //data
    private final float flightSpeed;
    private final float maxSearchRange = 2000;
    //if the ship has no target, search one in this forward cone
    private final float searchCone = 35;
    //if the ship's target is out of this attack cone, it can't be reached
    private final float cancellingCone = 45;
    private final float damping = 0.1f;
    
    private float offsetX=0, offsetY=0;
    private boolean eccm=false;

    //////////////////////
    //  DATA COLLECTING //
    //////////////////////
    
    public SCY_simpleRocketAI(MissileAPI missile, ShipAPI launchingShip) {
        this.missile = missile;
        flightSpeed = missile.getMaxSpeed();
        for (String s : missile.getSource().getVariant().getHullMods()) {
            if (s.equals("ECCMPackage")){
                eccm=true;
                break;
            }
        }
    }
    
    //////////////////////
    //   MAIN AI LOOP   //
    //////////////////////
    
    @Override
    public void advance(float amount) {
        
        if (engine != Global.getCombatEngine()) {
            this.engine = Global.getCombatEngine();
        }
        
        // assign a target only once
        if (!runOnce){
            setTarget(assignTarget(missile));
            if (target!=null && !eccm){
                offsetX=MathUtils.getRandomNumberInRange(0, target.getCollisionRadius()/2);
                offsetY=MathUtils.getRandomNumberInRange(0, target.getCollisionRadius()/2);
            }
            runOnce=true;            
        }
        
        //always accelerate
        missile.giveCommand(ShipCommand.ACCELERATE);
        
        //skip the AI if the game is paused, the missile is way off course, engineless or without a target
        if (Global.getCombatEngine().isPaused()
                || overshoot
                || missile.isFading()
                || missile.isFizzling()
                || target == null) {
            return;
        }        
        
        //fiding lead point to aim to
        lead = AIUtils.getBestInterceptPoint(missile.getLocation(), flightSpeed, target.getLocation(), target.getVelocity());
        lead = new Vector2f(lead.x+offsetX,lead.y+offsetY);
        
        if(lead == null) {
            return; //just in case to makes sure a correct lead has been calculated
        }
          
        //aimAngle = angle between the missile facing and the lead direction
        float aimAngle = MathUtils.getShortestRotation(missile.getFacing(), VectorUtils.getAngle(missile.getLocation(), lead));
      
        //if the missile overshoot the target, just shut the AI  
        if (Math.abs(aimAngle) > 90) {
            overshoot = true;
            return;                
            
        } else {
            //if the lead is forward, turn the missile toward the lead accelerating
                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);
        }
    }
    
    //////////////////////
    //    TARGETTING    //
    //////////////////////
    
    public CombatEntityAPI assignTarget(MissileAPI missile)
    {
        ShipAPI source = missile.getSource();
        ShipAPI currentTarget = source.getShipTarget();
        
        if (currentTarget != null &&
                !currentTarget.isFighter() &&
                !currentTarget.isDrone() &&
                currentTarget.isAlive() &&
                currentTarget.getOwner()!=missile.getOwner() &&
                //current target is in the attack cone
                Math.abs(MathUtils.getShortestRotation(missile.getFacing(), VectorUtils.getAngle(missile.getLocation(), currentTarget.getLocation()))) < cancellingCone){
            //return the ship's target if it's valid
            return (CombatEntityAPI)currentTarget;            
        } else {
            //search for the closest enemy in the cone of attack
            ShipAPI closest = null;
            float distance, closestDistance = Float.MAX_VALUE;
            //grab all nearby enemies
            for (ShipAPI tmp : AIUtils.getNearbyEnemies(missile, maxSearchRange))
            {
                //rule out ships out of the missile attack cone
                if (Math.abs(MathUtils.getShortestRotation(missile.getFacing(), VectorUtils.getAngle(missile.getLocation(), tmp.getLocation()))) > searchCone)
                {
                    continue;
                }
                //sort closest enemy
                distance = MathUtils.getDistance(tmp, missile.getLocation());  
                if (distance < closestDistance)
                {
                    closest = tmp;
                    closestDistance = distance;
                }
            }
            //return the closest enemy
            return closest;
        }
    }

    @Override
    public CombatEntityAPI getTarget() {
        return target;
    }

    @Override
    public void setTarget(CombatEntityAPI target) {
        this.target = target;
    }
    
    public void init(CombatEngineAPI engine) {}
}
« Last Edit: May 03, 2015, 01:32:36 PM by Tartiflette »
Logged
 

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #109 on: June 11, 2015, 07:39:50 AM »



Scratch that script! I show a much better version in my next post!


Spoiler
Another one that might interest other people:
With Deathfly we managed to create some pretty convincing fake beams that can be spawned from anywhere.
Spoiler


[close]

They impact instantly, deal soft flux, do not mess so much with PD weapons as the invisible missile trick. The core of the idea is still a dummy missile, but this time it's only for manipulating it's beam-like sprite and it's not moving toward the target. The script is the dummy missile's AI, that way it's instantiated and get removed with the beam fading. I added the few files I used in this example for reference.

Code: Java
//Fake beam AI script: stretches a "beam" shaped missile to the nearest intersection with any ship or asteroid and apply damage on impact.
//By Tartiflette and Deathfly
package data.scripts.ai;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.CombatAsteroidAPI;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
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.ShieldAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import static com.fs.starfarer.api.combat.WeaponAPI.WeaponSize.LARGE;
import static com.fs.starfarer.api.combat.WeaponAPI.WeaponSize.MEDIUM;
import java.awt.Color;
import java.awt.geom.Line2D;
import java.util.List;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;

public class SCY_fakeBeamAI implements MissileAIPlugin, GuidedMissileAI {
    
    private CombatEngineAPI engine;
    private final MissileAPI missile;
    private CombatEntityAPI target=null;
    private Vector2f start = new Vector2f();
    private boolean runOnce = false;
    private float aim=0, timer=0;
    
    //DATA
    //beam MAX range
    private float range = 500;
    //beam DURATION in seconds
    private final float duration = 0.5f;
    //beam WIDTH in su
    private final float width = 15;
    //beam burst DAMAGE
    private final float defaultDamage = 200;
    //beam damage TYPE
    private final DamageType type = DamageType.HIGH_EXPLOSIVE;
    //beam EMP
    private final float emp = 50;
    //impact SIZE
    private final float size = 50;
    //impact COLOR
    private final Color color = new Color (150,200,255,255);

    //////////////////////
    //  DATA COLLECTING //
    //////////////////////
    
    public SCY_fakeBeamAI(MissileAPI missile, ShipAPI launchingShip) {
        this.missile = missile;
        
        //adjust the range depending on the source weapon size
        WeaponAPI.WeaponSize wSize = missile.getWeapon().getSize();
        if (wSize.equals((WeaponAPI.WeaponSize)LARGE)){
            range = 700f;
        } else {
            if (wSize.equals((WeaponAPI.WeaponSize)MEDIUM)){
                range = 600f;
            } else {
                range = 500f;
            }
        }
    }
    
    //////////////////////
    //   MAIN AI LOOP   //
    //////////////////////
    
    @Override
    public void advance(float amount) {
        
        if (engine != Global.getCombatEngine()) {
            this.engine = Global.getCombatEngine();
        }        
        //cancelling IF
        if (Global.getCombatEngine().isPaused()) {return;}
                
        timer+=amount;
        
        //random delay
        if (Math.random()+timer*5 <= 0.85f){return;} //comment out this line to remove the random delay
            
        //only run once, with a random delay first
        if (!runOnce){          
                        
            start = missile.getLocation();
            aim = missile.getFacing();            
            CombatEntityAPI theTarget= null;
            float damage = defaultDamage;
    
            //default end point
            Vector2f end = MathUtils.getPointOnCircumference(start,range,aim);
            
            //list all nearby entities that could be hit
            List <CombatEntityAPI> entity = CombatUtils.getEntitiesWithinRange(start, range+250);
            if (!entity.isEmpty()){
                for (CombatEntityAPI e : entity){
                                        
                    //ignore phased ships
                    if (e.getCollisionClass() == CollisionClass.NONE){continue;}

                    //damage can be reduced against some modded ships
                    float newDamage = defaultDamage;

                    Vector2f col = new Vector2f();                  
                    //ignore everything but ships...
                    if (
                            e instanceof ShipAPI
                            &&
                            CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())
                            ){
                        //check for a shield impact, then hull and take the closest one                  
                        ShipAPI s = (ShipAPI) e;

                        //can the beam intersect a shield
                        Vector2f cShield = getShieldCollisionPoint(start, end, s);
                        if ( cShield != null ){
                            col = cShield;
                        }  

                        //now check for a direct hit
                        Vector2f cHull = CollisionUtils.getCollisionPoint(start, end, s);
                        if (
                                cHull != null
                                &&
                                MathUtils.getDistance(start, col) > MathUtils.getDistance(start, cHull)
                                ){
                            col = cHull;

                            //check for modded ships with damage reduction
                            if (s.getHullSpec().getBaseHullId().startsWith("exigency_")){
                                newDamage = defaultDamage/2;
                            }
                        }                    
                    } else
                        //...and asteroids!
                           if (
                                   e instanceof CombatAsteroidAPI
                                   &&
                                   CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())
                                   ){                              
                        Vector2f cAst = getCollisionPointOnCircumference(start,end,e.getLocation(),e.getCollisionRadius());
                        if ( cAst != null){
                            col = cAst;
                        }
                    }

                    //if there was an impact and it is closer than the curent beam end point, set it as the new end point and store the target to apply damage later damage
                    if (col != new Vector2f() && MathUtils.getDistance(start, col) < MathUtils.getDistance(start, end)) {
                        end = col;
                        theTarget = e;
                        damage = newDamage;
                    }
                }
            }
            
            //if the beam impacted something, apply the damage
            if (theTarget!=null){
                
                //damage
                engine.applyDamage(
                        theTarget,
                        end,
                        damage,
                        type,
                        emp,
                        false,
                        true,
                        missile.getSource()
                );
                //impact flash
                engine.addHitParticle(
                        end,
                        theTarget.getVelocity(),
                        (float)Math.random()*size/2+size,
                        1,
                        (float)Math.random()*duration/2+duration,
                        color
                );
                engine.addHitParticle(
                        end,
                        theTarget.getVelocity(),
                        (float)Math.random()*size/4+size/2,
                        1,
                        0.1f,
                        Color.WHITE
                );
            }          
            //stretch the "missile" sprite to the impact point, plus a little margin
            float length = MathUtils.getDistance(start, end);
            missile.getSpriteAPI().setHeight(length+10);            
            missile.getSpriteAPI().setAdditiveBlend();
            //never run this again
            runOnce = true;
        }
        
        //check for removal
        if (timer>=duration){
            engine.removeEntity(missile);
            return;
        }
        
        //check if the position and orientation has been changed by other scripts and weapons
        if (missile.getLocation() != start){
            setLocation(missile, start);
        }        
        if (missile.getFacing() != aim){
            missile.setFacing(aim);
        }
        
        //fading
        float fadeColor = Math.max(0,(Math.min(1,(float) Math.cos(timer * Math.PI/(2*duration)))));
        //shrinking
        float fadeWidth = width*fadeColor;
        
        missile.getSpriteAPI().setWidth(fadeWidth);
        missile.getSpriteAPI().setCenterY(fadeWidth/2);
        missile.getSpriteAPI().setColor(new Color(fadeColor,fadeColor,fadeColor,fadeColor));        
    }

    @Override
    public CombatEntityAPI getTarget() {
        return target;
    }

    @Override
    public void setTarget(CombatEntityAPI target) {
        this.target = target;
    }
    
    /////////////////////////////////////////
    //                                     //
    //             SHIELD HIT              //
    //                                     //
    /////////////////////////////////////////
    
    public static Vector2f getShieldCollisionPoint(Vector2f lineStart, Vector2f lineEnd, ShipAPI ship){
        // if target not shielded, return null
        ShieldAPI shield = ship.getShield();
        if (shield == null || shield.isOff()) return null;
        Vector2f circleCenter = shield.getLocation();
        float circleRadius = shield.getRadius();
        // calculate the collision point
        Vector2f tmp = new Vector2f(lineEnd);
        boolean shieldHit = false;
        Vector2f tmp1 = getCollisionPointOnCircumference(lineStart,lineEnd, circleCenter, circleRadius);
        if (tmp1 != null){
            if(shield.isWithinArc(tmp1)) {
            tmp = tmp1;
            shieldHit = true;
            }      
            // just in case...
            // if the hit come outside the shield's arc but did not hit the hull and hit the shield's "edge".
            if (!shield.isWithinArc(tmp1)){
                // find the hit point on shield's "edge"
                Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc()/2));
                Vector2f tmp2 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge1);
                tmp = tmp2 == null ? tmp: tmp2;
                Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc()/2));
                Vector2f tmp3 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge2);
                tmp = tmp3 == null ? tmp : tmp3;
                // make sure the line did not hit hull.
                if (CollisionUtils.getCollisionPoint(lineStart, tmp, ship) == null){
                    shieldHit = true;
                }
            }
        }
        return shieldHit? tmp : null;        
    }
    
    /////////////////////////////////////////
    //                                     //
    //       CIRCLE COLLISION POINT        //
    //                                     //
    /////////////////////////////////////////
    
    // return the first intersection point of segment lineStart to lineEnd and circumference.
    // if lineStart is outside the circle and segment can not intersection with the circumference, will return null
    // if lineStart is inside the circle, will return lineStart.
    public static Vector2f getCollisionPointOnCircumference(Vector2f lineStart, Vector2f lineEnd, Vector2f circleCenter, float circleRadius){
        
        Vector2f startToEnd = Vector2f.sub(lineEnd, lineStart, null);
        Vector2f startToCenter = Vector2f.sub(circleCenter, lineStart, null);
        double ptSegDistSq = (float) Line2D.ptSegDistSq(lineStart.x, lineStart.y, lineEnd.x, lineEnd.y, circleCenter.x, circleCenter.y);
        float circleRadiusSq = circleRadius * circleRadius;
        
        // if lineStart is outside the circle and segment can not reach the circumference, return null
        if (ptSegDistSq > circleRadiusSq || (startToCenter.lengthSquared() >= circleRadiusSq && startToCenter.lengthSquared()>startToEnd.lengthSquared())) return null;
        // if lineStart is within the circle, return it directly
        if (startToCenter.lengthSquared() < circleRadiusSq) return lineStart;
        
        // calculate the intersection point.
        startToEnd.normalise(startToEnd);
        double dist = Vector2f.dot(startToCenter, startToEnd) -  Math.sqrt(circleRadiusSq - ptSegDistSq);
        startToEnd.scale((float) dist);
        return Vector2f.add(lineStart, startToEnd, null);
    }
    
    /////////////////////////////////////////
    //                                     //
    //        SET TELEPORT LOCATION        //
    //                                     //
    /////////////////////////////////////////
    
    public void setLocation(CombatEntityAPI entity, Vector2f location) {  
        Vector2f dif = new Vector2f(location);  
        Vector2f.sub(location, entity.getLocation(), dif);  
        Vector2f.add(entity.getLocation(), dif, entity.getLocation());  
    }
}

[edit1] slightly modified the code to make the beams impervious to most scripts except the ones that replace a missile AI (like flares).
[edit2] New shield hit calculation from Deathfly that detect "sideway" hits to shields.
[edit3] Further refinement to avoid missfires: beams now ignore projectiles, most notably flares.
[edit4] More misfire avoidance: the lasers now ignore anything that isn't either a ship or an asteroid. Also corrected an issue while hitting shields from the "back".
[edit5] Should be good now, uploaded the latest files, swapped the missile to FLARE type so that it get ignored by most AI, checked for Exigency ships beam damage reduction and cleaned up the target type detection.
[edit6] Those bugs are getting on my nerves...

[close]

[attachment deleted by admin]
« Last Edit: June 16, 2015, 01:44:05 PM by Tartiflette »
Logged
 

19_30s

  • Ensign
  • *
  • Posts: 28
    • View Profile
Re: The Radioactive Code Dump
« Reply #110 on: June 11, 2015, 09:09:38 AM »

Good Contribution ;D
Logged

Sproginator

  • Admiral
  • *****
  • Posts: 3592
  • Forum Ancient
    • View Profile
Re: The Radioactive Code Dump
« Reply #111 on: June 11, 2015, 10:33:51 AM »

Cool mod! I could really do with some help getting Lazy's old Armor Piercing weapon script to work
Logged
A person who's never made a mistake, never tried anything new
- Albert Einstein

As long as we don't quit, we haven't failed
- Jamie Fristrom (Programmer for Spiderman2 & Lead Developer for Energy Hook)

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #112 on: June 16, 2015, 02:01:39 PM »

As I was saying, with Deathfly we managed to create some pretty convincing fake beams that can be spawned from anywhere.
Spoiler


[close]

Fake-beam V2.0, look exactly the same but is night 100% mods and AI friendly.
This time it's packed as an utility script you can call from any other script that suits you. It will automatically calculate the impact point, draw the beam and apply damage. And since it's no longer using any dummy missile it's completely transparent for the AI (but it won't see it coming either, beware with the range then) or other fancy scripted factions. The attached zip contains everything you need.

But if you want to take a look first:
Spoiler
The utility script that find the collision point, apply the damage, call the plugin, and create the impact glow:
Code: java
// By Tartiflette and DeathFly

package data.scripts.util;

import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.CombatAsteroidAPI;
import java.awt.Color;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.ShieldAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import data.scripts.plugins.FakeBeamPlugin;
import java.awt.geom.Line2D;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.CombatUtils;

public class FakeBeam {

    //
    //Fake beam generator.
    //
    //Create a visually convincing beam from arbitrary coordinates.
    //It however has several limitation:
    // - It deal damage instantly and is therefore only meant to be used for burst beams.
    // - It cannot be "cut" by another object passing between the two ends, a very short duration is thus preferable.
    // - Unlike vanilla, it deals full damage to armor, be carefull when using HIGH_EXPLOSIVE damage type.
    //
    // Most of the parameters are self explanatory but just in case:
    //
    //engine : Combat Engine
    //start : source point of the beam
    //range : maximum effective range (the beam will visually fade a few pixels farther)
    //aim : direction of the beam
    //width : width of the beam
    //fading : duration of the beam
    //normalDamage : nominal burst damage of the beam (don't forget to calculate the skill modifiers before that)
    //               will potentially be modified when fighting some modded factions like Exigency.
    //type : damage type of the beam
    //emp : nominal emp damage if any
    //source : ship dealing the damage
    //size : glow size on the impact point
    //duration : duration of the impact glow (should be at least as long as the beam fading)
    //color : color of the impact glow
    //
    //Note that there is no control over the beam's color, you'll have to directly modify the fakeBeamFX.png for that
    //   
   
   
    /////////////////////////////////////////
    //                                     //
    //             FAKE BEAM               //
    //                                     //
    /////////////////////////////////////////   
   
   
    public static void applyFakeBeamEffect (CombatEngineAPI engine, Vector2f start, float range, float aim, float width, float fading, float normalDamage, DamageType type, float emp, ShipAPI source, float size, float duration, Color color)
    {           
        CombatEntityAPI theTarget= null;
        float damage = normalDamage;

        //default end point
        Vector2f end = MathUtils.getPointOnCircumference(start,range,aim);

        //list all nearby entities that could be hit
        List <CombatEntityAPI> entity = CombatUtils.getEntitiesWithinRange(start, range+500);
        if (!entity.isEmpty()){
            for (CombatEntityAPI e : entity){

                //ignore un-hittable stuff like phased ships
                if (e.getCollisionClass() == CollisionClass.NONE){continue;}

                //damage can be reduced against some modded ships
                float newDamage = normalDamage;

                Vector2f col = new Vector2f(1000000,1000000);                 
                //ignore everything but ships...
                if (
                        e instanceof ShipAPI
                        &&
                        CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())
                        ){
                    //check for a shield impact, then hull and take the closest one                 
                    ShipAPI s = (ShipAPI) e;

                    //find the collision point with shields/hull
                    Vector2f hitPoint = getShipCollisionPoint(start, end, s);
                    if ( hitPoint != null ){
                        col = hitPoint;
                    }

                    //check for modded ships with damage reduction
                    if (s.getHullSpec().getBaseHullId().startsWith("exigency_")){
                        newDamage = normalDamage/2;
                    }
                                       
                } else
                    //...and asteroids!
                       if (
                               e instanceof CombatAsteroidAPI
                               &&
                               CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())
                               ){                               
                    Vector2f cAst = getCollisionPointOnCircumference(start,end,e.getLocation(),e.getCollisionRadius());
                    if ( cAst != null){
                        col = cAst;
                    }
                }

                //if there was an impact and it is closer than the curent beam end point, set it as the new end point and store the target to apply damage later damage
                if (col != new Vector2f(1000000,1000000) && MathUtils.getDistance(start, col) < MathUtils.getDistance(start, end)) {
                    end = col;
                    theTarget = e;
                    damage = newDamage;
                }               
            }
                           
            //if the beam impacted something, apply the damage
            if (theTarget!=null){
               
                //damage
                engine.applyDamage(
                        theTarget,
                        end,
                        damage,
                        type,
                        emp,
                        false,
                        true,
                        source
                );
                //impact flash
                engine.addHitParticle(
                        end,
                        theTarget.getVelocity(),
                        (float)Math.random()*size/2+size,
                        1,
                        (float)Math.random()*duration/2+duration,
                        color
                );
                engine.addHitParticle(
                        end,
                        theTarget.getVelocity(),
                        (float)Math.random()*size/4+size/2,
                        1,
                        0.1f,
                        Color.WHITE
                );
            }           
               
            //create the visual effect
            Map <String,Float> VALUES = new HashMap<>();
            VALUES.put("t", fading); //duration
            VALUES.put("w", width); //width
            VALUES.put("h", MathUtils.getDistance(start, end)+10); //length
            VALUES.put("x", start.x); //origin X
            VALUES.put("y", start.y); //origin Y
            VALUES.put("a", aim); //angle
           
            //Add the beam to the plugin
            FakeBeamPlugin.addMember(VALUES);
        }
    }
   
   
    /////////////////////////////////////////
    //                                     //
    //             SHIP HIT                //
    //                                     //
    /////////////////////////////////////////
   
   
    // return the collision point of segment lineStart to lineEnd and a ship (will consider shield).
    // if line can not hit the ship, will return null.
    // if lineStart hit the ship, will return lineStart.
    // if lineStart hit the shield, will return lineStart.
   
    public static Vector2f getShipCollisionPoint(Vector2f lineStart, Vector2f lineEnd, ShipAPI ship){
       
        // 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(lineStart, lineEnd, 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 = getCollisionPointOnCircumference(lineStart, lineEnd, 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.
                    boolean hit = false;
                    Vector2f tmp = new Vector2f(lineEnd);
                   
                    //the beam cannot go farther than it's max range or the hull
                    Vector2f hullHit = CollisionUtils.getCollisionPoint(lineStart, lineEnd, ship);                   
                    if (hullHit != null){
                        tmp = hullHit;
                        hit = true;
                    }
                   
                    //find if the shield is hit from the left or right side
                    if (MathUtils.getShortestRotation(
                            VectorUtils.getAngle(lineStart, lineEnd),
                            VectorUtils.getAngle(lineStart, circleCenter)
                            )
                            <=0){
                        //left side hit
                        Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc()/2));
                        Vector2f tmp2 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge1);
                        if(tmp2 != null){
                            tmp = tmp2;
                            hit = true;
                        }
                    } else {           
                        //right side hit
                        Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc()/2));
                        Vector2f tmp3 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge2);
                        if(tmp3 != null){
                            tmp = tmp3;
                            hit = true;
                        }
                    }
                    // return null if do not hit anything.
                    return hit ? tmp : null;
                }
            }
        }
        return null;
    }
   
    /////////////////////////////////////////
    //                                     //
    //       CIRCLE COLLISION POINT        //
    //                                     //
    /////////////////////////////////////////
   
    // return the first intersection point of segment lineStart to lineEnd and circumference.
    // if lineStart is outside the circle and segment can not intersection with the circumference, will return null.
    // if lineStart is inside the circle, will return lineStart.
   
    public static Vector2f getCollisionPointOnCircumference(Vector2f lineStart, Vector2f lineEnd, Vector2f circleCenter, float circleRadius){
       
        Vector2f startToEnd = Vector2f.sub(lineEnd, lineStart, null);
        Vector2f startToCenter = Vector2f.sub(circleCenter, lineStart, null);
        double ptSegDistSq = (float) Line2D.ptSegDistSq(lineStart.x, lineStart.y, lineEnd.x, lineEnd.y, circleCenter.x, circleCenter.y);
        float circleRadiusSq = circleRadius * circleRadius;
       
        // if lineStart is outside the circle and segment can not reach the circumference, return null
        if (ptSegDistSq > circleRadiusSq || (startToCenter.lengthSquared() >= circleRadiusSq && startToCenter.lengthSquared()>startToEnd.lengthSquared())) return null;
        // if lineStart is within the circle, return it directly
        if (startToCenter.lengthSquared() < circleRadiusSq) return lineStart;
       
        // calculate the intersection point.
        startToEnd.normalise(startToEnd);
        double dist = Vector2f.dot(startToCenter, startToEnd) -  Math.sqrt(circleRadiusSq - ptSegDistSq);
        startToEnd.scale((float) dist);
        return Vector2f.add(lineStart, startToEnd, null);
    }
   
    /////////////////////////////////////////
    //                                     //
    //             SHIELD HIT              //
    //                                     //
    /////////////////////////////////////////
   
    // SHOULD ONLY BE USED WHEN YOU ONLY NEED 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 lineStart to lineEnd and ship's shield.
    // if the line can not hit the shield or if the ship has no shield, return null.
    // if ignoreHull = flase and the line hit the ship's hull first, return null.
    // if lineStart is inside the shield, will return lineStart.
   
    public static Vector2f getShieldCollisionPoint(Vector2f lineStart, Vector2f lineEnd, ShipAPI ship, boolean ignoreHull){
        // 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 = getCollisionPointOnCircumference(lineStart,lineEnd, 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(lineEnd);
                boolean hit = false;
               
                //find if the shield is hit from the left or right side
                if (MathUtils.getShortestRotation(
                        VectorUtils.getAngle(lineStart, lineEnd),
                        VectorUtils.getAngle(lineStart, circleCenter)
                        )
                        >=0){
                    Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc()/2));
                    Vector2f tmp2 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge1);
                    if(tmp2 != null){
                        tmp = tmp2;
                        hit = true;
                    }
                } else {               
                    Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc()/2));
                    Vector2f tmp3 = CollisionUtils.getCollisionPoint(lineStart, 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(lineStart, tmp, ship) != null) return null;
                // return null if do not hit shield.
                return hit ? tmp : null;
            }
        }
        return null;
    }   
}
The everyframe plugin that draw the sprite and make it fade:
Code: java
//By Tartiflette with DeathFly's help
//draw arbitrary beam sprites wherever you need them and fade them out
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.ViewportAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class FakeBeamPlugin extends BaseEveryFrameCombatPlugin {
  
    //FAKEBEAMS is the core of the script, storing both the weapons that have a flash rendered and the index of the current sprite used    
    public static List<Map<String,Float>> FAKEBEAMS = new ArrayList();
    
    //set the function to access FAKEBEAMS from the weapons scripts    
    public static void addMember(Map<String,Float> data) {
        FAKEBEAMS.add(data);
    }
            
    private List<Map<String,Float>> toRemove = new ArrayList<>();
    
    private SpriteAPI beam = Global.getSettings().getSprite("beams", "SCY_fakeBeamFX");
    
    @Override
    public void init(CombatEngineAPI engine) {
        //reinitialize the map
        FAKEBEAMS.clear();
    }
    
    @Override
    public void renderInWorldCoords(ViewportAPI view)
    {
        CombatEngineAPI engine = Global.getCombatEngine();
        if (engine == null){return;}
        
        if (!FAKEBEAMS.isEmpty()){
            float amount = (engine.isPaused() ? 0f : engine.getElapsedInLastFrame());
            
            //dig through the FAKEBEAMS
            for (Map< String,Float > entry : FAKEBEAMS) {
                  
                //Time calculation
                float time = entry.get("t");
                time -= amount;
                
                if (time <= 0){        
                    //faded out, remove the beam and skip                    
                    toRemove.add(entry);
                } else {
                    //draw the beam otherwise                                        
                    float opacity = Math.max(0,(Math.min(1,(float) Math.sin(time * Math.PI))));
                    
                    render(
                            beam, //Sprite to draw
                            entry.get("w") * opacity, //Width entry srinking with the opacity
                            2*entry.get("h"), //Height entry, multiplied by two because centered
                            entry.get("a"), //Angle entry
                            opacity, //opacity duh!
                            entry.get("x"), //X position entry
                            entry.get("y") //Y position entry
                    );
                    
                    //and store the new time value
                    entry.put("t", time);
                }
            }
            //remove the beams that faded out
            //can't be done from within the iterator or it will fail when members will be missing
            if (!toRemove.isEmpty()){
                for(Map< String,Float > w : toRemove ){
                    FAKEBEAMS.remove(w);
                }
                toRemove.clear();
            }            
        }
    }
    
    private void render ( SpriteAPI sprite, float width, float height, float angle, float opacity, float posX, float posY){
        //where the magic happen
        sprite.setAlphaMult(opacity);
        sprite.setSize(width, height);
        sprite.setAdditiveBlend();
        sprite.setAngle(angle-90);
        sprite.renderAtCenter(posX, posY);    
    }
}
and the two lines to add to the settings to make the whole thing work:
Code
{
"plugins":{
"FakeBeamPlugin":"data.scripts.plugins.FakeBeamPlugin",
},
                
"graphics":{
"beams":{
"SCY_fakeBeamFX":"graphics/FAKEBEAM/fakeBeamFX.png",
},
}
}
[close]

To use it you just need to add the plugin and sprites to you settings.json, then a line like that:
Code: java
for (int x=0; x<nbRays; x++) { 
   FakeBeam.applyFakeBeamEffect(  
                            engine,
                            weapon.getLocation(), //start point
                            beamRange, //beam range
                            weapon.getFacing()+MathUtils.getRandomNumberInRange(-10, 10), //spreading
                            width, //beam width
                            duration, //beam duration
                            defaultDamage, //effective damage (will be modified against some specific mods)
                            dmgType, //damage type. Remember that beams are supposed to deal half damage against armor
                            defaultEmp, //effective EMP
                            missile.getSource(), //damage source
                            size, //impact size
                            duration, //impact duration
                            color //impact color
                        );
}
and BAM, you got a laser shotgun. Simple! 8)
[Edit1] (again) faster collision detection using less point calculations.

[attachment deleted by admin]
« Last Edit: June 17, 2015, 11:36:40 AM by Tartiflette »
Logged
 

Nicke535

  • Commander
  • ***
  • Posts: 240
  • Degenerate Core
    • View Profile
Re: The Radioactive Code Dump
« Reply #113 on: June 17, 2015, 11:57:40 PM »

*Awesome quote regarding laser shotguns*

Awesome! Thank you so much, i needed a piece of code just like that! (I will, of course, give full credit)

Deathfly

  • Modders' Vanguard
  • Commander
  • ***
  • Posts: 245
  • Murdered by T. H. Morgan
    • View Profile
Re: The Radioactive Code Dump
« Reply #114 on: July 24, 2015, 12:41:22 AM »

well, I think it is time to post the newer FakeBeamTM.

fakeBeam 3.4, for more customizability! (and bug fix.)

As before, this one is packed as a utility again. All needed things are packed in the attached zip and it is ready to use. (expact the settings.json was renamed to avoid accidental override.)

(to use this, you need LazyLib.)

The main change form fakeBeam 2.0 is fix a major bug that will cause some misjudge on shield hit calculate.(That was all my fault, please don't blame Tartiflette >.<) And expand the customizability of the beam render script so you can change the most parameter before or after a fakeBeam was rendered. (like the fakeBeam form and to, fakeBeam sprite and color. fade in, fade out, and duration time etc.)

And this package contains 3 part of scripts.
1st
\data\scripts\plugins\FakeBeamPlugin.java
This one is the fakeBeam render.
Spoiler
If you want to render a FakeBeamTM just call
renderFakeBeam(Vector2f form, Vector2f to, float width, float duration, float fadeInDuration, float fadeOutDuration, SpriteAPI beamSprite, Color beamColor)
and it will render a FakeBeam and return it.

After a FakeBeam was rendered, you can use FakeBeam.setForm(Vector2f form) and FakeBeam.setTo(Vector2f to) to move the beam dynamical, FakeBeam.setSprite(SpriteAPI beamSprite) to mod the sprite, FakeBeam.setColor(Color beamColor) to change beam's color, FakeBeam.setDuration(float duration) to reset the beams live time ect.

Code: java
//By Tartiflette and Deathfly
//draw arbitrary beam sprites wherever you need them and fade them out
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.ViewportAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import org.lazywizard.lazylib.FastTrig;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lwjgl.util.vector.Vector2f;

public class FakeBeamPlugin extends BaseEveryFrameCombatPlugin {

    //FAKEBEAMS is the core of the script, storing both the weapons that have a flash rendered and the index of the current sprite used    
    private static final List<FakeBeam> FAKEBEAMS = new ArrayList<>();
    private final List<FakeBeam> toRemove = new ArrayList<>();

    @Override
    public void init(CombatEngineAPI engine) {
        //reinitialize the map
        FAKEBEAMS.clear();
    }

    @Override
    public void renderInWorldCoords(ViewportAPI view) {
        CombatEngineAPI engine = Global.getCombatEngine();
        if (engine == null) {
            return;
        }

        if (!FAKEBEAMS.isEmpty()) {
            float amount = (engine.isPaused() ? 0f : engine.getElapsedInLastFrame());

            //dig through the FAKEBEAMS
            for (FakeBeam fakeBeam : FAKEBEAMS) {

                //Time calculation
                float fadeIn = fakeBeam.getFadeInTimer();
                float liveTime = fakeBeam.getDurationTimer();
                float fadeOut = fakeBeam.getFadeOutTimer();
                float opacity = 0;

                // check if the beam is fading in
                if (fadeIn > 0) {
                    fadeIn -= amount;
                    fakeBeam.setFadeInTimer(fadeIn);
                    opacity = (float) FastTrig.cos((fadeIn / fakeBeam.getFadeInDuration()) * (Math.PI / 2));
                    // check if the beam is on full power
                } else if (liveTime > 0) {
                    liveTime -= amount;
                    fakeBeam.setDurationTimer(liveTime);
                    opacity = 1;
                    // check if the beam is fading out
                } else if (fadeOut > 0) {
                    fadeOut -= amount;
                    fakeBeam.setFadeOutTimer(fadeOut);
                    opacity = (float) FastTrig.sin((fadeOut / fakeBeam.getFadeOutDuration()) * (Math.PI / 2));
                } else {
                    //completely faded out, remove the beam and skip              
                    toRemove.add(fakeBeam);
                    continue;
                }

                //draw the beam
                render(
                        fakeBeam.getSprite(), //Sprite to draw
                        fakeBeam.getColor(), //Sprite color
                        fakeBeam.getWidth() * opacity, //Width entry srinking with the opacity
                        fakeBeam.getLength(), //Height entry, multiplied by two because centered
                        fakeBeam.getAngleToRender(), //Angle entry
                        opacity, //opacity duh!
                        fakeBeam.getCenter()//Center
                );
            }

            //remove the beams that faded out
            //can't be done from within the iterator or it will fail when members will be missing
            if (!toRemove.isEmpty()) {
                for (FakeBeam w : toRemove) {
                    FAKEBEAMS.remove(w);
                }
                toRemove.clear();
            }
        }
    }

    private void render(SpriteAPI sprite, Color color, float width, float height, float angle, float opacity, Vector2f center) {
        //where the magic happen
        if (color != null) {
            sprite.setColor(color);
        }
        sprite.setAlphaMult(opacity);
        sprite.setSize(height, width);
        sprite.setAdditiveBlend();
        sprite.setAngle(angle);
        sprite.renderAtCenter(center.x, center.y);
    }
    
    // a class to record all we need to render a fake beam.
    public static class FakeBeam {

        protected SpriteAPI beamSprite = Global.getSettings().getSprite("graphics/fx/beamcore.png");
        protected Color beamColor = Color.white;
        protected float width = 0f;
        protected float length = 0f;
        protected float angleForRender = 0f;
        protected float duration = 0f;
        protected float durationTimer = 0f;
        protected float fadeInDuration = 0f;
        protected float fadeInTimer = 0f;
        protected float fadeOutDuration = 0f;
        protected float fadeOutTimer = 0f;
        protected Vector2f centerLoc = null;
        protected Vector2f form = null;
        protected Vector2f to = null;

        public void setSprite(SpriteAPI beamSprite) {
            this.beamSprite = beamSprite;
        }

        public SpriteAPI getSprite() {
            return beamSprite;
        }

        public void setColor(Color beamColor) {
            this.beamColor = beamColor;
        }

        public Color getColor() {
            return beamColor;
        }

        public void setWidth(float width) {
            this.width = width;
        }

        public float getWidth() {
            return width;
        }

        public void setLength(float length) {
            this.length = length;
            if (form != null) {
                setTo(MathUtils.getPointOnCircumference(form, length, angleForRender));
            }
        }

        public float getLength() {
            return length;
        }

        public void setCurrAngle(float currAngle) {
            if (form != null) {
                setTo(MathUtils.getPointOnCircumference(form, length, currAngle));
            }
        }

        public float getCurrAngle() {
            return angleForRender;
        }

        public float getAngleToRender() {
            return angleForRender;
        }

        public void setDuration(float duration) {
            this.duration = duration;
            this.durationTimer = duration;
        }

        public float getDuration() {
            return duration;
        }

        public void setDurationTimer(float durationTimer) {
            this.durationTimer = durationTimer;
        }

        public float getDurationTimer() {
            return durationTimer;
        }

        public void setFadeInDuration(float fadeInDuration) {
            this.fadeInDuration = fadeInDuration;
            this.fadeInTimer = fadeInDuration;
        }

        public float getFadeInDuration() {
            return fadeInDuration;
        }

        public void setFadeInTimer(float fadeInTimer) {
            this.fadeInTimer = fadeInTimer;
        }

        public float getFadeInTimer() {
            return fadeInTimer;
        }

        public void setFadeOutDuration(float fadeOutDuration) {
            this.fadeOutDuration = fadeOutDuration;
            this.fadeOutTimer = fadeOutDuration;
        }

        public float getFadeOutDuration() {
            return fadeOutDuration;
        }

        public void setFadeOutTimer(float fadeOutTimer) {
            this.fadeOutTimer = fadeOutTimer;
        }

        public float getFadeOutTimer() {
            return fadeOutTimer;
        }

        public void setForm(Vector2f form) {
            this.form = form;
            if (this.form != null && this.to != null) {
                this.centerLoc = MathUtils.getMidpoint(this.form, this.to);
                this.length = MathUtils.getDistance(form, to);
                this.angleForRender = VectorUtils.getAngle(form, to);
            }
        }

        public Vector2f getForm() {
            return form;
        }

        public void setTo(Vector2f to) {
            this.to = to;
            if (this.to != null && this.form != null) {
                this.centerLoc = MathUtils.getMidpoint(this.form, this.to);
                this.length = MathUtils.getDistance(form, to);
                this.angleForRender = VectorUtils.getAngle(form, to);
            }
        }

        public Vector2f getTo() {
            return to;
        }

        public void setCenter(Vector2f center) {
            this.centerLoc = center;
        }

        public Vector2f getCenter() {
            return centerLoc;
        }
    }

    // 4 methods for render fake beam, will return FakeBeam for some after rendered modify.
    public static FakeBeam renderFakeBeam(Vector2f form, Vector2f to, float width, float duration, float fadeInDuration, float fadeOutDuration, SpriteAPI beamSprite, Color beamColor) {
        FakeBeam fakeBeam = new FakeBeam();
        if (beamSprite != null) {
            fakeBeam.setSprite(beamSprite);
        }
        fakeBeam.setColor(beamColor);
        fakeBeam.setForm(form);
        fakeBeam.setTo(to);
        fakeBeam.setWidth(width);
        fakeBeam.setDuration(duration);
        fakeBeam.setFadeInDuration(fadeInDuration);
        fakeBeam.setFadeOutDuration(fadeOutDuration);
        FAKEBEAMS.add(fakeBeam);
        return fakeBeam;
    }

    public static FakeBeam renderFakeBeam(Vector2f form, Vector2f to, float width, float duration, SpriteAPI beamSprite, Color beamColor) {
        return renderFakeBeam(form, to, width, duration - 0.5f, 0.25f, 0.25f, beamSprite, beamColor);
    }

    public static FakeBeam renderFakeBeam(Vector2f form, float length, float aim, float width, float duration, float fadeInDuration, float fadeOutDuration, SpriteAPI beamSprite, Color beamColor) {
        Vector2f to = MathUtils.getPointOnCircumference(form, length, aim);
        return renderFakeBeam(form, to, width, duration, fadeInDuration, fadeOutDuration, beamSprite, beamColor);
    }

    public static FakeBeam renderFakeBeam(Vector2f form, float length, float aim, float width, float duration, SpriteAPI beamSprite, Color beamColor) {
        Vector2f to = MathUtils.getPointOnCircumference(form, length, aim);
        return renderFakeBeam(form, to, width, duration, beamSprite, beamColor);
    }
}
[close]
2nd
\data\scripts\\utils\CollisionUtilsEX.java
This one expand the CollisionUtils in LazyLib for detection collision between a segment and a ship/shield/circular.
Spoiler
This one is quite self-describing. Just use them if you need.
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 {

    /////////////////////////////////////////
    //                                     //
    //             SHIP HIT                //
    //                                     //
    /////////////////////////////////////////
    // 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.
    // if segStart hit the ship, will return segStart.
    // if segStart hit the shield, will return segStart.
    public static Vector2f getShipCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship) {

        // 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 = getCollisionPointOnCircumference(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.
                    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;
                    }
                    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 do not hit anything.
                    return hit ? tmp : null;
                }
            }
        }
        return null;
    }

    /////////////////////////////////////////
    //                                     //
    //       CIRCLE COLLISION POINT        //
    //                                     //
    /////////////////////////////////////////
    // 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.
    // if segStart is inside the circle, will return segStart.
    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;
        
        // 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);
    }

    /////////////////////////////////////////
    //                                     //
    //             SHIELD HIT              //
    //                                     //
    /////////////////////////////////////////
    // SHOULD ONLY BE USED WHEN YOU ONLY NEED 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 if the ship has no shield, return null.
    // if ignoreHull = flase and the segment hit the ship's hull first, return null.
    // if segStart is inside the shield, will return segStart.
    public static Vector2f getShieldCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship, boolean ignoreHull) {
        // 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 = getCollisionPointOnCircumference(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;

                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;
    }
}

[close]
3rd
\data\scripts\utils\FakeBeam.java
A demo for how to use FakeBeamTM
Spoiler
This code was moded form SCY. By use it you can replicate the laser head torpedo's FakeBeam effect. (But the VFX seens a bit discrepancy...never mind.)
It serve as a demo to show how to fake a burst beam. Including how to find out the hit point of beam, and how to render a FakeBeamTM.
Code: java
// By Tartiflette and Deathfly
package data.scripts.util;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.CombatAsteroidAPI;
import java.awt.Color;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import data.scripts.plugins.FakeBeamPlugin;
import java.util.List;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;

public class FakeBeam {

    //
    //Fake beam generator.
    //
    //Create a visually convincing beam from arbitrary coordinates.
    //It however has several limitation:
    // - It deal damage instantly and is therefore only meant to be used for burst beams.
    // - It cannot be "cut" by another object passing between the two ends, a very short duration is thus preferable.
    // - Unlike vanilla, it deals full damage to armor, be carefull when using HIGH_EXPLOSIVE damage type.
    //
    // Most of the parameters are self explanatory but just in case:
    //
    //engine : Combat Engine
    //start : source point of the beam
    //range : maximum effective range (the beam will visually fade a few pixels farther)
    //aim : direction of the beam
    //width : width of the beam
    //fading : duration of the beam
    //normalDamage : nominal burst damage of the beam (don't forget to calculate the skill modifiers before that)
    //               will potentially be modified when fighting some modded factions like Exigency.
    //type : damage type of the beam
    //emp : nominal emp damage if any
    //source : ship dealing the damage
    //size : glow size on the impact point
    //duration : duration of the impact glow (should be at least as long as the beam fading)
    //color : color of the impact glow
    //
    //Note that there is no control over the beam's color, you'll have to directly modify the fakeBeamFX.png for that
    //    
    /////////////////////////////////////////
    //                                     //
    //             FAKE BEAM               //
    //                                     //
    /////////////////////////////////////////    
    public static void applyFakeBeamEffect(CombatEngineAPI engine, Vector2f start, float range, float aim, float width, float fading, float normalDamage, DamageType type, float emp, ShipAPI source, float size, float duration, Color color) {
        CombatEntityAPI theTarget = null;
        float damage = normalDamage;
        // beam sprite to darw.
        SpriteAPI beamSprite = Global.getSettings().getSprite("beams", "SCY_fakeBeamFX");

        //default end point
        Vector2f end = MathUtils.getPointOnCircumference(start, range, aim);
      

        //list all nearby entities that could be hit
        List<CombatEntityAPI> entity = CombatUtils.getEntitiesWithinRange(start, range + 500);
        if (!entity.isEmpty()) {
            for (CombatEntityAPI e : entity) {

                //ignore un-hittable stuff like phased ships
                if (e.getCollisionClass() == CollisionClass.NONE) {
                    continue;
                }
                
                

                //damage can be reduced against some modded ships
                float newDamage = normalDamage;

                Vector2f col = new Vector2f(1000000, 1000000);
                //ignore everything but ships...
                if (e instanceof ShipAPI
                        && CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())) {
                    //check for a shield impact, then hull and take the closest one                  
                    ShipAPI s = (ShipAPI) e;

                    //find the collision point with shields/hull
                    Vector2f hitPoint = CollisionUtilsEX.getShipCollisionPoint(start, end, s);
                    if (hitPoint != null) {
                        col = hitPoint;
                    }

                    //check for modded ships with damage reduction (not in use)
//                    if (s.getHullSpec().getBaseHullId().startsWith("exigency_")) {
//                        newDamage = normalDamage / 2;
//                    }
                    
                    //check for beam damage reduction
                    newDamage = normalDamage * s.getMutableStats().getBeamDamageTakenMult().getModifiedValue();

                } else //...and asteroids!
                if (e instanceof CombatAsteroidAPI
                        && CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())) {
                    Vector2f cAst = CollisionUtilsEX.getCollisionPointOnCircumference(start, end, e.getLocation(), e.getCollisionRadius());
                    if (cAst != null) {
                        col = cAst;
                    }
                }

                //if there was an impact and it is closer than the curent beam end point, set it as the new end point and store the target to apply damage later damage
                if (col != new Vector2f(1000000, 1000000) && MathUtils.getDistance(start, col) < MathUtils.getDistance(start, end)) {
                    end = col;
                    theTarget = e;
                    damage = newDamage;
                }
            }

            //if the beam impacted something, apply the damage
            if (theTarget != null) {

                //damage
                engine.applyDamage(
                        theTarget,
                        end,
                        damage,
                        type,
                        emp,
                        false,
                        true,
                        source
                );
                //impact flash
                engine.addHitParticle(
                        end,
                        theTarget.getVelocity(),
                        (float) Math.random() * size / 2 + size,
                        1,
                        (float) Math.random() * duration / 2 + duration,
                        color
                );
                engine.addHitParticle(
                        end,
                        theTarget.getVelocity(),
                        (float) Math.random() * size / 4 + size / 2,
                        1,
                        0.1f,
                        Color.WHITE
                );
            }
            //Add the beam to the plugin
            FakeBeamPlugin.renderFakeBeam(start, MathUtils.getDistance(start, end) + 10, aim, width, duration, beamSprite, null);
        }
    }
}
[close]


And as before, the two lines needs to be added to the settings.json to make the whole thing work:
Code
{
"plugins":{
"FakeBeamPlugin":"data.scripts.plugins.FakeBeamPlugin",
},
                
"graphics":{
"beams":{
"SCY_fakeBeamFX":"graphics/FAKEBEAM/fakeBeamFX.png",
},
}
}
(Or you can add your own beam sprite instead of SCY_fakeBeamFX.)

And that's all. Hope this one will help.
Have fun!

EDIT: It is my bad to forget this at the frist time. But later better then never.
A special credit to 1930s who give me this idea and the old fakeBeam render source code. And I do hope your Knights will emerge here in sometime, for peace.


[attachment deleted by admin]
« Last Edit: July 27, 2015, 06:51:42 AM 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.

TJJ

  • Admiral
  • *****
  • Posts: 1905
    • View Profile
Re: The Radioactive Code Dump
« Reply #115 on: July 24, 2015, 03:45:43 AM »

If( col != new Vector2f(1000000, 1000000)........

That expression is redundant, it will always be true.
Logged

Tartiflette

  • Admiral
  • *****
  • Posts: 3529
  • MagicLab discord: https://discord.gg/EVQZaD3naU
    • View Profile
Re: The Radioactive Code Dump
« Reply #116 on: July 24, 2015, 04:10:54 AM »

If( col != new Vector2f(1000000, 1000000)........

That expression is redundant, it will always be true.
It's there to check if the temporary "col" point has been moved to a collision point since it's creation, witch is the purpose of the few lines above. If it's still at (1000000,1000000) that means the beam didn't collide with anything, but it's not always the case.
Logged
 

TJJ

  • Admiral
  • *****
  • Posts: 1905
    • View Profile
Re: The Radioactive Code Dump
« Reply #117 on: July 24, 2015, 05:22:43 AM »

If( col != new Vector2f(1000000, 1000000)........

That expression is redundant, it will always be true.
It's there to check if the temporary "col" point has been moved to a collision point since it's creation, witch is the purpose of the few lines above. If it's still at (1000000,1000000) that means the beam didn't collide with anything, but it's not always the case.

You're creating a new object, then checking its reference for inequality against something else.
A newly created reference will never be equal to something (anything!) else.
Thus "col != new Vector2f(1000000, 1000000)" is a tautology, it will always evaluate to "true". (RuntimeExceptions/Errors/concurrency not withstanding.)
« Last Edit: July 24, 2015, 05:24:51 AM by TJJ »
Logged

Deathfly

  • Modders' Vanguard
  • Commander
  • ***
  • Posts: 245
  • Murdered by T. H. Morgan
    • View Profile
Re: The Radioactive Code Dump
« Reply #118 on: July 24, 2015, 05:45:09 AM »

If( col != new Vector2f(1000000, 1000000)........

That expression is redundant, it will always be true.
It's there to check if the temporary "col" point has been moved to a collision point since it's creation, witch is the purpose of the few lines above. If it's still at (1000000,1000000) that means the beam didn't collide with anything, but it's not always the case.

You're creating a new object, then checking its reference for inequality against something else.
A newly created reference will never be equal to something (anything!) else.
Thus "col != new Vector2f(1000000, 1000000)" is a tautology, it will always evaluate to "true". (RuntimeExceptions/Errors/concurrency not withstanding.)

you just remind me a old odd bug we used to have. ^_^
We used to check "col != new Vector2f(0, 0)" until we found if the target's loc is Vector2f(0, 0), the beam will always miss it. >.<
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.

kazi

  • Admiral
  • *****
  • Posts: 714
    • View Profile
Re: The Radioactive Code Dump
« Reply #119 on: August 10, 2015, 02:08:10 AM »

Here's a vastly improved version of the FakeBeam code. I pretty much completely rewrote it- posting it here cause I got the original source/idea from here (and it seemed like the nice thing to do). It still performs all of the same function, with none of the drawbacks. Targets/beam hit points are recalculated 8 times a second, and damage is applied continuously. You can essentially use this for any type of beam now, including ones with long firing durations.

We've gone from 3 classes worth of code to 2: a spec class that performs all of the hit and damage calculations, and a rendering class that tracks and manages instances of the spec class fed to it.

The renderer:
Spoiler
Code: Java
package data.scripts.plugins.beamRenderer;

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.ViewportAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

import com.fs.starfarer.api.input.InputEventAPI;
import org.lazywizard.lazylib.MathUtils;
import org.lwjgl.util.vector.Vector2f;

/** Draw arbitrary beam sprites wherever you need them and fade them out. Has none of the drawbacks of the old code (static beams, one-time damage).
 *  @author Tartiflette and Deathfly (complete and TOTAL rewrite by kazi)
 */
public class BeamRendererPlugin extends BaseEveryFrameCombatPlugin {

    private CombatEngineAPI engine;

    private static ArrayList<BeamSpec> beamsToRender = new ArrayList<>();
    private ArrayList<BeamSpec> toRemove = new ArrayList<>();

    // add beams to the rendering/damage thread this way (by creating a NEW beamSpec object using the constructor)
    public static void addBeam(BeamSpec newBeam) {
        beamsToRender.add(newBeam);
        newBeam.calcImpactPoint();

        // only draw flashes once
        newBeam.engine.addHitParticle(newBeam.startLoc, new Vector2f(),
                (float) Math.random() * newBeam.size / 2 + newBeam.size,
                1,
                (float) Math.random() * newBeam.duration / 2 + newBeam.duration,
                newBeam.beamColor);
        if (newBeam.target != null) {
            newBeam.engine.addHitParticle(newBeam.hitLoc, newBeam.target.getVelocity(),
                    (float) Math.random() * newBeam.size / 4 + newBeam.size / 2,
                    1, 0.1f, Color.WHITE);
            newBeam.engine.addHitParticle(newBeam.hitLoc, newBeam.target.getVelocity(),
                    (float) Math.random() * newBeam.size / 2 + newBeam.size,
                    1,
                    (float) Math.random() * newBeam.duration / 2 + newBeam.duration,
                    newBeam.beamColor);
        }
    }

    @Override
    public void init(CombatEngineAPI combatEngineAPI) {
        engine = Global.getCombatEngine();
        //reinitialize the map
        beamsToRender.clear();
    }

    @Override
    public void advance(float amount, List<InputEventAPI> events) {
        if (engine == null || engine.isPaused()) {
            return;
        }

        // recalculate render coords and apply damage
        for (BeamSpec beam : beamsToRender) {
            beam.update(amount);
        }
    }

    @Override
    public void renderInWorldCoords(ViewportAPI view) {
        if (engine == null) {
            return;
        }

        if (!beamsToRender.isEmpty()) {
            // iterate through the beams, rendering each in turn
            for (BeamSpec beam : beamsToRender) {
                if (beam.isDone) {
                    toRemove.add(beam);
                    continue;
                }

                //draw the beam
                SpriteAPI sprite = beam.sprite;
                sprite.setAlphaMult(beam.opacity);
                sprite.setSize(MathUtils.getDistance(beam.startLoc, beam.hitLoc) + 25f, beam.width);
                sprite.setAngle(beam.aim);
                Vector2f center = MathUtils.getMidpoint(beam.startLoc, beam.hitLoc);
                sprite.renderAtCenter(center.x, center.y);
            }

            //remove the beams that are done
            if (!toRemove.isEmpty()) {
                beamsToRender.removeAll(toRemove);
                toRemove.clear();
            }
        }
    }
}
[close]

The spec class:
Spoiler
Code: Java
package data.scripts.plugins.beamRenderer;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.graphics.SpriteAPI;
import data.scripts.util.ilk_CollisionUtilsEX;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.FastTrig;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;

import java.awt.*;
import java.util.List;

/**
 * Created by Jeff on 2015-08-09.
 */
public class BeamSpec {

    // initialization variables
    CombatEngineAPI engine;
    ShipAPI source;
    float damage;
    DamageType type;
    float empDamage;
    float duration;
    float fadeOut;
    float fadeIn;
    float aim;
    float range;
    float width;
    SpriteAPI sprite;
    float size;
    Color beamColor;

    // dynamic variables calculated by update method
    private float interval;
    private float delta;
    private static final float RECALC_INTERVAL = 0.125f;

    //renderer variables
    Vector2f startLoc;
    Vector2f hitLoc;
    boolean isDone;
    float intensity;
    float opacity;
    CombatEntityAPI target;

    public BeamSpec(CombatEngineAPI combatEngineAPI, ShipAPI setSource, Vector2f startLocSet, float rangeSet, float aimSet, float damageAmt,
                    DamageType damageType, float empDamageAmt, float time, float fadeInSet, float fadeOutSet,
                    String spriteKey, String spriteName, float wide, Color colorSet) {
        engine = combatEngineAPI;
        source = setSource;
        startLoc = startLocSet;
        damage = damageAmt;
        type = damageType;
        empDamage = empDamageAmt;
        duration = time;
        fadeOut = fadeOutSet;
        fadeIn = fadeInSet;
        range = rangeSet;
        aim = aimSet;
        width = wide;
        size = 2 * width;
        sprite = Global.getSettings().getSprite(spriteKey, spriteName);
        sprite.setAdditiveBlend();
        beamColor = colorSet;

        interval = 0f;
        intensity = 0f;
        opacity = 0f;
        hitLoc = new Vector2f();
        isDone = false;
    }

    /** Recalculate damage and hit location based on updated time since last frame
     *
     * @param amount delta time
     */
    public void update(float amount) {

        delta += amount;

        // where are we at in the current beam firing cycle
        if (delta <= fadeIn) {
            intensity = delta / fadeIn;
            opacity = (float) (FastTrig.sin(intensity * Math.PI / 2));
            //second condition for elseif not necessary for next as values lower than fadeIn have already been caught
        } else if (delta <= duration + fadeIn) {
            intensity = 1f;
            opacity = 1f;
        } else if (delta <= fadeIn + duration + fadeOut) {
            intensity = 1f - ((delta - fadeIn - duration) / fadeOut);
            opacity = (float) (FastTrig.sin(intensity * Math.PI / 2));
        } else {
            intensity = 0f;
            opacity = 0f;
            isDone = true;
        }

        // only recalc hitpoint after a certain update interval to avoid wasting cpu for no reason
        interval += amount;
        if (interval > RECALC_INTERVAL) {
            interval = 0f;
            calcImpactPoint();
        }

        // recalc damage if we've hit something
        if (target != null) {
            float currDamage = damage * amount * intensity;
            float currEmp = empDamage * amount * intensity;

            if (target instanceof ShipAPI) {
                ShipAPI ship = (ShipAPI) target;
                //check for modded ships with damage reduction
                //if (ship.getHullSpec().getBaseHullId().startsWith("exigency_") ) currDamage /= 2;

                //check for beam damage reduction
                currDamage *= ship.getMutableStats().getBeamDamageTakenMult().getModifiedValue();

                //check for emp damage reduction
                currEmp *= ship.getMutableStats().getEmpDamageTakenMult().getModifiedValue();
            }
            // DEAL DE DAMAGE!
            engine.applyDamage(target, hitLoc, currDamage, type, currEmp, false, true, source);
        }
    }


    // did we hit something?
    void calcImpactPoint() {
        //default end point
        Vector2f end = MathUtils.getPointOnCircumference(startLoc, range, aim);
        CombatEntityAPI theTarget = null;

        //list all nearby entities that could be hit
        for (CombatEntityAPI entity : CombatUtils.getEntitiesWithinRange(startLoc, range + 500f)) {

            // ignore un-hittable stuff like phased ships
            if (entity.getCollisionClass() == CollisionClass.NONE) {
                continue;
            }

            // ignore friendlies
            if (entity.getOwner() == source.getOwner()) continue;

            // check for collision
            if (CollisionUtils.getCollides(startLoc, end, entity.getLocation(), entity.getCollisionRadius())) {
                Vector2f collide = null;

                // ship collision?
                if (entity instanceof ShipAPI) {
                    //find the collision point with shields/hull
                    Vector2f hitPoint = ilk_CollisionUtilsEX.getShipCollisionPoint(startLoc, end, (ShipAPI) entity);
                    if (hitPoint != null) collide = hitPoint;

                    // asteroid collision?
                } else if (entity instanceof CombatAsteroidAPI) {
                    Vector2f hitPoint = ilk_CollisionUtilsEX.getCollisionPointOnCircumference(startLoc, end, entity.getLocation(), entity.getCollisionRadius());
                    if (hitPoint != null) collide = hitPoint;
                }

                //if impact is closer than the curent beam end point, set it as the new end point and save target
                if ((collide != null) && (MathUtils.getDistance(startLoc, collide) < MathUtils.getDistance(startLoc, end))) {
                    end = collide;
                    theTarget = entity;
                }
            }
        }

        //okay update variables
        target = theTarget;
        hitLoc = end;
    }

    public void setColor(Color color) {
        sprite.setColor(color);
        beamColor = color;
    }

    public void setWidth(float wide) {
        width = wide;
    }

    public void setStartLoc(Vector2f startLoc) {
        this.startLoc = startLoc;
    }
}
[close]

To use, just invoke the renderer's add() method with a new instance of the beam spec, like so:
Code
BeamRendererPlugin.addBeam(new BeamSpec(engine, proj.getSource(), fireLoc, 700f, proj.getFacing(),
                                1000f, DamageType.ENERGY, 0f,
                                0.7f, 0.1f, 0.2f, //duration, in, out
                                "beams", "ilk_fakeBeamFX", 27, new Color(224,184,225,175)));

Note: the only requirement for running this is having both the renderer and the spec class in the same package, as they talk to each other using protected field access (got lazy there). You'll still need the CollisionUtilsEX that Deathfly posted, and the plugin also needs to be registered in settings.json.

EDIT: It just occurred to me that this still doesn't take into account armor values, but it should be super easy to add in (given that you have a hit location for every frame).
« Last Edit: August 10, 2015, 02:13:07 AM by kazi »
Logged
Pages: 1 ... 6 7 [8] 9 10