001package com.fs.starfarer.api.impl.campaign.abilities;
002
003import org.lwjgl.util.vector.Vector2f;
004
005import com.fs.starfarer.api.Global;
006import com.fs.starfarer.api.ui.TooltipMakerAPI;
007import com.fs.starfarer.api.util.Misc;
008
009/**
010 * Ability that's turned on and then plays out until it's finished.
011 * 
012 * (Why use methods to pass in sound ids etc instead of passing them in to a constructor?
013 * Mainly so they don't need to go into the save file as they would if they were
014 * stored in member variables.)
015 * 
016 * @author Alex Mosolov
017 *
018 * Copyright 2015 Fractal Softworks, LLC
019 */
020public abstract class BaseDurationAbility extends BaseAbilityPlugin {
021
022        public static final float LOOP_FADE_TIME_DAYS = 0.1f;
023        
024        public float getLoopSoundUIVolume() {
025                if (loopFadeLeft > 0) {
026                        return loopFadeLeft / LOOP_FADE_TIME_DAYS;
027                }
028                if (level > 0) {
029                        if (fadingOut) {
030                                // keep loop volume at 1 when ability is winding down, fade it out after
031                                return 1;
032                        } else {
033                                // ramp loop volume up as ability winds up
034                                return level;
035                        }
036                }
037                return 0;
038        }
039        public float getLoopSoundUIPitch() { return 1f; }
040        
041        public float getLoopSoundWorldVolume() { return getLoopSoundUIVolume(); }
042        public float getLoopSoundWorldPitch() { return 1f; }
043        
044
045        
046        public float getCooldownDays() { return spec.getDeactivationCooldown(); }
047        public float getDurationDays() { return spec.getDurationDays(); }
048        public float getTotalDurationDays() { return spec.getDurationDays() + spec.getActivationDays() + spec.getDeactivationDays(); }
049        public float getActivationDays() { return spec.getActivationDays(); }
050        public float getDeactivationDays() { return spec.getDeactivationDays(); }
051        
052        
053        protected abstract void activateImpl();
054        
055        /**
056         * Will be called once when level is 0 and consistently when level >0.
057         * @param level
058         */
059        protected abstract void applyEffect(float amount, float level);
060        protected abstract void deactivateImpl();
061        protected abstract void cleanupImpl();
062        
063
064        protected boolean turnedOn = false;
065        protected float activeDaysLeft = 0;
066        protected float cooldownLeft = 0;
067        protected float level = 0;
068        
069        protected float loopFadeLeft = 0;
070        protected boolean fadingOut = false;
071        
072        @Override
073        public void advance(float amount) {
074                super.advance(amount);
075                
076                if (entity.isInCurrentLocation() && entity.isVisibleToPlayerFleet() &&
077                                getLoopSoundWorldVolume() > 0 && !Global.getSector().isPaused()) {
078                        String soundId = getLoopSoundWorld();
079                        if (soundId != null) {
080                                Global.getSector().getCampaignUI().suppressMusic(spec.getMusicSuppression() * getLoopSoundWorldVolume());
081                                Global.getSoundPlayer().playLoop(soundId, entity, 
082                                                        getLoopSoundWorldPitch(), getLoopSoundWorldVolume(),
083                                                        entity.getLocation(), entity.getVelocity());
084                        }
085                }
086                
087                if (entity.isPlayerFleet() && getLoopSoundUIVolume() > 0 && !Global.getSector().isPaused()) {
088                        String soundId = getLoopSoundUI();
089                        if (soundId != null) {
090                                Global.getSector().getCampaignUI().suppressMusic(spec.getMusicSuppression() * getLoopSoundUIVolume());
091                                Global.getSoundPlayer().playLoop(soundId, entity, 
092                                                        getLoopSoundUIPitch(), getLoopSoundUIVolume(),
093                                                        entity.getLocation(), entity.getVelocity());
094                        }
095                }
096                
097                if (activeDaysLeft > 0) {
098                        float days = Global.getSector().getClock().convertToDays(amount);
099                        activeDaysLeft -= days;
100                        
101                        if (activeDaysLeft <= 0) {
102                                level = 1f;
103                                applyEffect(amount, level);
104                                activeDaysLeft = 0;
105                                
106                                deactivate();
107                        }
108                } else if (cooldownLeft > 0) {
109                        float days = Global.getSector().getClock().convertToDays(amount);
110                        cooldownLeft -= days;
111                        if (cooldownLeft < 0) cooldownLeft = 0;
112                }
113                
114                if (loopFadeLeft > 0) {
115                        float days = Global.getSector().getClock().convertToDays(amount);
116                        loopFadeLeft -= days;
117                        if (loopFadeLeft < 0) {
118                                loopFadeLeft = 0;
119                        }
120                }
121                
122                
123                float prevLevel = level;
124                if (activeDaysLeft > 0) {
125                        float a = getActivationDays();
126                        float d = getDeactivationDays();
127                        float t = getTotalDurationDays();
128                        if (activeDaysLeft > t - a) {
129                                if (a <= 0) {
130                                        level = 1;
131                                } else {
132                                        level = 1f - (activeDaysLeft - (t - a)) / a;
133                                }
134                        } else if (activeDaysLeft < d) {
135                                fadingOut = true;
136                                if (d <= 0) {
137                                        level = 0;
138                                } else {
139                                        level = activeDaysLeft / d;
140                                }
141                        } else {
142                                level = 1;
143                        }
144                } else {
145                        level = 0;
146                }
147                
148                if (prevLevel != level || level > 0) {
149                        applyEffect(amount, level);
150                        disableIncompatible();
151                }
152        }
153
154        
155        protected void addIncompatibleToTooltip(TooltipMakerAPI tooltip, boolean expanded) {
156                addIncompatibleToTooltip(tooltip, "Disables the following abilities while active:",
157                                                                                  "Expand tooltip to view conflicting abilities",
158                                                                                  expanded);
159        }
160        
161        @Override
162        public float getCooldownFraction() {
163                if (cooldownLeft <= 0) return 1f;
164                return 1f - cooldownLeft / getCooldownDays();
165        }
166        
167        @Override
168        public float getProgressFraction() {
169                if (getTotalDurationDays() <= 0 || !turnedOn) return 0f;
170                return 1f - (activeDaysLeft / getTotalDurationDays());
171        }
172        
173        @Override
174        public boolean showProgressIndicator() {
175                return turnedOn;
176        }
177        
178        @Override
179        public boolean showActiveIndicator() {
180                return false;
181        }
182        
183        @Override
184        public boolean isUsable() {
185                return !isOnCooldown() && !isInProgress() && !turnedOn && disableFrames <= 0;
186        }
187        public void pressButton() {
188                if (isUsable() && !turnedOn) {
189                        activate();
190                        if (entity.isPlayerFleet()) {
191                                String soundId = getOnSoundUI();
192                                if (soundId != null) {
193                                        if (PLAY_UI_SOUNDS_IN_WORLD_SOURCES) {
194                                                Global.getSoundPlayer().playSound(soundId, 1f, 1f, Global.getSoundPlayer().getListenerPos(), new Vector2f());
195                                        } else {
196                                                Global.getSoundPlayer().playUISound(soundId, 1f, 1f);
197                                        }
198                                }
199                        }
200                }
201        }
202        
203        public void activate() {
204                if (isUsable() && !turnedOn) {
205                        turnedOn = true;
206                        loopFadeLeft = 0f;
207                        fadingOut = false;
208                        activeDaysLeft = getTotalDurationDays();
209                        
210                        if (entity.isInCurrentLocation() && entity.isVisibleToPlayerFleet() && !entity.isPlayerFleet()) {
211                                String soundId = getOnSoundWorld();
212                                if (soundId != null) {
213                                        Global.getSoundPlayer().playSound(soundId, 1f, 1f, entity.getLocation(), entity.getVelocity());
214                                }
215                        }
216                        if (getActivationDays() <= 0) {
217                                level = 1;
218                        }
219                        
220                        if (entity.isInCurrentLocation()) {
221                                if (getActivationText() != null && entity.isVisibleToPlayerFleet()) {
222                                        entity.addFloatingText(getActivationText(), Misc.setAlpha(entity.getIndicatorColor(), 255), 0.5f);
223                                }
224                        }
225                        
226                        activateImpl();
227                        applyEffect(0f, level);
228                        interruptIncompatible();
229                        disableIncompatible();
230                        
231                        if (getTotalDurationDays() <= 0) {
232                                deactivate();
233                        }
234                        
235                        super.activate();
236                }
237        }
238        
239        public void deactivate() {
240                if (turnedOn) {
241                        turnedOn = false;
242                        activeDaysLeft = 0f;
243                        loopFadeLeft = LOOP_FADE_TIME_DAYS;
244                        if (entity.isInCurrentLocation() && entity.isVisibleToPlayerFleet() && !entity.isPlayerFleet()) {
245                                String soundId = getOffSoundWorld();
246                                if (soundId != null) {
247                                        Global.getSoundPlayer().playSound(soundId, 1f, 1f, entity.getLocation(), entity.getVelocity());
248                                }
249                        }
250                        if (entity.isPlayerFleet()) {
251                                String soundId = getOffSoundUI();
252                                if (soundId != null) {
253                                        if (PLAY_UI_SOUNDS_IN_WORLD_SOURCES) {
254                                                Global.getSoundPlayer().playSound(soundId, 1f, 1f, Global.getSoundPlayer().getListenerPos(), new Vector2f());
255                                        } else {
256                                                Global.getSoundPlayer().playUISound(soundId, 1f, 1f);
257                                        }
258                                }
259                        }
260                        cooldownLeft = getCooldownDays();
261                        
262                        level = 0;
263                        applyEffect(0f, level);
264                        
265                        
266                        if (entity.isInCurrentLocation()) {
267                                if (getDeactivationText() != null && entity.isVisibleToPlayerFleet()) {
268                                        entity.addFloatingText(getDeactivationText(), Misc.setAlpha(entity.getIndicatorColor(), 255), 0.5f);
269                                }
270                        }
271                        deactivateImpl();
272                        
273                        super.deactivate();
274                }
275        }
276        
277        
278        @Override
279        public void cleanup() {
280                super.cleanup();
281                
282                applyEffect(0f, 0f);
283                
284                cleanupImpl();
285        }
286        
287        public boolean isActive() {
288                return false;
289        }
290        
291        @Override
292        public boolean isInProgress() {
293                return super.isInProgress();
294        }
295        
296        
297        public boolean hasCustomButtonPressSounds() {
298                return getOnSoundUI() != null;
299        }
300        
301        @Override
302        public boolean runWhilePaused() {
303                return false;
304        }
305        public float getActiveDaysLeft() {
306                return activeDaysLeft;
307        }
308        public void setActiveDaysLeft(float activeDaysLeft) {
309                this.activeDaysLeft = activeDaysLeft;
310        }
311        public float getCooldownLeft() {
312                return cooldownLeft;
313        }
314        public void setCooldownLeft(float cooldownLeft) {
315                this.cooldownLeft = cooldownLeft;
316        }
317        public boolean isFadingOut() {
318                return fadingOut;
319        }
320        public float getLevel() {
321                return level;
322        }
323        
324}
325
326
327
328
329