001package com.fs.starfarer.api.impl.combat;
002
003import java.awt.Color;
004import java.util.List;
005
006import org.lwjgl.util.vector.Vector2f;
007
008import com.fs.starfarer.api.Global;
009import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
010import com.fs.starfarer.api.combat.CombatEngineAPI;
011import com.fs.starfarer.api.combat.CombatEntityAPI;
012import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
013import com.fs.starfarer.api.combat.MissileAPI;
014import com.fs.starfarer.api.combat.MutableShipStatsAPI;
015import com.fs.starfarer.api.combat.ShipAPI;
016import com.fs.starfarer.api.combat.ShipSystemAPI;
017import com.fs.starfarer.api.combat.ShipSystemAPI.SystemState;
018import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags;
019import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
020import com.fs.starfarer.api.input.InputEventAPI;
021import com.fs.starfarer.api.util.Misc;
022import com.fs.starfarer.api.util.WeightedRandomPicker;
023
024public class MineStrikeStats extends BaseShipSystemScript implements MineStrikeStatsAIInfoProvider {
025        
026        protected static float MINE_RANGE = 1000f;
027        
028        public static final float MIN_SPAWN_DIST = 75f;
029        public static final float MIN_SPAWN_DIST_FRIGATE = 110f;
030        
031        public static final float LIVE_TIME = 5f;
032        
033        public static final Color JITTER_COLOR = new Color(255,155,255,75);
034        public static final Color JITTER_UNDER_COLOR = new Color(255,155,255,155);
035
036        
037        public static float getRange(ShipAPI ship) {
038                if (ship == null) return MINE_RANGE;
039                return ship.getMutableStats().getSystemRangeBonus().computeEffective(MINE_RANGE);
040        }
041        
042        public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
043                ShipAPI ship = null;
044                //boolean player = false;
045                if (stats.getEntity() instanceof ShipAPI) {
046                        ship = (ShipAPI) stats.getEntity();
047                } else {
048                        return;
049                }
050                
051                
052                float jitterLevel = effectLevel;
053                if (state == State.OUT) {
054                        jitterLevel *= jitterLevel;
055                }
056                float maxRangeBonus = 25f;
057                float jitterRangeBonus = jitterLevel * maxRangeBonus;
058                if (state == State.OUT) {
059                }
060                
061                ship.setJitterUnder(this, JITTER_UNDER_COLOR, jitterLevel, 11, 0f, 3f + jitterRangeBonus);
062                ship.setJitter(this, JITTER_COLOR, jitterLevel, 4, 0f, 0 + jitterRangeBonus);
063                
064                if (state == State.IN) {
065                } else if (effectLevel >= 1) {
066                        Vector2f target = ship.getMouseTarget();
067                        if (ship.getShipAI() != null && ship.getAIFlags().hasFlag(AIFlags.SYSTEM_TARGET_COORDS)){
068                                target = (Vector2f) ship.getAIFlags().getCustom(AIFlags.SYSTEM_TARGET_COORDS);
069                        }
070                        if (target != null) {
071                                float dist = Misc.getDistance(ship.getLocation(), target);
072                                float max = getMaxRange(ship) + ship.getCollisionRadius();
073                                if (dist > max) {
074                                        float dir = Misc.getAngleInDegrees(ship.getLocation(), target);
075                                        target = Misc.getUnitVectorAtDegreeAngle(dir);
076                                        target.scale(max);
077                                        Vector2f.add(target, ship.getLocation(), target);
078                                }
079                                
080                                target = findClearLocation(ship, target);
081                                
082                                if (target != null) {
083                                        spawnMine(ship, target);
084                                }
085                        }
086                        
087                } else if (state == State.OUT ) {
088                }
089        }
090        
091        
092        public void unapply(MutableShipStatsAPI stats, String id) {
093        }
094        
095        public void spawnMine(ShipAPI source, Vector2f mineLoc) {
096                CombatEngineAPI engine = Global.getCombatEngine();
097                Vector2f currLoc = Misc.getPointAtRadius(mineLoc, 30f + (float) Math.random() * 30f);
098                //Vector2f currLoc = null;
099                float start = (float) Math.random() * 360f;
100                for (float angle = start; angle < start + 390; angle += 30f) {
101                        if (angle != start) {
102                                Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle);
103                                loc.scale(50f + (float) Math.random() * 30f);
104                                currLoc = Vector2f.add(mineLoc, loc, new Vector2f());
105                        }
106                        for (MissileAPI other : Global.getCombatEngine().getMissiles()) {
107                                if (!other.isMine()) continue;
108                                
109                                float dist = Misc.getDistance(currLoc, other.getLocation());
110                                if (dist < other.getCollisionRadius() + 40f) {
111                                        currLoc = null;
112                                        break;
113                                }
114                        }
115                        if (currLoc != null) {
116                                break;
117                        }
118                }
119                if (currLoc == null) {
120                        currLoc = Misc.getPointAtRadius(mineLoc, 30f + (float) Math.random() * 30f);
121                }
122                
123                
124                
125                //Vector2f currLoc = mineLoc;
126                MissileAPI mine = (MissileAPI) engine.spawnProjectile(source, null, 
127                                                                                                                          "minelayer2", 
128                                                                                                                          currLoc, 
129                                                                                                                          (float) Math.random() * 360f, null);
130                if (source != null) {
131                        Global.getCombatEngine().applyDamageModifiersToSpawnedProjectileWithNullWeapon(
132                                                                                        source, WeaponType.MISSILE, false, mine.getDamage());
133//                      float extraDamageMult = source.getMutableStats().getMissileWeaponDamageMult().getModifiedValue();
134//                      mine.getDamage().setMultiplier(mine.getDamage().getMultiplier() * extraDamageMult);
135                }
136                
137                
138                float fadeInTime = 0.5f;
139                mine.getVelocity().scale(0);
140                mine.fadeOutThenIn(fadeInTime);
141                
142                Global.getCombatEngine().addPlugin(createMissileJitterPlugin(mine, fadeInTime));
143                
144                //mine.setFlightTime((float) Math.random());
145                float liveTime = LIVE_TIME;
146                //liveTime = 0.01f;
147                mine.setFlightTime(mine.getMaxFlightTime() - liveTime);
148                
149                Global.getSoundPlayer().playSound("mine_teleport", 1f, 1f, mine.getLocation(), mine.getVelocity());
150        }
151        
152        protected EveryFrameCombatPlugin createMissileJitterPlugin(final MissileAPI mine, final float fadeInTime) {
153                return new BaseEveryFrameCombatPlugin() {
154                        float elapsed = 0f;
155                        @Override
156                        public void advance(float amount, List<InputEventAPI> events) {
157                                if (Global.getCombatEngine().isPaused()) return;
158                        
159                                elapsed += amount;
160                                
161                                float jitterLevel = mine.getCurrentBaseAlpha();
162                                if (jitterLevel < 0.5f) {
163                                        jitterLevel *= 2f;
164                                } else {
165                                        jitterLevel = (1f - jitterLevel) * 2f;
166                                }
167                                
168                                float jitterRange = 1f - mine.getCurrentBaseAlpha();
169                                //jitterRange = (float) Math.sqrt(jitterRange);
170                                float maxRangeBonus = 50f;
171                                float jitterRangeBonus = jitterRange * maxRangeBonus;
172                                Color c = JITTER_UNDER_COLOR;
173                                c = Misc.setAlpha(c, 70);
174                                //mine.setJitter(this, c, jitterLevel, 15, jitterRangeBonus * 0.1f, jitterRangeBonus);
175                                mine.setJitter(this, c, jitterLevel, 15, jitterRangeBonus * 0, jitterRangeBonus);
176                                
177                                if (jitterLevel >= 1 || elapsed > fadeInTime) {
178                                        Global.getCombatEngine().removePlugin(this);
179                                }
180                        }
181                };
182        }
183        
184        
185        protected float getMaxRange(ShipAPI ship) {
186                return getMineRange(ship);
187        }
188
189        
190        @Override
191        public String getInfoText(ShipSystemAPI system, ShipAPI ship) {
192                if (system.isOutOfAmmo()) return null;
193                if (system.getState() != SystemState.IDLE) return null;
194                
195                Vector2f target = ship.getMouseTarget();
196                if (target != null) {
197                        float dist = Misc.getDistance(ship.getLocation(), target);
198                        float max = getMaxRange(ship) + ship.getCollisionRadius();
199                        if (dist > max) {
200                                return "OUT OF RANGE";
201                        } else {
202                                return "READY";
203                        }
204                }
205                return null;
206        }
207
208        
209        @Override
210        public boolean isUsable(ShipSystemAPI system, ShipAPI ship) {
211                return ship.getMouseTarget() != null;
212        }
213        
214        
215        private Vector2f findClearLocation(ShipAPI ship, Vector2f dest) {
216                if (isLocationClear(dest)) return dest;
217                
218                float incr = 50f;
219
220                WeightedRandomPicker<Vector2f> tested = new WeightedRandomPicker<Vector2f>();
221                for (float distIndex = 1; distIndex <= 32f; distIndex *= 2f) {
222                        float start = (float) Math.random() * 360f;
223                        for (float angle = start; angle < start + 360; angle += 60f) {
224                                Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle);
225                                loc.scale(incr * distIndex);
226                                Vector2f.add(dest, loc, loc);
227                                tested.add(loc);
228                                if (isLocationClear(loc)) {
229                                        return loc;
230                                }
231                        }
232                }
233                
234                if (tested.isEmpty()) return dest; // shouldn't happen
235                
236                return tested.pick();
237        }
238        
239        private boolean isLocationClear(Vector2f loc) {
240                for (ShipAPI other : Global.getCombatEngine().getShips()) {
241                        if (other.isShuttlePod()) continue;
242                        if (other.isFighter()) continue;
243                        
244//                      Vector2f otherLoc = other.getLocation();
245//                      float otherR = other.getCollisionRadius();
246                        
247//                      if (other.isPiece()) {
248//                              System.out.println("ewfewfewfwe");
249//                      }
250                        Vector2f otherLoc = other.getShieldCenterEvenIfNoShield();
251                        float otherR = other.getShieldRadiusEvenIfNoShield();
252                        if (other.isPiece()) {
253                                otherLoc = other.getLocation();
254                                otherR = other.getCollisionRadius();
255                        }
256                        
257                        
258//                      float dist = Misc.getDistance(loc, other.getLocation());
259//                      float r = other.getCollisionRadius();
260                        float dist = Misc.getDistance(loc, otherLoc);
261                        float r = otherR;
262                        //r = Math.min(r, Misc.getTargetingRadius(loc, other, false) + r * 0.25f);
263                        float checkDist = MIN_SPAWN_DIST;
264                        if (other.isFrigate()) checkDist = MIN_SPAWN_DIST_FRIGATE;
265                        if (dist < r + checkDist) {
266                                return false;
267                        }
268                }
269                for (CombatEntityAPI other : Global.getCombatEngine().getAsteroids()) {
270                        float dist = Misc.getDistance(loc, other.getLocation());
271                        if (dist < other.getCollisionRadius() + MIN_SPAWN_DIST) {
272                                return false;
273                        }
274                }
275                
276                return true;
277        }
278
279
280        public float getFuseTime() {
281                return 3f;
282        }
283
284
285        public float getMineRange(ShipAPI ship) {
286                return getRange(ship);
287                //return MINE_RANGE;
288        }
289
290        
291}
292
293
294
295
296
297
298
299