Done: Audio, Graphics. Tbd: Input, cleanup Config, cleanup old BaseApp etcmaster
parent
eee9ed14dc
commit
3c0763a4c8
@ -0,0 +1,38 @@ |
||||
package mightypork.gamecore.backend.lwjgl; |
||||
|
||||
|
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.initializers.InitTask; |
||||
import mightypork.gamecore.util.SlickLogRedirector; |
||||
import mightypork.utils.logging.writers.LogWriter; |
||||
|
||||
|
||||
/** |
||||
* Initializer that redirects slick logging to main logger. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class InitTaskRedirectSlickLog extends InitTask { |
||||
|
||||
@Override |
||||
public void run(App app) |
||||
{ |
||||
LogWriter ml = mightypork.utils.logging.Log.getMainLogger(); |
||||
SlickLogRedirector slr = new SlickLogRedirector(ml); |
||||
org.newdawn.slick.util.Log.setLogSystem(slr); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String getName() |
||||
{ |
||||
return "slick_log"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String[] getDependencies() |
||||
{ |
||||
return new String[] { "log" }; |
||||
} |
||||
} |
@ -0,0 +1,171 @@ |
||||
package mightypork.gamecore.backend.lwjgl; |
||||
|
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
|
||||
import mightypork.gamecore.resources.audio.DeferredAudio; |
||||
import mightypork.gamecore.resources.audio.SoundSystem; |
||||
import mightypork.utils.files.FileUtils; |
||||
import mightypork.utils.math.constraints.vect.Vect; |
||||
|
||||
import org.lwjgl.openal.AL10; |
||||
import org.newdawn.slick.openal.Audio; |
||||
import org.newdawn.slick.openal.SoundStore; |
||||
|
||||
|
||||
/** |
||||
* SlickUtil-based deferred audio resource. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class SlickAudio extends DeferredAudio { |
||||
|
||||
private double pauseLoopPosition = 0; |
||||
private boolean looping = false; |
||||
private boolean paused = false; |
||||
private double lastPlayPitch = 1; |
||||
private double lastPlayGain = 1; |
||||
|
||||
/** Audio resource */ |
||||
private Audio backingAudio = null; |
||||
private int sourceID; |
||||
|
||||
|
||||
public SlickAudio(String resourceName) { |
||||
super(resourceName); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void loadResource(String resource) throws IOException |
||||
{ |
||||
final String ext = FileUtils.getExtension(resource); |
||||
|
||||
try (final InputStream stream = FileUtils.getResource(resource)) { |
||||
|
||||
if (ext.equalsIgnoreCase("ogg")) { |
||||
backingAudio = SoundStore.get().getOgg(resource, stream); |
||||
|
||||
} else if (ext.equalsIgnoreCase("wav")) { |
||||
backingAudio = SoundStore.get().getWAV(resource, stream); |
||||
|
||||
} else if (ext.equalsIgnoreCase("aif")) { |
||||
backingAudio = SoundStore.get().getAIF(resource, stream); |
||||
|
||||
} else if (ext.equalsIgnoreCase("mod")) { |
||||
backingAudio = SoundStore.get().getMOD(resource, stream); |
||||
|
||||
} else { |
||||
throw new RuntimeException("Invalid audio file extension."); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void pauseLoop() |
||||
{ |
||||
if (!ensureLoaded()) return; |
||||
|
||||
if (isPlaying() && looping) { |
||||
pauseLoopPosition = backingAudio.getPosition(); |
||||
stop(); |
||||
paused = true; |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void resumeLoop() |
||||
{ |
||||
if (!ensureLoaded()) return; |
||||
|
||||
if (looping && paused) { |
||||
sourceID = backingAudio.playAsSoundEffect((float) lastPlayPitch, (float) lastPlayGain, true); |
||||
backingAudio.setPosition((float) pauseLoopPosition); |
||||
paused = false; |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void adjustGain(double gain) |
||||
{ |
||||
AL10.alSourcef(sourceID, AL10.AL_GAIN, (float) gain); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void stop() |
||||
{ |
||||
if (!isLoaded()) return; |
||||
|
||||
backingAudio.stop(); |
||||
paused = false; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean isPlaying() |
||||
{ |
||||
if (!isLoaded()) return false; |
||||
|
||||
return backingAudio.isPlaying(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean isPaused() |
||||
{ |
||||
if (!isLoaded()) return false; |
||||
|
||||
return backingAudio.isPaused(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void play(double pitch, double gain, boolean loop) |
||||
{ |
||||
play(pitch, gain, loop, SoundSystem.getListener()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void play(double pitch, double gain, boolean loop, double x, double y) |
||||
{ |
||||
play(pitch, gain, loop, x, y, SoundSystem.getListener().z()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void play(double pitch, double gain, boolean loop, double x, double y, double z) |
||||
{ |
||||
if (!ensureLoaded()) return; |
||||
|
||||
this.lastPlayPitch = pitch; |
||||
this.lastPlayGain = gain; |
||||
looping = loop; |
||||
|
||||
sourceID = backingAudio.playAsSoundEffect((float) pitch, (float) gain, loop, (float) x, (float) y, (float) z); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void play(double pitch, double gain, boolean loop, Vect pos) |
||||
{ |
||||
if (!ensureLoaded()) return; |
||||
|
||||
play(pitch, gain, loop, pos.x(), pos.y(), pos.z()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void destroy() |
||||
{ |
||||
if (!isLoaded() || backingAudio == null) return; |
||||
|
||||
backingAudio.release(); |
||||
backingAudio = null; |
||||
} |
||||
} |
@ -0,0 +1,91 @@ |
||||
package mightypork.gamecore.backend.lwjgl; |
||||
|
||||
|
||||
import java.nio.FloatBuffer; |
||||
|
||||
import org.lwjgl.openal.AL; |
||||
import org.lwjgl.openal.AL10; |
||||
import org.newdawn.slick.openal.SoundStore; |
||||
|
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.resources.audio.AudioModule; |
||||
import mightypork.gamecore.resources.audio.AudioReadyEvent; |
||||
import mightypork.gamecore.resources.audio.DeferredAudio; |
||||
import mightypork.gamecore.util.BufferHelper; |
||||
import mightypork.utils.logging.Log; |
||||
import mightypork.utils.math.constraints.vect.Vect; |
||||
import mightypork.utils.math.constraints.vect.var.VectVar; |
||||
|
||||
|
||||
/** |
||||
* SlickUtil-based audio module |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class SlickAudioModule extends AudioModule { |
||||
|
||||
private static final Vect INITIAL_LISTENER_POS = Vect.ZERO; |
||||
private static final int MAX_SOURCES = 256; |
||||
|
||||
private VectVar listener = Vect.makeVar(); |
||||
private static boolean soundSystemInited = false; |
||||
|
||||
|
||||
public SlickAudioModule() { |
||||
|
||||
if (!soundSystemInited) { |
||||
soundSystemInited = true; |
||||
|
||||
try { |
||||
SoundStore.get().setMaxSources(MAX_SOURCES); |
||||
SoundStore.get().init(); |
||||
setListenerPos(INITIAL_LISTENER_POS); |
||||
|
||||
App.bus().send(new AudioReadyEvent()); |
||||
} catch (final Throwable t) { |
||||
Log.e("Error initializing sound system.", t); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setListenerPos(Vect pos) |
||||
{ |
||||
listener.setTo(pos); |
||||
final FloatBuffer buf3 = BufferHelper.alloc(3); |
||||
final FloatBuffer buf6 = BufferHelper.alloc(6); |
||||
buf3.clear(); |
||||
BufferHelper.fill(buf3, (float) pos.x(), (float) pos.y(), (float) pos.z()); |
||||
AL10.alListener(AL10.AL_POSITION, buf3); |
||||
buf3.clear(); |
||||
BufferHelper.fill(buf3, 0, 0, 0); |
||||
AL10.alListener(AL10.AL_VELOCITY, buf3); |
||||
buf6.clear(); |
||||
BufferHelper.fill(buf6, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f); |
||||
AL10.alListener(AL10.AL_ORIENTATION, buf6); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Vect getListenerPos() |
||||
{ |
||||
return listener; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void deinitSoundSystem() |
||||
{ |
||||
SoundStore.get().clear(); |
||||
AL.destroy(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected DeferredAudio doCreateResource(String res) |
||||
{ |
||||
return new SlickAudio(res); |
||||
} |
||||
|
||||
} |
@ -1,13 +0,0 @@ |
||||
package mightypork.gamecore.core.config; |
||||
|
||||
|
||||
import mightypork.utils.files.config.PropertyManager; |
||||
|
||||
|
||||
/** |
||||
* Config setup, class used to populate the config file. |
||||
*/ |
||||
public interface ConfigSetup { |
||||
|
||||
void addOptions(PropertyManager prop); |
||||
} |
@ -1,30 +0,0 @@ |
||||
package mightypork.gamecore.core.config; |
||||
|
||||
|
||||
import mightypork.gamecore.input.KeyStroke; |
||||
|
||||
|
||||
/** |
||||
* Key options - restricted access to {@link Config} for keys |
||||
*/ |
||||
public class KeyOpts { |
||||
|
||||
public void add(String cfgKey, String dataString) |
||||
{ |
||||
add(cfgKey, dataString, null); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @param cfgKey key in config file |
||||
* @param dataString string representing the keystroke (format for |
||||
* {@link KeyStroke}) |
||||
* @param comment optional comment |
||||
*/ |
||||
public void add(String cfgKey, String dataString, String comment) |
||||
{ |
||||
final KeyProperty kprop = new KeyProperty(Config.prefixKey(cfgKey), KeyStroke.createFromDataString(dataString), comment); |
||||
Config.strokes.put(Config.prefixKey(cfgKey), kprop); |
||||
Config.cfg.putProperty(kprop); |
||||
} |
||||
} |
@ -1,10 +0,0 @@ |
||||
package mightypork.gamecore.core.config; |
||||
|
||||
|
||||
/** |
||||
* Key configurator. Config access restricted to key options. |
||||
*/ |
||||
public interface KeySetup { |
||||
|
||||
public void addKeys(KeyOpts keys); |
||||
} |
@ -1,34 +0,0 @@ |
||||
package mightypork.gamecore.core.modules; |
||||
|
||||
|
||||
import mightypork.gamecore.input.InputSystem; |
||||
import mightypork.gamecore.resources.audio.SoundSystem; |
||||
import mightypork.utils.eventbus.BusAccess; |
||||
|
||||
|
||||
/** |
||||
* App interface visible to subsystems |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public interface AppAccess extends BusAccess { |
||||
|
||||
/** |
||||
* @return sound system |
||||
*/ |
||||
abstract SoundSystem getSoundSystem(); |
||||
|
||||
|
||||
/** |
||||
* @return input system |
||||
*/ |
||||
abstract InputSystem getInput(); |
||||
|
||||
|
||||
/** |
||||
* Quit to OS<br> |
||||
* Destroy app & exit VM |
||||
*/ |
||||
abstract void shutdown(); |
||||
|
||||
} |
@ -1,56 +0,0 @@ |
||||
package mightypork.gamecore.core.modules; |
||||
|
||||
|
||||
import mightypork.gamecore.input.InputSystem; |
||||
import mightypork.gamecore.resources.audio.SoundSystem; |
||||
import mightypork.utils.eventbus.EventBus; |
||||
|
||||
|
||||
/** |
||||
* App access adapter (defualt {@link AppAccess} implementation) |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class AppAccessAdapter implements AppAccess { |
||||
|
||||
private final AppAccess app; |
||||
|
||||
|
||||
/** |
||||
* @param app app access |
||||
*/ |
||||
public AppAccessAdapter(AppAccess app) { |
||||
if (app == null) throw new NullPointerException("AppAccess instance cannot be null."); |
||||
|
||||
this.app = app; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final SoundSystem getSoundSystem() |
||||
{ |
||||
return app.getSoundSystem(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final InputSystem getInput() |
||||
{ |
||||
return app.getInput(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final EventBus getEventBus() |
||||
{ |
||||
return app.getEventBus(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final void shutdown() |
||||
{ |
||||
app.shutdown(); |
||||
} |
||||
|
||||
} |
@ -1,52 +0,0 @@ |
||||
package mightypork.gamecore.core.modules; |
||||
|
||||
|
||||
import mightypork.gamecore.input.InputSystem; |
||||
import mightypork.gamecore.resources.audio.SoundSystem; |
||||
import mightypork.utils.eventbus.clients.RootBusNode; |
||||
|
||||
|
||||
/** |
||||
* App event bus client, to be used for subsystems, screens and anything that |
||||
* needs access to the eventbus and other systems; Attached directly to bus. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public abstract class AppModule extends RootBusNode implements AppAccess { |
||||
|
||||
private final AppAccess app; |
||||
|
||||
|
||||
/** |
||||
* Create a module |
||||
* |
||||
* @param app access to app systems |
||||
*/ |
||||
public AppModule(AppAccess app) { |
||||
super(app); |
||||
|
||||
this.app = app; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final SoundSystem getSoundSystem() |
||||
{ |
||||
return app.getSoundSystem(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final InputSystem getInput() |
||||
{ |
||||
return app.getInput(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final void shutdown() |
||||
{ |
||||
app.shutdown(); |
||||
} |
||||
|
||||
} |
@ -1,54 +0,0 @@ |
||||
package mightypork.gamecore.core.modules; |
||||
|
||||
|
||||
import mightypork.gamecore.input.InputSystem; |
||||
import mightypork.gamecore.resources.audio.SoundSystem; |
||||
import mightypork.utils.eventbus.clients.BusNode; |
||||
import mightypork.utils.eventbus.clients.DelegatingClient; |
||||
import mightypork.utils.eventbus.clients.RootBusNode; |
||||
|
||||
|
||||
/** |
||||
* Delegating bus client, to be attached to any {@link DelegatingClient}, such |
||||
* as a {@link RootBusNode}. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class AppSubModule extends BusNode implements AppAccess { |
||||
|
||||
private final AppAccess app; |
||||
|
||||
|
||||
/** |
||||
* Create submodule |
||||
* |
||||
* @param app access to app systems |
||||
*/ |
||||
public AppSubModule(AppAccess app) { |
||||
super(app); |
||||
|
||||
this.app = app; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final SoundSystem getSoundSystem() |
||||
{ |
||||
return app.getSoundSystem(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final InputSystem getInput() |
||||
{ |
||||
return app.getInput(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public final void shutdown() |
||||
{ |
||||
app.shutdown(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,47 @@ |
||||
package mightypork.gamecore.initializers; |
||||
|
||||
|
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.utils.annotations.Stub; |
||||
|
||||
|
||||
/** |
||||
* App initializer. A sequence of initializers is executed once the start() |
||||
* method on App is called. Adding initializers is one way to customize the App |
||||
* behavior and features. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public abstract class InitTask { |
||||
|
||||
/** |
||||
* Run the initalizer on app. |
||||
* |
||||
* @param app the app instance. |
||||
*/ |
||||
public abstract void run(App app); |
||||
|
||||
|
||||
/** |
||||
* Get name of this initializer (for dependency resolver).<br> |
||||
* The name should be short, snake_case and precise. |
||||
* |
||||
* @return name |
||||
*/ |
||||
public abstract String getName(); |
||||
|
||||
|
||||
/** |
||||
* Get what other initializers must be already loaded before this can load.<br> |
||||
* Depending on itself or creating a circular dependency will cause error.<br> |
||||
* If the dependencies cannot be satisfied, the initialization sequence will |
||||
* be aborted. |
||||
* |
||||
* @return array of names of required initializers. |
||||
*/ |
||||
@Stub |
||||
public String[] getDependencies() |
||||
{ |
||||
return new String[] {}; |
||||
} |
||||
} |
@ -0,0 +1,79 @@ |
||||
package mightypork.gamecore.initializers; |
||||
|
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashSet; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
import mightypork.utils.logging.Log; |
||||
|
||||
|
||||
public class InitTaskResolver { |
||||
|
||||
/** |
||||
* Order init tasks so that all dependencies are loaded before thye are |
||||
* needed by the tasks. |
||||
* |
||||
* @param tasks task list |
||||
* @return task list ordered |
||||
*/ |
||||
public static List<InitTask> order(List<InitTask> tasks) |
||||
{ |
||||
List<InitTask> remaining = new ArrayList<>(tasks); |
||||
|
||||
List<InitTask> ordered = new ArrayList<>(); |
||||
Set<String> loaded = new HashSet<>(); |
||||
|
||||
// resolve task order
|
||||
int addedThisIteration = 0; |
||||
do { |
||||
for (Iterator<InitTask> i = remaining.iterator(); i.hasNext();) { |
||||
InitTask task = i.next(); |
||||
|
||||
String[] deps = task.getDependencies(); |
||||
if (deps == null) deps = new String[] {}; |
||||
|
||||
int unmetDepsCount = deps.length; |
||||
|
||||
for (String d : deps) { |
||||
if (loaded.contains(d)) unmetDepsCount--; |
||||
} |
||||
|
||||
if (unmetDepsCount == 0) { |
||||
ordered.add(task); |
||||
loaded.add(task.getName()); |
||||
i.remove(); |
||||
addedThisIteration++; |
||||
} |
||||
} |
||||
} while (addedThisIteration > 0); |
||||
|
||||
// check if any tasks are left out
|
||||
if (remaining.size() > 0) { |
||||
|
||||
// build error message for each bad task
|
||||
for (InitTask task : remaining) { |
||||
String notSatisfied = ""; |
||||
|
||||
for (String d : task.getDependencies()) { |
||||
if (!loaded.contains(d)) { |
||||
|
||||
if (!notSatisfied.isEmpty()) { |
||||
notSatisfied += ", "; |
||||
} |
||||
|
||||
notSatisfied += d; |
||||
} |
||||
} |
||||
|
||||
Log.w("InitTask \"" + task.getName() + "\" - missing dependencies: " + notSatisfied); |
||||
} |
||||
|
||||
throw new RuntimeException("Some InitTask dependencies could not be satisfied."); |
||||
} |
||||
|
||||
return ordered; |
||||
} |
||||
} |
@ -0,0 +1,74 @@ |
||||
package mightypork.gamecore.initializers.tasks; |
||||
|
||||
|
||||
import java.io.IOException; |
||||
|
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.initializers.InitTask; |
||||
import mightypork.utils.ion.Ion; |
||||
import mightypork.utils.ion.IonInput; |
||||
import mightypork.utils.ion.IonOutput; |
||||
import mightypork.utils.ion.IonizerBinary; |
||||
import mightypork.utils.math.algo.Coord; |
||||
import mightypork.utils.math.algo.Move; |
||||
|
||||
|
||||
/** |
||||
* Register extra ionizables added by the game library (non-native ION types).<br> |
||||
* This initializer can be called anywhere in the initialization sequence. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class InitTaskRegisterIonizables extends InitTask { |
||||
|
||||
@Override |
||||
public void run(App app) |
||||
{ |
||||
Ion.registerIndirect(255, new IonizerBinary<Coord>() { |
||||
|
||||
@Override |
||||
public void save(Coord object, IonOutput out) throws IOException |
||||
{ |
||||
out.writeInt(object.x); |
||||
out.writeInt(object.y); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Coord load(IonInput in) throws IOException |
||||
{ |
||||
final int x = in.readInt(); |
||||
final int y = in.readInt(); |
||||
return new Coord(x, y); |
||||
} |
||||
|
||||
}); |
||||
|
||||
Ion.registerIndirect(254, new IonizerBinary<Move>() { |
||||
|
||||
@Override |
||||
public void save(Move object, IonOutput out) throws IOException |
||||
{ |
||||
out.writeInt(object.x()); |
||||
out.writeInt(object.y()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Move load(IonInput in) throws IOException |
||||
{ |
||||
final int x = in.readInt(); |
||||
final int y = in.readInt(); |
||||
return new Move(x, y); |
||||
} |
||||
|
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public String getName() |
||||
{ |
||||
return "ion"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,42 @@ |
||||
package mightypork.gamecore.initializers.tasks; |
||||
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler; |
||||
|
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.initializers.InitTask; |
||||
import mightypork.utils.annotations.Stub; |
||||
import mightypork.utils.logging.Log; |
||||
|
||||
|
||||
/** |
||||
* Add a crash handler to the app.<br> |
||||
* For customized crash message / crash dialog etc, override the |
||||
* uncaughtException() method. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class InitTaskSetupCrashHandler extends InitTask implements UncaughtExceptionHandler { |
||||
|
||||
@Override |
||||
public void run(App app) |
||||
{ |
||||
Thread.setDefaultUncaughtExceptionHandler(this); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
@Stub |
||||
public void uncaughtException(Thread thread, Throwable throwable) |
||||
{ |
||||
Log.e("The game has crashed.", throwable); |
||||
App.shutdown(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String getName() |
||||
{ |
||||
return "crash_handler"; |
||||
} |
||||
} |
@ -0,0 +1,100 @@ |
||||
package mightypork.gamecore.initializers.tasks; |
||||
|
||||
|
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.initializers.InitTask; |
||||
import mightypork.gamecore.render.GraphicsModule; |
||||
|
||||
|
||||
/** |
||||
* Setup main window. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class InitTaskSetupDisplay extends InitTask { |
||||
|
||||
private int width = 800, height = 600, fps = 60; |
||||
private boolean resizable, fullscreen; |
||||
private String title = "Game"; |
||||
|
||||
|
||||
/** |
||||
* Set initial window size |
||||
* |
||||
* @param width width (px) |
||||
* @param height height (px) |
||||
*/ |
||||
public void setSize(int width, int height) |
||||
{ |
||||
this.width = width; |
||||
this.height = height; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set whether the window should be resizable |
||||
* |
||||
* @param resizable true for resizable |
||||
*/ |
||||
public void setResizable(boolean resizable) |
||||
{ |
||||
this.resizable = resizable; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set window title |
||||
* |
||||
* @param title title text |
||||
*/ |
||||
public void setTitle(String title) |
||||
{ |
||||
this.title = title; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set desired framerate. |
||||
* |
||||
* @param fps FPS |
||||
*/ |
||||
public void setTargetFps(int fps) |
||||
{ |
||||
this.fps = fps; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set whether the window should start in fullscreen |
||||
* |
||||
* @param fullscreen true for fullscreen |
||||
*/ |
||||
public void setFullscreen(boolean fullscreen) |
||||
{ |
||||
this.fullscreen = fullscreen; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void run(App app) |
||||
{ |
||||
GraphicsModule gfx = app.getBackend().getGraphics(); |
||||
|
||||
gfx.setSize(width, height); |
||||
gfx.setResizable(resizable); |
||||
gfx.setTitle(title); |
||||
gfx.setTargetFps(fps); |
||||
|
||||
if (fullscreen) gfx.setFullscreen(true); |
||||
|
||||
gfx.createDisplay(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String getName() |
||||
{ |
||||
return "display"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,105 @@ |
||||
package mightypork.gamecore.initializers.tasks; |
||||
|
||||
|
||||
import java.io.File; |
||||
import java.util.logging.Level; |
||||
|
||||
import mightypork.gamecore.core.WorkDir; |
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.initializers.InitTask; |
||||
import mightypork.utils.logging.Log; |
||||
import mightypork.utils.logging.writers.LogWriter; |
||||
|
||||
|
||||
/** |
||||
* Init main logger and console log printing.<br> |
||||
* Must be called after workdir is initialized. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class InitTaskSetupLog extends InitTask { |
||||
|
||||
private String logDir = "log"; |
||||
private String logName = "runtime"; |
||||
private int archiveCount = 5; |
||||
|
||||
private Level levelWrite = Level.ALL; |
||||
private Level levelPrint = Level.ALL; |
||||
|
||||
|
||||
/** |
||||
* Set log directory (relative to workdir).<br> |
||||
* Defaults to "log". |
||||
* |
||||
* @param logDir log directory. |
||||
*/ |
||||
public void setLogDir(String logDir) |
||||
{ |
||||
this.logDir = logDir; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set log name. This name is used as a prefix for archived log files.<br> |
||||
* Should contain only valid filename characters.<br> |
||||
* Defaults to "runtime". |
||||
* |
||||
* @param logName log name |
||||
*/ |
||||
public void setLogName(String logName) |
||||
{ |
||||
// TODO validate characters
|
||||
this.logName = logName; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set number of logs to keep in the logs directory.<br> |
||||
* Set to 0 to keep just the last log, -1 to keep unlimited number of logs.<br> |
||||
* Defaults to 5. |
||||
* |
||||
* @param archiveCount logs to keep |
||||
*/ |
||||
public void setArchiveCount(int archiveCount) |
||||
{ |
||||
this.archiveCount = archiveCount; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set logging levels (minimal level of message to be accepted)<br> |
||||
* Defaults to ALL, ALL. |
||||
* |
||||
* @param levelWrite level for writing to file |
||||
* @param levelPrint level for writing to stdout / stderr |
||||
*/ |
||||
public void setLevels(Level levelWrite, Level levelPrint) |
||||
{ |
||||
this.levelWrite = levelWrite; |
||||
this.levelPrint = levelPrint; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void run(App app) |
||||
{ |
||||
final LogWriter log = Log.create(logName, new File(WorkDir.getDir(logDir), logName + ".log"), archiveCount); |
||||
Log.setMainLogger(log); |
||||
Log.setLevel(levelWrite); |
||||
Log.setSysoutLevel(levelPrint); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String getName() |
||||
{ |
||||
return "log"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String[] getDependencies() |
||||
{ |
||||
return new String[] { "workdir" }; |
||||
} |
||||
} |
@ -0,0 +1,113 @@ |
||||
package mightypork.gamecore.initializers.tasks; |
||||
|
||||
|
||||
import java.io.File; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Map.Entry; |
||||
|
||||
import javax.swing.JOptionPane; |
||||
|
||||
import mightypork.gamecore.core.WorkDir; |
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.initializers.InitTask; |
||||
import mightypork.utils.annotations.Stub; |
||||
import mightypork.utils.files.InstanceLock; |
||||
import mightypork.utils.logging.Log; |
||||
|
||||
|
||||
/** |
||||
* Initializer that takes care of setting up the proper workdir. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class InitTaskSetupWorkdir extends InitTask { |
||||
|
||||
private final File workdirPath; |
||||
private boolean doLock; |
||||
private String lockFile = ".lock"; |
||||
private Map<String, String> namedPaths = new HashMap<>(); |
||||
|
||||
|
||||
/** |
||||
* @param workdir path to the working directory |
||||
* @param lock whether to lock the directory (single instance mode) |
||||
*/ |
||||
public InitTaskSetupWorkdir(File workdir, boolean lock) { |
||||
this.workdirPath = workdir; |
||||
this.doLock = lock; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set name of the lock file. |
||||
* |
||||
* @param lockFile |
||||
*/ |
||||
public void setLockFileName(String lockFile) |
||||
{ |
||||
this.lockFile = lockFile; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Add a named path |
||||
* |
||||
* @param alias path alias (snake_case) |
||||
* @param path path (relative to the workdir) |
||||
*/ |
||||
public void addPath(String alias, String path) |
||||
{ |
||||
namedPaths.put(alias, path); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void run(App app) |
||||
{ |
||||
WorkDir.init(workdirPath); |
||||
|
||||
// lock working directory
|
||||
if (doLock) { |
||||
final File lock = WorkDir.getFile(lockFile); |
||||
if (!InstanceLock.onFile(lock)) { |
||||
onLockError(); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
for (Entry<String, String> e : namedPaths.entrySet()) { |
||||
WorkDir.addPath(e.getKey(), e.getValue()); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Called when the lock file could not be obtained (cannot write or already |
||||
* exists).<br> |
||||
* Feel free to override this method to define custom behavior. |
||||
*/ |
||||
@Stub |
||||
protected void onLockError() |
||||
{ |
||||
Log.e("Could not obtain lock file.\nOnly one instance can run at a time."); |
||||
|
||||
//@formatter:off
|
||||
JOptionPane.showMessageDialog( |
||||
null, |
||||
"Another instance is already running.\n(Delete the "+lockFile +" file in the working directory to override)", |
||||
"Lock Error", |
||||
JOptionPane.ERROR_MESSAGE |
||||
); |
||||
//@formatter:on
|
||||
|
||||
App.shutdown(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String getName() |
||||
{ |
||||
return "workdir"; |
||||
} |
||||
} |
@ -0,0 +1,53 @@ |
||||
package mightypork.gamecore.initializers.tasks; |
||||
|
||||
|
||||
import java.io.IOException; |
||||
|
||||
import mightypork.gamecore.core.WorkDir; |
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.initializers.InitTask; |
||||
import mightypork.utils.logging.Log; |
||||
|
||||
|
||||
/** |
||||
* initializer task that writes a system info header to the log file.<br> |
||||
* Must be called after log is initialized. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class InitTaskWriteLogHeader extends InitTask { |
||||
|
||||
@Override |
||||
public void run(App app) |
||||
{ |
||||
String txt = ""; |
||||
|
||||
txt += "\n### SYSTEM INFO ###\n\n"; |
||||
txt += " Platform ...... " + System.getProperty("os.name") + "\n"; |
||||
txt += " Runtime ....... " + System.getProperty("java.runtime.name") + "\n"; |
||||
txt += " Java .......... " + System.getProperty("java.version") + "\n"; |
||||
txt += " Launch path ... " + System.getProperty("user.dir") + "\n"; |
||||
|
||||
try { |
||||
txt += " Workdir ....... " + WorkDir.getWorkDir().getCanonicalPath() + "\n"; |
||||
} catch (final IOException e) { |
||||
Log.e(e); |
||||
} |
||||
|
||||
Log.i(txt); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String getName() |
||||
{ |
||||
return "log_header"; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public String[] getDependencies() |
||||
{ |
||||
return new String[] { "log", "workdir" }; |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
package mightypork.gamecore.input; |
||||
|
||||
/** |
||||
* Type of keystroke (falling / rising edge) |
||||
*/ |
||||
public enum Edge |
||||
{ |
||||
/** Activated by falling edge (press) */ |
||||
FALLING, |
||||
/** Activated by rising edge (release) */ |
||||
RISING; |
||||
} |
@ -0,0 +1,28 @@ |
||||
package mightypork.gamecore.plugins; |
||||
|
||||
|
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.utils.annotations.Stub; |
||||
import mightypork.utils.eventbus.clients.BusNode; |
||||
|
||||
|
||||
/** |
||||
* App plugin. Plugins are an easy way to extend app functionality.<br> |
||||
* Typically, a plugin waits for trigger event(s) and performs some action upon |
||||
* receiving them. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class AppPlugin extends BusNode { |
||||
|
||||
/** |
||||
* Initialize the plugin for the given App.<br> |
||||
* The plugin is already attached to the event bus. |
||||
* |
||||
* @param app |
||||
*/ |
||||
@Stub |
||||
public void initialize(App app) |
||||
{ |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
package mightypork.gamecore.plugins.screenshot; |
||||
|
||||
|
||||
import mightypork.gamecore.core.events.MainLoopRequest; |
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.plugins.AppPlugin; |
||||
import mightypork.utils.Support; |
||||
|
||||
|
||||
/** |
||||
* This plugin waits for a {@link ScreenshotRequest} event.<br> |
||||
* Upon receiving it, a screenshot is captured and written to file |
||||
* asynchronously. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class ScreenshotPlugin extends AppPlugin { |
||||
|
||||
/** |
||||
* Take screenshot. Called by the trigger event. |
||||
*/ |
||||
void takeScreenshot() |
||||
{ |
||||
App.bus().send(new MainLoopRequest(new Runnable() { |
||||
|
||||
@Override |
||||
public void run() |
||||
{ |
||||
Runnable tts = new TaskTakeScreenshot(); |
||||
Support.runAsThread(tts); |
||||
} |
||||
})); |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
package mightypork.gamecore.plugins.screenshot; |
||||
|
||||
|
||||
import mightypork.utils.eventbus.BusEvent; |
||||
import mightypork.utils.eventbus.events.flags.SingleReceiverEvent; |
||||
|
||||
|
||||
/** |
||||
* Event used to request screenshot capture. |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
@SingleReceiverEvent |
||||
public class ScreenshotRequest extends BusEvent<ScreenshotPlugin> { |
||||
|
||||
@Override |
||||
protected void handleBy(ScreenshotPlugin handler) |
||||
{ |
||||
handler.takeScreenshot(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,100 @@ |
||||
package mightypork.gamecore.plugins.screenshot; |
||||
|
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
|
||||
import mightypork.gamecore.core.WorkDir; |
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.render.Screenshot; |
||||
import mightypork.utils.Support; |
||||
import mightypork.utils.logging.Log; |
||||
|
||||
|
||||
/** |
||||
* Task that takes screenshot and asynchronously saves it to a file.<br> |
||||
* Can be run in a separate thread, but must be instantiated in the render |
||||
* thread. |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public class TaskTakeScreenshot implements Runnable { |
||||
|
||||
private final Screenshot scr; |
||||
|
||||
|
||||
/** |
||||
* Take screenshot. Must be called in render thread. |
||||
*/ |
||||
public TaskTakeScreenshot() { |
||||
scr = App.gfx().takeScreenshot(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void run() |
||||
{ |
||||
// generate unique filename
|
||||
final File file = getScreenshotFile(); |
||||
|
||||
Log.f3("Saving screenshot to file: " + file); |
||||
|
||||
// save to disk
|
||||
try { |
||||
scr.save(file); |
||||
} catch (final IOException e) { |
||||
Log.e("Failed to save screenshot.", e); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @return File to save the screenshot to. |
||||
*/ |
||||
protected File getScreenshotFile() |
||||
{ |
||||
final String fname = getBaseFilename(); |
||||
return findFreeFile(fname); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @return directory for screenshots |
||||
*/ |
||||
protected File getScreenshotDirectory() |
||||
{ |
||||
return WorkDir.getDir("_screenshot_dir"); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get base filename for the screenshot, without extension. |
||||
* |
||||
* @return filename |
||||
*/ |
||||
protected String getBaseFilename() |
||||
{ |
||||
return Support.getTime("yyyy-MM-dd_HH-mm-ss"); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Find first free filename for the screenshot, by adding -NUMBER after the |
||||
* base filename and before extension. |
||||
* |
||||
* @param base_name base filename |
||||
* @return full path to screenshot file |
||||
*/ |
||||
protected File findFreeFile(String base_name) |
||||
{ |
||||
File file; |
||||
int index = 0; |
||||
while (true) { |
||||
file = new File(getScreenshotDirectory(), base_name + (index > 0 ? "-" + index : "") + ".png"); |
||||
if (!file.exists()) break; |
||||
index++; |
||||
} |
||||
return file; |
||||
} |
||||
|
||||
} |
@ -1,57 +0,0 @@ |
||||
package mightypork.gamecore.render; |
||||
|
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
|
||||
import mightypork.gamecore.core.WorkDir; |
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.utils.Support; |
||||
import mightypork.utils.logging.Log; |
||||
|
||||
import org.newdawn.slick.opengl.GLUtils; |
||||
|
||||
|
||||
/** |
||||
* Task that takes screenshot and asynchronously saves it to a file.<br> |
||||
* Can be run in a separate thread, but must be instantiated in the render |
||||
* thread. |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public class TaskTakeScreenshot implements Runnable { |
||||
|
||||
private final Screenshot scr; |
||||
|
||||
|
||||
public TaskTakeScreenshot() { |
||||
GLUtils.checkGLContext(); |
||||
scr = App.gfx().takeScreenshot(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void run() |
||||
{ |
||||
final String fname = Support.getTime("yyyy-MM-dd_HH-mm-ss"); |
||||
|
||||
// generate unique filename
|
||||
File file; |
||||
int index = 0; |
||||
while (true) { |
||||
file = new File(WorkDir.getDir("_screenshot_dir"), fname + (index > 0 ? "-" + index : "") + ".png"); |
||||
if (!file.exists()) break; |
||||
index++; |
||||
} |
||||
|
||||
Log.f3("Saving screenshot to file: " + file); |
||||
|
||||
// save to disk
|
||||
try { |
||||
scr.save(file); |
||||
} catch (final IOException e) { |
||||
Log.e("Failed to save screenshot.", e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -1,15 +0,0 @@ |
||||
package mightypork.gamecore.render.events; |
||||
|
||||
|
||||
import mightypork.utils.eventbus.BusEvent; |
||||
|
||||
|
||||
public class ScreenshotRequest extends BusEvent<ScreenshotRequestListener> { |
||||
|
||||
@Override |
||||
protected void handleBy(ScreenshotRequestListener handler) |
||||
{ |
||||
handler.onScreenshotRequest(); |
||||
} |
||||
|
||||
} |
@ -1,7 +0,0 @@ |
||||
package mightypork.gamecore.render.events; |
||||
|
||||
|
||||
public interface ScreenshotRequestListener { |
||||
|
||||
public void onScreenshotRequest(); |
||||
} |
@ -0,0 +1,220 @@ |
||||
package mightypork.gamecore.resources.audio; |
||||
|
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.lwjgl.openal.AL; |
||||
import org.newdawn.slick.openal.SoundStore; |
||||
|
||||
import mightypork.gamecore.backend.BackendModule; |
||||
import mightypork.gamecore.backend.lwjgl.SlickAudio; |
||||
import mightypork.gamecore.core.modules.App; |
||||
import mightypork.gamecore.resources.ResourceLoadRequest; |
||||
import mightypork.gamecore.resources.audio.players.EffectPlayer; |
||||
import mightypork.gamecore.resources.audio.players.LoopPlayer; |
||||
import mightypork.utils.interfaces.Updateable; |
||||
import mightypork.utils.logging.Log; |
||||
import mightypork.utils.math.constraints.vect.Vect; |
||||
|
||||
|
||||
/** |
||||
* Abstract audio backend module |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public abstract class AudioModule extends BackendModule implements Updateable { |
||||
|
||||
/** |
||||
* Set listener position |
||||
* |
||||
* @param pos listener position |
||||
*/ |
||||
public abstract void setListenerPos(Vect pos); |
||||
|
||||
|
||||
/** |
||||
* Get current listener position |
||||
* |
||||
* @return listener position |
||||
*/ |
||||
public abstract Vect getListenerPos(); |
||||
|
||||
// -- instance --
|
||||
|
||||
private final Volume masterVolume = new Volume(1D); |
||||
private final Volume effectsVolume = new JointVolume(masterVolume); |
||||
private final Volume loopsVolume = new JointVolume(masterVolume); |
||||
|
||||
private final List<LoopPlayer> loopPlayers = new ArrayList<>(); |
||||
private final List<DeferredAudio> resources = new ArrayList<>(); |
||||
|
||||
|
||||
@Override |
||||
public void destroy() |
||||
{ |
||||
for (final DeferredAudio r : resources) { |
||||
r.destroy(); |
||||
} |
||||
|
||||
deinitSoundSystem(); |
||||
} |
||||
|
||||
|
||||
protected abstract void deinitSoundSystem(); |
||||
|
||||
|
||||
@Override |
||||
public void update(double delta) |
||||
{ |
||||
for (final Updateable lp : loopPlayers) { |
||||
lp.update(delta); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create effect resource |
||||
* |
||||
* @param resource resource path |
||||
* @param pitch default pitch (1 = unchanged) |
||||
* @param gain default gain (0-1) |
||||
* @return player |
||||
*/ |
||||
public EffectPlayer createEffect(String resource, double pitch, double gain) |
||||
{ |
||||
return new EffectPlayer(createResource(resource), pitch, gain, effectsVolume); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Register loop resource (music / effect loop) |
||||
* |
||||
* @param resource resource path |
||||
* @param pitch default pitch (1 = unchanged) |
||||
* @param gain default gain (0-1) |
||||
* @param fadeIn default time for fadeIn |
||||
* @param fadeOut default time for fadeOut |
||||
* @return player |
||||
*/ |
||||
public LoopPlayer createLoop(String resource, double pitch, double gain, double fadeIn, double fadeOut) |
||||
{ |
||||
final LoopPlayer p = new LoopPlayer(createResource(resource), pitch, gain, loopsVolume); |
||||
p.setFadeTimes(fadeIn, fadeOut); |
||||
loopPlayers.add(p); |
||||
return p; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create {@link DeferredAudio} for a resource |
||||
* |
||||
* @param res a resource name |
||||
* @return the resource |
||||
* @throws IllegalArgumentException if resource is already registered |
||||
*/ |
||||
protected DeferredAudio createResource(String res) |
||||
{ |
||||
final DeferredAudio a = doCreateResource(res);; |
||||
App.bus().send(new ResourceLoadRequest(a)); |
||||
resources.add(a); |
||||
return a; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create a backend-specific deferred audio resource instance. |
||||
* |
||||
* @param res resource path |
||||
* @return Deferred Audio |
||||
*/ |
||||
protected abstract DeferredAudio doCreateResource(String res); |
||||
|
||||
|
||||
/** |
||||
* Fade out all loops (ie. for screen transitions) |
||||
*/ |
||||
public void fadeOutAllLoops() |
||||
{ |
||||
for (final LoopPlayer p : loopPlayers) { |
||||
p.fadeOut(); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Pause all loops (leave volume unchanged) |
||||
*/ |
||||
public void pauseAllLoops() |
||||
{ |
||||
for (final LoopPlayer p : loopPlayers) { |
||||
p.pause(); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set level of master volume |
||||
* |
||||
* @param d level |
||||
*/ |
||||
public void setMasterVolume(double d) |
||||
{ |
||||
masterVolume.set(d); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set level of effects volume |
||||
* |
||||
* @param d level |
||||
*/ |
||||
public void setEffectsVolume(double d) |
||||
{ |
||||
effectsVolume.set(d); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set level of music volume |
||||
* |
||||
* @param d level |
||||
*/ |
||||
public void setMusicVolume(double d) |
||||
{ |
||||
loopsVolume.set(d); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get level of master volume |
||||
* |
||||
* @return level |
||||
*/ |
||||
public double getMasterVolume() |
||||
{ |
||||
return masterVolume.get(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get level of effects volume |
||||
* |
||||
* @return level |
||||
*/ |
||||
public double getEffectsVolume() |
||||
{ |
||||
return effectsVolume.get(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get level of music volume |
||||
* |
||||
* @return level |
||||
*/ |
||||
public double getMusicVolume() |
||||
{ |
||||
return loopsVolume.get(); |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
package mightypork.gamecore.resources.audio; |
||||
|
||||
|
||||
import mightypork.gamecore.resources.BaseDeferredResource; |
||||
import mightypork.utils.annotations.Alias; |
||||
|
||||
|
||||
/** |
||||
* Base deferred audio |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
@Alias(name = "Audio") |
||||
public abstract class DeferredAudio extends BaseDeferredResource implements IAudio { |
||||
|
||||
/** |
||||
* Create deferred primitive audio player |
||||
* |
||||
* @param resourceName resource to load when needed |
||||
*/ |
||||
public DeferredAudio(String resourceName) { |
||||
super(resourceName); |
||||
} |
||||
} |
@ -0,0 +1,99 @@ |
||||
package mightypork.gamecore.resources.audio; |
||||
|
||||
|
||||
import mightypork.utils.interfaces.Destroyable; |
||||
import mightypork.utils.math.constraints.vect.Vect; |
||||
|
||||
|
||||
/** |
||||
* Audio resource interface (backend independent) |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public interface IAudio extends Destroyable { |
||||
|
||||
/** |
||||
* Pause loop (remember position and stop playing) - if was looping |
||||
*/ |
||||
void pauseLoop(); |
||||
|
||||
|
||||
/** |
||||
* Resume loop (if was paused) |
||||
*/ |
||||
void resumeLoop(); |
||||
|
||||
|
||||
/** |
||||
* Adjust gain for the currently playing effect (can be used for fading |
||||
* music) |
||||
* |
||||
* @param gain gain to set 0..1 |
||||
*/ |
||||
void adjustGain(double gain); |
||||
|
||||
|
||||
/** |
||||
* Stop audio playback |
||||
*/ |
||||
void stop(); |
||||
|
||||
|
||||
/** |
||||
* @return true if the audio is playing |
||||
*/ |
||||
boolean isPlaying(); |
||||
|
||||
|
||||
/** |
||||
* @return trie if the audio is paused |
||||
*/ |
||||
boolean isPaused(); |
||||
|
||||
|
||||
/** |
||||
* Play as sound effect at listener position |
||||
* |
||||
* @param pitch pitch (1 = default) |
||||
* @param gain gain (0-1) |
||||
* @param loop looping |
||||
*/ |
||||
void play(double pitch, double gain, boolean loop); |
||||
|
||||
|
||||
/** |
||||
* Play as sound effect at given X-Y position |
||||
* |
||||
* @param pitch pitch (1 = default) |
||||
* @param gain gain (0-1) |
||||
* @param loop looping |
||||
* @param x |
||||
* @param y |
||||
*/ |
||||
void play(double pitch, double gain, boolean loop, double x, double y); |
||||
|
||||
|
||||
/** |
||||
* Play as sound effect at given position |
||||
* |
||||
* @param pitch pitch (1 = default) |
||||
* @param gain gain (0-1) |
||||
* @param loop looping |
||||
* @param x |
||||
* @param y |
||||
* @param z |
||||
*/ |
||||
void play(double pitch, double gain, boolean loop, double x, double y, double z); |
||||
|
||||
|
||||
/** |
||||
* Play as sound effect at given position |
||||
* |
||||
* @param pitch pitch (1 = default) |
||||
* @param gain gain (0-1) |
||||
* @param loop looping |
||||
* @param pos coord |
||||
*/ |
||||
void play(double pitch, double gain, boolean loop, Vect pos); |
||||
|
||||
} |
@ -1,249 +0,0 @@ |
||||
package mightypork.gamecore.resources.audio; |
||||
|
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
|
||||
import mightypork.gamecore.resources.BaseLazyResource; |
||||
import mightypork.utils.annotations.Alias; |
||||
import mightypork.utils.files.FileUtils; |
||||
import mightypork.utils.math.constraints.vect.Vect; |
||||
|
||||
import org.newdawn.slick.openal.Audio; |
||||
import org.newdawn.slick.openal.SoundStore; |
||||
|
||||
|
||||
/** |
||||
* Wrapper class for slick audio |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
@Alias(name = "Audio") |
||||
public class LazyAudio extends BaseLazyResource { |
||||
|
||||
private enum PlayMode |
||||
{ |
||||
EFFECT, MUSIC; |
||||
} |
||||
|
||||
/** Audio resource */ |
||||
private Audio backingAudio = null; |
||||
|
||||
// last play options
|
||||
private PlayMode mode = PlayMode.EFFECT; |
||||
private double pauseLoopPosition = 0; |
||||
private boolean looping = false; |
||||
private boolean paused = false; |
||||
private double lastPlayPitch = 1; |
||||
private double lastPlayGain = 1; |
||||
|
||||
|
||||
/** |
||||
* Create deferred primitive audio player |
||||
* |
||||
* @param resourceName resource to load when needed |
||||
*/ |
||||
public LazyAudio(String resourceName) { |
||||
super(resourceName); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Pause loop (remember position and stop playing) - if was looping |
||||
*/ |
||||
public void pauseLoop() |
||||
{ |
||||
if (!ensureLoaded()) return; |
||||
|
||||
if (isPlaying() && looping) { |
||||
pauseLoopPosition = backingAudio.getPosition(); |
||||
stop(); |
||||
paused = true; |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Resume loop (if was paused) |
||||
* |
||||
* @return source ID |
||||
*/ |
||||
public int resumeLoop() |
||||
{ |
||||
if (!ensureLoaded()) return -1; |
||||
|
||||
int source = -1; |
||||
if (looping && paused) { |
||||
if (mode == PlayMode.MUSIC) { |
||||
source = backingAudio.playAsMusic((float) lastPlayPitch, (float) lastPlayGain, true); |
||||
} else { |
||||
source = backingAudio.playAsSoundEffect((float) lastPlayPitch, (float) lastPlayGain, true); |
||||
} |
||||
backingAudio.setPosition((float) pauseLoopPosition); |
||||
paused = false; |
||||
} |
||||
return source; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void loadResource(String resource) throws IOException |
||||
{ |
||||
final String ext = FileUtils.getExtension(resource); |
||||
|
||||
try (final InputStream stream = FileUtils.getResource(resource)) { |
||||
|
||||
if (ext.equalsIgnoreCase("ogg")) { |
||||
backingAudio = SoundStore.get().getOgg(resource, stream); |
||||
|
||||
} else if (ext.equalsIgnoreCase("wav")) { |
||||
backingAudio = SoundStore.get().getWAV(resource, stream); |
||||
|
||||
} else if (ext.equalsIgnoreCase("aif")) { |
||||
backingAudio = SoundStore.get().getAIF(resource, stream); |
||||
|
||||
} else if (ext.equalsIgnoreCase("mod")) { |
||||
backingAudio = SoundStore.get().getMOD(resource, stream); |
||||
|
||||
} else { |
||||
throw new RuntimeException("Invalid audio file extension."); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Stop playing |
||||
*/ |
||||
public void stop() |
||||
{ |
||||
if (!isLoaded()) return; |
||||
|
||||
backingAudio.stop(); |
||||
paused = false; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @return true if the audio is playing |
||||
*/ |
||||
public boolean isPlaying() |
||||
{ |
||||
if (!isLoaded()) return false; |
||||
|
||||
return backingAudio.isPlaying(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @return trie if the audio is paused |
||||
*/ |
||||
public boolean isPaused() |
||||
{ |
||||
if (!isLoaded()) return false; |
||||
|
||||
return backingAudio.isPaused(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Play as sound effect at listener position |
||||
* |
||||
* @param pitch pitch (1 = default) |
||||
* @param gain gain (0-1) |
||||
* @param loop looping |
||||
* @return source id |
||||
*/ |
||||
public int playAsEffect(double pitch, double gain, boolean loop) |
||||
{ |
||||
return playAsEffect(pitch, gain, loop, SoundSystem.getListener()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Play as sound effect at given X-Y position |
||||
* |
||||
* @param pitch pitch (1 = default) |
||||
* @param gain gain (0-1) |
||||
* @param loop looping |
||||
* @param x |
||||
* @param y |
||||
* @return source id |
||||
*/ |
||||
public int playAsEffect(double pitch, double gain, boolean loop, double x, double y) |
||||
{ |
||||
return playAsEffect(pitch, gain, loop, x, y, SoundSystem.getListener().z()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Play as sound effect at given position |
||||
* |
||||
* @param pitch pitch (1 = default) |
||||
* @param gain gain (0-1) |
||||
* @param loop looping |
||||
* @param x |
||||
* @param y |
||||
* @param z |
||||
* @return source id |
||||
*/ |
||||
public int playAsEffect(double pitch, double gain, boolean loop, double x, double y, double z) |
||||
{ |
||||
if (!ensureLoaded()) return -1; |
||||
|
||||
this.lastPlayPitch = pitch; |
||||
this.lastPlayGain = gain; |
||||
looping = loop; |
||||
mode = PlayMode.EFFECT; |
||||
return backingAudio.playAsSoundEffect((float) pitch, (float) gain, loop, (float) x, (float) y, (float) z); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Play as sound effect at given position |
||||
* |
||||
* @param pitch pitch (1 = default) |
||||
* @param gain gain (0-1) |
||||
* @param loop looping |
||||
* @param pos coord |
||||
* @return source id |
||||
*/ |
||||
public int playAsEffect(double pitch, double gain, boolean loop, Vect pos) |
||||
{ |
||||
if (!ensureLoaded()) return -1; |
||||
|
||||
return playAsEffect(pitch, gain, loop, pos.x(), pos.y(), pos.z()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Play as music using source 0.<br> |
||||
* Discouraged, since this does not allow cross-fading. |
||||
* |
||||
* @param pitch play pitch |
||||
* @param gain play gain |
||||
* @param loop looping |
||||
* @return source |
||||
*/ |
||||
public int playAsMusic(double pitch, double gain, boolean loop) |
||||
{ |
||||
if (!ensureLoaded()) return -1; |
||||
|
||||
this.lastPlayPitch = (float) pitch; |
||||
this.lastPlayGain = (float) gain; |
||||
looping = loop; |
||||
mode = PlayMode.MUSIC; |
||||
return backingAudio.playAsMusic((float) pitch, (float) gain, loop); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void destroy() |
||||
{ |
||||
if (!isLoaded() || backingAudio == null) return; |
||||
|
||||
backingAudio.release(); |
||||
backingAudio = null; |
||||
} |
||||
|
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue