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