Added JSRClone, audio engine rewrite, bump to alpha.2
|
@ -1,50 +0,0 @@
|
|||
## Features targeted for 22.3.0.0-alpha.0
|
||||
* `[PlutoRuntime,PlutoCore]` **Initial implementation of the layer system (formerly known as "stage")**
|
||||
* A "layer", in this context, is a set of assets bound together
|
||||
by programming logic.
|
||||
Layer switching and asset management are handled by the engine.
|
||||
* Layers can be stacked on top of each other and run sequentially
|
||||
from bottom to top
|
||||
* Upon layer switch
|
||||
1. Unload unused assets
|
||||
2. Load new assets
|
||||
* Provide multiple means of layer switching
|
||||
* Two modes with the initial release, asynchronous switching will come at a later date
|
||||
1. Instant switch
|
||||
* Assets will be loaded and unloaded synchronously
|
||||
* The layer switch will happen in one frame
|
||||
2. Deferred switch
|
||||
* The layer will continue running until all assets load
|
||||
* Assets will load synchronously, but at a slower pace
|
||||
to avoid frame stutter
|
||||
* Automated asset loading
|
||||
* All asset management will eventually be handled by `PlutoCore`
|
||||
* This includes audio clips, textures, sprites
|
||||
* Add a common interface for all assets
|
||||
* Let the stage system handle audio playback
|
||||
* This API should be as neutral as possible and avoid steering towards
|
||||
game-only use
|
||||
* The stage manager should be relatively low-overhead and allow multiple
|
||||
instances
|
||||
* Allow stages to be inherited from, creating a stack-like structure
|
||||
* `[PlutoAudio]` Integrate the Audio API with the Stage API
|
||||
|
||||
## Features targeted for an unspecified future release
|
||||
* `[PlutoSpritesheet]` Expanded capabilities
|
||||
* Support for 9-slice rendering
|
||||
* Support for animated sprite rendering
|
||||
* Support for multidirectional sprite rendering
|
||||
* A spritesheet skeleton editor
|
||||
|
||||
* `[PlutoRuntime]`
|
||||
* Asynchronous switch
|
||||
* Assets will be loaded in asynchronously, where applicable
|
||||
* Falls back to deferred switching for synchronous loading,
|
||||
such as OpenGL texture upload
|
||||
|
||||
* `[PlutoGUI]` A fully-fledged GUI engine
|
||||
* Improve font-rendering capabilities
|
||||
* Subpixel rendering support [?]
|
||||
* Reimplement support for bitmap fonts
|
||||
* Improve upon the support of thread-local Pluto instances
|
||||
* The long term goal is to allow an unlimited amount of Pluto applications at any given time
|
27
README.md
|
@ -25,7 +25,7 @@ repositories {
|
|||
|
||||
|
||||
dependencies {
|
||||
implementation group: "org.plutoengine", name: "plutocore", version: "22.2.0.0-alpha.0"
|
||||
implementation group: "org.plutoengine", name: "plutocore", version: "22.2.0.0-alpha.2"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -41,10 +41,17 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.plutoengine", "plutocore", "22.2.0.0-alpha.0")
|
||||
implementation("org.plutoengine", "plutocore", "22.2.0.0-alpha.2")
|
||||
}
|
||||
```
|
||||
|
||||
### Licensing
|
||||
|
||||
While all code of PlutoEngine is licensed under MIT, some media in this repository
|
||||
is licensed under different terms:
|
||||
|
||||
* Music in the **jsr-clone** demo is made by Selulance, licensed under **CC - BY 3.0**
|
||||
|
||||
### Versioning
|
||||
|
||||
All submodules share a version number for simplicity reasons.
|
||||
|
@ -64,6 +71,9 @@ version numbers.*
|
|||
|
||||
## Usability status of submodules
|
||||
|
||||
Keep in mind PlutoEngine is in alpha and all features are tentative.
|
||||
The following list simply provides an overview of how likely breaking changes are to occur.
|
||||
|
||||
### Safe submodules
|
||||
* **PlutoCore** - Stable
|
||||
* **PlutoFramebuffer** - Stable
|
||||
|
@ -74,12 +84,12 @@ version numbers.*
|
|||
* **PlutoDisplay** - Stable, collision API nowhere near completion
|
||||
* **PlutoUSS2** - Stable
|
||||
* **PlutoLib** - Mostly stable
|
||||
|
||||
* **PlutoRuntime** - Mostly stable
|
||||
|
||||
### Unstable submodules
|
||||
* **PlutoGUI** - Recently rewritten, the API is highly unstable
|
||||
* **PlutoRuntime** - Somewhat tentative, the module API has been rewritten and might contain bugs
|
||||
* **PlutoAudio** - Somewhat usable, unfinished
|
||||
|
||||
* **PlutoAudio** - Very tentative, work in progress
|
||||
* **PlutoGUI** - Recently rewritten, the API is highly unstable, work in progress
|
||||
|
||||
|
||||
## Current priorities
|
||||
|
||||
|
@ -92,12 +102,11 @@ See `NEXT_RELEASE_DRAFT.md` for details.
|
|||
|
||||
### Very high priority
|
||||
[ *Implemented in the current release.* ]
|
||||
* Implement the layer system and integrate all existing systems with it
|
||||
* Improve image loading capabilities, possibly rewrite PlutoLib#TPL
|
||||
|
||||
### High priority
|
||||
[ *Implemented in the next release.* ]
|
||||
* Finish PlutoAudio
|
||||
* Depends on the stage system
|
||||
* Expand upon the Color API
|
||||
* Color mixing and blending
|
||||
* Color transformation
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
## 22.2.0.0-alpha.2
|
||||
* `[SDK]` The libraries now always reference natives for all architectures
|
||||
* `[SDK]` Replaced `NEXT_RELEASE_DRAFT.md` with [an issue tracker](https://github.com/493msi/plutoengine/issues)
|
||||
* `[PlutoAudio]` **Partial rewrite and support for managed sound effects**
|
||||
* `plutoengine-demos/` **Added the `jsr-clone` demo**
|
||||
* `[PlutoSpritesheet]` Renamed `TemporalSprite#getSideCount` to `getFrameCount`
|
||||
|
||||
## 22.2.0.0-alpha.1
|
||||
|
|
|
@ -5,11 +5,16 @@ import org.gradle.api.JavaVersion
|
|||
|
||||
object Versions {
|
||||
const val lwjglVersion = "3.3.1"
|
||||
val lwjglNatives = when (OperatingSystem.current()) {
|
||||
OperatingSystem.LINUX -> "natives-linux"
|
||||
OperatingSystem.WINDOWS -> "natives-windows"
|
||||
else -> throw Error("Unsupported operating system!")
|
||||
}
|
||||
val lwjglNatives = listOf(
|
||||
"natives-linux-arm64",
|
||||
"natives-linux-arm32",
|
||||
"natives-linux",
|
||||
"natives-macos-arm64",
|
||||
"natives-macos",
|
||||
"natives-windows-arm64",
|
||||
"natives-windows",
|
||||
"natives-windows-x86"
|
||||
)
|
||||
|
||||
const val jomlVersion = "1.10.2"
|
||||
const val jomlPrimitivesVersion = "1.10.0"
|
||||
|
@ -23,7 +28,7 @@ object Versions {
|
|||
|
||||
const val isPrerelease = true
|
||||
const val prereleaseName = "alpha"
|
||||
const val prerealeaseUpdate = 1
|
||||
const val prerealeaseUpdate = 2
|
||||
|
||||
val versionFull =
|
||||
if (isPrerelease)
|
||||
|
|
|
@ -12,5 +12,7 @@ dependencies {
|
|||
|
||||
api("org.lwjgl:lwjgl-openal")
|
||||
|
||||
runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = Versions.lwjglNatives)
|
||||
org.plutoengine.Versions.lwjglNatives.forEach {
|
||||
runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = it)
|
||||
}
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
import org.lwjgl.stb.STBVorbis;
|
||||
import org.lwjgl.stb.STBVorbisInfo;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.plutoengine.buffer.BufferHelper;
|
||||
import org.plutoengine.logger.Logger;
|
||||
|
@ -10,8 +7,6 @@ import org.plutoengine.logger.SmartSeverity;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
|
@ -23,7 +18,7 @@ public class AudioLoader
|
|||
* medium-sized audio files, however it is discouraged to use such a track
|
||||
* in multiple audio sources at once due to the cost of seeking.
|
||||
*/
|
||||
public static ISeekableAudioTrack loadMemoryDecoded(Path path)
|
||||
public static SeekableTrack loadMemoryDecoded(Path path)
|
||||
{
|
||||
|
||||
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
|
||||
|
@ -45,13 +40,13 @@ public class AudioLoader
|
|||
* for from-memory PCM streaming. Good for frequently used small audio
|
||||
* files.
|
||||
*/
|
||||
public static ISeekableAudioTrack loadMemoryPCM(Path path)
|
||||
public static RandomAccessClip loadMemoryPCM(Path path)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
|
||||
|
||||
try
|
||||
{
|
||||
return new MemoryPCMTrack(path);
|
||||
return new MemoryPCMClip(path);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
@ -61,7 +56,7 @@ public class AudioLoader
|
|||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer loadIntoMemory(Path path) throws IOException
|
||||
static ByteBuffer loadIntoMemory(Path path) throws IOException
|
||||
{
|
||||
var size = Files.size(path);
|
||||
|
||||
|
@ -73,202 +68,4 @@ public class AudioLoader
|
|||
return BufferHelper.readToByteBuffer(path, readData);
|
||||
}
|
||||
|
||||
private abstract static class StreamableTrack implements IAudioStream
|
||||
{
|
||||
protected int channels;
|
||||
protected int sampleRate;
|
||||
|
||||
@Override
|
||||
public int getChannels()
|
||||
{
|
||||
return this.channels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleRate()
|
||||
{
|
||||
return this.sampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class SeekableTrack extends StreamableTrack implements ISeekableAudioTrack
|
||||
{
|
||||
protected int samplesLength;
|
||||
|
||||
@Override
|
||||
public int getLengthInSamples()
|
||||
{
|
||||
return this.samplesLength;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MemoryPCMTrack extends SeekableTrack
|
||||
{
|
||||
private final ShortBuffer pcmAudio;
|
||||
|
||||
private int sampleOffset = 0;
|
||||
|
||||
private MemoryPCMTrack(Path path) throws IOException
|
||||
{
|
||||
long handle = MemoryUtil.NULL;
|
||||
ByteBuffer audioBytes = null;
|
||||
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
audioBytes = loadIntoMemory(path);
|
||||
|
||||
IntBuffer error = stack.mallocInt(1);
|
||||
handle = STBVorbis.stb_vorbis_open_memory(audioBytes, error, null);
|
||||
|
||||
if (handle == MemoryUtil.NULL)
|
||||
{
|
||||
this.close();
|
||||
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
|
||||
}
|
||||
|
||||
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
|
||||
STBVorbis.stb_vorbis_get_info(handle, info);
|
||||
|
||||
this.channels = info.channels();
|
||||
this.sampleRate = info.sample_rate();
|
||||
|
||||
// Downmix to mono, SOUNDS HORRIBLE
|
||||
//
|
||||
// this.channels = 1;
|
||||
// this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
|
||||
// this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
|
||||
// var ptr = stack.pointers(this.pcmAudio);
|
||||
// STBVorbis.stb_vorbis_get_samples_short(handle, ptr, this.samplesLength);
|
||||
|
||||
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
|
||||
this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
|
||||
STBVorbis.stb_vorbis_get_samples_short_interleaved(handle, this.channels, this.pcmAudio);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
this.close();
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemoryUtil.memFree(audioBytes);
|
||||
|
||||
if (handle != MemoryUtil.NULL)
|
||||
{
|
||||
STBVorbis.stb_vorbis_close(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(int sampleIndex)
|
||||
{
|
||||
this.sampleOffset = sampleIndex * this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getSamples(ShortBuffer pcm)
|
||||
{
|
||||
this.pcmAudio.limit(Math.min(this.sampleOffset + pcm.remaining(), this.pcmAudio.capacity()));
|
||||
int read = this.pcmAudio.remaining();
|
||||
pcm.put(this.pcmAudio);
|
||||
this.sampleOffset += read;
|
||||
pcm.clear();
|
||||
|
||||
return read / this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleOffset()
|
||||
{
|
||||
return this.sampleOffset / this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
MemoryUtil.memFree(this.pcmAudio);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MemoryDecodedVorbisTrack extends SeekableTrack
|
||||
{
|
||||
protected long handle;
|
||||
|
||||
private final ByteBuffer encodedAudio;
|
||||
|
||||
private MemoryDecodedVorbisTrack(Path path) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
this.encodedAudio = loadIntoMemory(path);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
this.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
IntBuffer error = stack.mallocInt(1);
|
||||
this.handle = STBVorbis.stb_vorbis_open_memory(this.encodedAudio, error, null);
|
||||
|
||||
if (this.handle == MemoryUtil.NULL)
|
||||
{
|
||||
this.close();
|
||||
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
|
||||
}
|
||||
|
||||
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
|
||||
STBVorbis.stb_vorbis_get_info(this.handle, info);
|
||||
|
||||
this.channels = info.channels();
|
||||
this.sampleRate = info.sample_rate();
|
||||
}
|
||||
|
||||
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(this.handle);
|
||||
|
||||
Logger.logf(SmartSeverity.AUDIO, """
|
||||
\tSample rate:\t%d
|
||||
\t\tChannels:\t%d
|
||||
\t\tSamples:\t%d
|
||||
%n""", this.sampleRate, this.channels, this.samplesLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getSamples(ShortBuffer pcm)
|
||||
{
|
||||
if (this.handle == MemoryUtil.NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return STBVorbis.stb_vorbis_get_samples_short_interleaved(this.handle, this.getChannels(), pcm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
MemoryUtil.memFree(this.encodedAudio);
|
||||
|
||||
if (this.handle != MemoryUtil.NULL)
|
||||
{
|
||||
STBVorbis.stb_vorbis_close(this.handle);
|
||||
this.handle = MemoryUtil.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void seek(int sampleIndex)
|
||||
{
|
||||
STBVorbis.stb_vorbis_seek(this.handle, sampleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleOffset()
|
||||
{
|
||||
return STBVorbis.stb_vorbis_get_sample_offset(this.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
public abstract class ClipTrack extends SeekableTrack implements ISeekableClip
|
||||
{
|
||||
protected int samplesLength;
|
||||
|
||||
@Override
|
||||
public int getLengthInSamples()
|
||||
{
|
||||
return this.samplesLength;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
public interface IAudio extends AutoCloseable
|
||||
{
|
||||
int getSampleRate();
|
||||
|
||||
int getChannels();
|
||||
|
||||
void close();
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public interface IAudioStream extends AutoCloseable
|
||||
{
|
||||
int getSamples(ShortBuffer pcm);
|
||||
|
||||
int getSampleRate();
|
||||
|
||||
int getChannels();
|
||||
|
||||
void close();
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
public interface IClip extends IAudio
|
||||
{
|
||||
int getLengthInSamples();
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public interface IRandomAccessAudio extends IClip
|
||||
{
|
||||
int getSamples(ShortBuffer pcm, int offset, boolean loopRead);
|
||||
}
|
|
@ -1,11 +1,7 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
public interface ISeekableAudioTrack extends IAudioStream
|
||||
public interface ISeekableClip extends ISeekableTrack, IClip
|
||||
{
|
||||
int getSampleOffset();
|
||||
|
||||
int getLengthInSamples();
|
||||
|
||||
default void skip(int sampleCount)
|
||||
{
|
||||
this.seek(Math.min(Math.max(0, this.getSampleOffset() + sampleCount), this.getLengthInSamples()));
|
||||
|
@ -15,11 +11,4 @@ public interface ISeekableAudioTrack extends IAudioStream
|
|||
{
|
||||
this.seek(Math.round(this.getLengthInSamples() * offset0to1));
|
||||
}
|
||||
|
||||
default void rewind()
|
||||
{
|
||||
this.seek(0);
|
||||
}
|
||||
|
||||
void seek(int sampleIndex);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
public interface ISeekableTrack extends IStreamingAudio
|
||||
{
|
||||
void seek(int sampleIndex);
|
||||
|
||||
default void rewind()
|
||||
{
|
||||
this.seek(0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public interface IStreamingAudio extends IAudio
|
||||
{
|
||||
int getSamples(ShortBuffer pcm);
|
||||
|
||||
int getSampleOffset();
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
import org.lwjgl.stb.STBVorbis;
|
||||
import org.lwjgl.stb.STBVorbisInfo;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.plutoengine.logger.Logger;
|
||||
import org.plutoengine.logger.SmartSeverity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class MemoryDecodedVorbisTrack extends ClipTrack
|
||||
{
|
||||
protected long handle;
|
||||
|
||||
private final ByteBuffer encodedAudio;
|
||||
|
||||
MemoryDecodedVorbisTrack(Path path) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
this.encodedAudio = AudioLoader.loadIntoMemory(path);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
this.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
IntBuffer error = stack.mallocInt(1);
|
||||
this.handle = STBVorbis.stb_vorbis_open_memory(this.encodedAudio, error, null);
|
||||
|
||||
if (this.handle == MemoryUtil.NULL)
|
||||
{
|
||||
this.close();
|
||||
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
|
||||
}
|
||||
|
||||
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
|
||||
STBVorbis.stb_vorbis_get_info(this.handle, info);
|
||||
|
||||
this.channels = info.channels();
|
||||
this.sampleRate = info.sample_rate();
|
||||
}
|
||||
|
||||
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(this.handle);
|
||||
|
||||
Logger.logf(SmartSeverity.AUDIO, """
|
||||
\tSample rate:\t%d
|
||||
\t\tChannels:\t%d
|
||||
\t\tSamples:\t%d
|
||||
%n""", this.sampleRate, this.channels, this.samplesLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getSamples(ShortBuffer pcm)
|
||||
{
|
||||
if (this.handle == MemoryUtil.NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return STBVorbis.stb_vorbis_get_samples_short_interleaved(this.handle, this.getChannels(), pcm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
MemoryUtil.memFree(this.encodedAudio);
|
||||
|
||||
if (this.handle != MemoryUtil.NULL)
|
||||
{
|
||||
STBVorbis.stb_vorbis_close(this.handle);
|
||||
this.handle = MemoryUtil.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void seek(int sampleIndex)
|
||||
{
|
||||
STBVorbis.stb_vorbis_seek(this.handle, sampleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleOffset()
|
||||
{
|
||||
return STBVorbis.stb_vorbis_get_sample_offset(this.handle);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
import org.lwjgl.stb.STBVorbis;
|
||||
import org.lwjgl.stb.STBVorbisInfo;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class MemoryPCMClip extends RandomAccessClip
|
||||
{
|
||||
private final ShortBuffer pcmAudio;
|
||||
|
||||
MemoryPCMClip(Path path) throws IOException
|
||||
{
|
||||
long handle = MemoryUtil.NULL;
|
||||
ByteBuffer audioBytes = null;
|
||||
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
audioBytes = AudioLoader.loadIntoMemory(path);
|
||||
|
||||
IntBuffer error = stack.mallocInt(1);
|
||||
handle = STBVorbis.stb_vorbis_open_memory(audioBytes, error, null);
|
||||
|
||||
if (handle == MemoryUtil.NULL)
|
||||
{
|
||||
this.close();
|
||||
throw new IOException(String.format("Failed to load '%s', error code %d.\n", path, error.get(0)));
|
||||
}
|
||||
|
||||
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
|
||||
STBVorbis.stb_vorbis_get_info(handle, info);
|
||||
|
||||
this.channels = info.channels();
|
||||
this.sampleRate = info.sample_rate();
|
||||
|
||||
// Downmix to mono, SOUNDS HORRIBLE
|
||||
//
|
||||
// this.channels = 1;
|
||||
// this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
|
||||
// this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
|
||||
// var ptr = stack.pointers(this.pcmAudio);
|
||||
// STBVorbis.stb_vorbis_get_samples_short(handle, ptr, this.samplesLength);
|
||||
|
||||
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
|
||||
this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
|
||||
STBVorbis.stb_vorbis_get_samples_short_interleaved(handle, this.channels, this.pcmAudio);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
this.close();
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemoryUtil.memFree(audioBytes);
|
||||
|
||||
if (handle != MemoryUtil.NULL)
|
||||
{
|
||||
STBVorbis.stb_vorbis_close(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLengthInSamples()
|
||||
{
|
||||
return this.samplesLength / this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSamples(ShortBuffer pcm, int offset, boolean loopRead)
|
||||
{
|
||||
int readTotal = 0;
|
||||
int read;
|
||||
|
||||
do
|
||||
{
|
||||
int thisRemaining = this.pcmAudio.limit() - offset;
|
||||
read = Math.min(pcm.remaining() - readTotal, thisRemaining);
|
||||
pcm.put(pcm.position() + readTotal, this.pcmAudio, offset, read);
|
||||
readTotal += read;
|
||||
|
||||
offset = (offset + read) % (this.getLengthInSamples() * this.channels);
|
||||
}
|
||||
while (loopRead && pcm.limit() - readTotal > 0);
|
||||
|
||||
return readTotal / this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
MemoryUtil.memFree(this.pcmAudio);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
public abstract class RandomAccessClip extends Track implements IRandomAccessAudio, IClip
|
||||
{
|
||||
protected int samplesLength;
|
||||
|
||||
@Override
|
||||
public int getLengthInSamples()
|
||||
{
|
||||
return this.samplesLength;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
public abstract class SeekableTrack extends Track implements ISeekableTrack
|
||||
{
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.plutoengine.audio;
|
||||
|
||||
abstract class Track implements IAudio
|
||||
{
|
||||
protected int channels;
|
||||
protected int sampleRate;
|
||||
|
||||
@Override
|
||||
public int getChannels()
|
||||
{
|
||||
return this.channels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleRate()
|
||||
{
|
||||
return this.sampleRate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
class AudioBuffer implements AutoCloseable
|
||||
{
|
||||
private final int id;
|
||||
private final int format;
|
||||
private final int sampleRate;
|
||||
|
||||
AudioBuffer(int id, int format, int sampleRate)
|
||||
{
|
||||
this.id = id;
|
||||
this.format = format;
|
||||
this.sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
public int getID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void writeData(ShortBuffer data)
|
||||
{
|
||||
AL10.alBufferData(this.id, this.format, data, this.sampleRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
AL10.alDeleteBuffers(this.id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.plutoengine.audio.RandomAccessClip;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public class AudioClipSource extends AudioDoubleBufferedSource
|
||||
{
|
||||
private final RandomAccessClip clip;
|
||||
|
||||
private final boolean looping;
|
||||
|
||||
private int readHead = 0;
|
||||
|
||||
public AudioClipSource(RandomAccessClip clip, boolean looping)
|
||||
{
|
||||
super(clip);
|
||||
|
||||
this.clip = clip;
|
||||
this.looping = looping;
|
||||
}
|
||||
|
||||
public AudioClipSource(RandomAccessClip clip)
|
||||
{
|
||||
this(clip, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSamples(ShortBuffer pcmTransferBuf)
|
||||
{
|
||||
var read = this.clip.getSamples(pcmTransferBuf, this.readHead * this.channels, this.looping);
|
||||
this.readHead += read / this.channels;
|
||||
|
||||
if (this.looping)
|
||||
this.readHead %= this.clip.getLengthInSamples();
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
public boolean isLooping()
|
||||
{
|
||||
return this.looping;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.joml.Matrix4x3f;
|
||||
import org.joml.Matrix4x3fc;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3fc;
|
||||
import org.lwjgl.openal.*;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.plutoengine.Pluto;
|
||||
import org.plutoengine.component.PlutoLocalComponent;
|
||||
import org.plutoengine.logger.Logger;
|
||||
import org.plutoengine.logger.SmartSeverity;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class AudioContext extends PlutoLocalComponent
|
||||
{
|
||||
private long device = MemoryUtil.NULL;
|
||||
private long context = MemoryUtil.NULL;
|
||||
private ALCapabilities capabilities;
|
||||
|
||||
private Matrix4x3fc transformation;
|
||||
|
||||
AudioContext()
|
||||
{
|
||||
this.transformation = new Matrix4x3f();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMount(ComponentDependencyManager manager)
|
||||
{
|
||||
var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null);
|
||||
if (devicePtr == MemoryUtil.NULL)
|
||||
{
|
||||
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to open the default audio device.");
|
||||
|
||||
// No audio device found, but the game should not crash just because we have no audio
|
||||
return;
|
||||
}
|
||||
|
||||
this.device = devicePtr;
|
||||
|
||||
var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null);
|
||||
if (contextPtr == MemoryUtil.NULL)
|
||||
{
|
||||
ALC10.alcCloseDevice(devicePtr);
|
||||
|
||||
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to create an OpenAL context.");
|
||||
|
||||
// The game should not crash just because we have no audio
|
||||
return;
|
||||
}
|
||||
|
||||
this.context = contextPtr;
|
||||
|
||||
EXTThreadLocalContext.alcSetThreadContext(contextPtr);
|
||||
|
||||
ALCCapabilities deviceCaps = ALC.createCapabilities(devicePtr);
|
||||
var alCapabilities = AL.createCapabilities(deviceCaps);
|
||||
|
||||
if (Pluto.DEBUG_MODE)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO, "OpenAL10: %b\n", alCapabilities.OpenAL10);
|
||||
Logger.logf(SmartSeverity.AUDIO, "OpenAL11: %b\n", alCapabilities.OpenAL11);
|
||||
Logger.logf(SmartSeverity.AUDIO, "Distance model: %s\n", EnumDistanceModel.getByID(AL11.alGetInteger(AL11.AL_DISTANCE_MODEL)));
|
||||
}
|
||||
|
||||
this.capabilities = alCapabilities;
|
||||
|
||||
Logger.log(SmartSeverity.AUDIO_PLUS, "Audio engine started.");
|
||||
}
|
||||
|
||||
public void setTransformation(Matrix4x3fc transformation)
|
||||
{
|
||||
this.transformation = transformation;
|
||||
}
|
||||
|
||||
public Vector3fc transform(Vector3fc vector)
|
||||
{
|
||||
return this.transformation.transformPosition(vector, new Vector3f());
|
||||
}
|
||||
|
||||
public void setDistanceModel(EnumDistanceModel model)
|
||||
{
|
||||
AL10.alDistanceModel(model.getALID());
|
||||
}
|
||||
|
||||
public void setSpeedOfSound(float speedOfSound)
|
||||
{
|
||||
AL11.alSpeedOfSound(speedOfSound);
|
||||
}
|
||||
|
||||
public void setSpeed(Vector3fc speed)
|
||||
{
|
||||
var tSpeed = this.transformation.transformPosition(speed, new Vector3f());
|
||||
AL10.alListener3f(AL10.AL_VELOCITY, tSpeed.x(), tSpeed.y(), tSpeed.z());
|
||||
}
|
||||
|
||||
public void setPosition(Vector3f position)
|
||||
{
|
||||
var tPosition = this.transformation.transformPosition(position, new Vector3f());
|
||||
AL10.alListener3f(AL10.AL_POSITION, tPosition.x(), tPosition.y(), tPosition.z());
|
||||
}
|
||||
|
||||
public void setVolume(float volume)
|
||||
{
|
||||
AL10.alListenerf(AL10.AL_GAIN, volume);
|
||||
}
|
||||
|
||||
public void setOrientation(Vector3f at, Vector3f up)
|
||||
{
|
||||
float[] data = new float[6];
|
||||
data[0] = at.x;
|
||||
data[1] = at.y;
|
||||
data[2] = at.z;
|
||||
data[3] = up.x;
|
||||
data[4] = up.y;
|
||||
data[5] = up.z;
|
||||
AL10.alListenerfv(AL10.AL_ORIENTATION, data);
|
||||
}
|
||||
|
||||
public boolean isReady()
|
||||
{
|
||||
return this.capabilities != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUnmount()
|
||||
{
|
||||
Logger.log(SmartSeverity.AUDIO_MINUS, "Shutting down the audio engine.");
|
||||
|
||||
EXTThreadLocalContext.alcSetThreadContext(MemoryUtil.NULL);
|
||||
|
||||
ALC10.alcDestroyContext(this.context);
|
||||
ALC10.alcCloseDevice(this.device);
|
||||
|
||||
this.context = MemoryUtil.NULL;
|
||||
this.device = MemoryUtil.NULL;
|
||||
this.capabilities = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnique()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.SOFTDirectChannels;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.plutoengine.audio.IAudio;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
abstract class AudioDoubleBufferedSource extends AudioSource
|
||||
{
|
||||
private static final int BUFFER_SIZE_PER_CHANNEL = 8192;
|
||||
private static final int DOUBLE_BUFFER = 2;
|
||||
|
||||
protected final int format;
|
||||
|
||||
protected final int channels;
|
||||
|
||||
protected final int sampleRate;
|
||||
|
||||
private final IntBuffer bufferIDs;
|
||||
protected final Map<Integer, AudioBuffer> buffers;
|
||||
|
||||
protected final ShortBuffer pcmTransferBuf;
|
||||
|
||||
protected boolean audioBufferDepleted;
|
||||
|
||||
protected boolean closed;
|
||||
|
||||
protected AudioDoubleBufferedSource(IAudio audio)
|
||||
{
|
||||
this.format = switch (audio.getChannels()) {
|
||||
case 1 -> AL10.AL_FORMAT_MONO16;
|
||||
case 2 -> AL10.AL_FORMAT_STEREO16;
|
||||
default -> throw new UnsupportedOperationException("Unsupported number of channels: " + audio.getChannels());
|
||||
};
|
||||
|
||||
this.channels = audio.getChannels();
|
||||
this.sampleRate = audio.getSampleRate();
|
||||
|
||||
int bufferSize = audio.getChannels() * BUFFER_SIZE_PER_CHANNEL;
|
||||
|
||||
this.pcmTransferBuf = MemoryUtil.memAllocShort(bufferSize);
|
||||
|
||||
this.bufferIDs = MemoryUtil.memCallocInt(DOUBLE_BUFFER);
|
||||
AL10.alGenBuffers(this.bufferIDs);
|
||||
this.buffers = IntStream.range(0, this.bufferIDs.limit())
|
||||
.mapToObj(i -> new AudioBuffer(this.bufferIDs.get(i), this.format, this.sampleRate))
|
||||
.collect(Collectors.toMap(AudioBuffer::getID, Function.identity(), (l, r) -> l));
|
||||
|
||||
AL10.alSourcei(this.id, SOFTDirectChannels.AL_DIRECT_CHANNELS_SOFT, AL10.AL_TRUE);
|
||||
}
|
||||
|
||||
public boolean play()
|
||||
{
|
||||
if (this.closed)
|
||||
return false;
|
||||
|
||||
var state = AL10.alGetSourcei(this.id, AL10.AL_SOURCE_STATE);
|
||||
|
||||
return switch (state)
|
||||
{
|
||||
case AL10.AL_PLAYING -> true;
|
||||
case AL10.AL_PAUSED, AL10.AL_STOPPED -> super.play();
|
||||
case AL10.AL_INITIAL -> {
|
||||
this.buffers.values()
|
||||
.forEach(this::stream);
|
||||
|
||||
yield super.play();
|
||||
}
|
||||
default -> false; // Unexpected value, just say it didn't play
|
||||
};
|
||||
}
|
||||
|
||||
private void stream(AudioBuffer buffer)
|
||||
{
|
||||
if (this.audioBufferDepleted)
|
||||
return;
|
||||
|
||||
this.fillTransferBuffer();
|
||||
|
||||
if (this.audioBufferDepleted)
|
||||
return;
|
||||
|
||||
buffer.writeData(this.pcmTransferBuf);
|
||||
AL10.alSourceQueueBuffers(this.id, buffer.getID());
|
||||
}
|
||||
|
||||
private void fillTransferBuffer()
|
||||
{
|
||||
this.pcmTransferBuf.clear();
|
||||
int samplesPerChannel = this.getSamples(this.pcmTransferBuf);
|
||||
|
||||
if (samplesPerChannel < BUFFER_SIZE_PER_CHANNEL)
|
||||
{
|
||||
this.audioBufferDepleted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var samples = samplesPerChannel * this.channels;
|
||||
this.pcmTransferBuf.limit(samples);
|
||||
}
|
||||
|
||||
protected abstract int getSamples(ShortBuffer pcmTransferBuf);
|
||||
|
||||
protected List<AudioBuffer> unqueueBuffers()
|
||||
{
|
||||
int processed = AL10.alGetSourcei(this.id, AL10.AL_BUFFERS_PROCESSED);
|
||||
var unqueued = new ArrayList<AudioBuffer>(DOUBLE_BUFFER);
|
||||
|
||||
for (int i = 0; i < processed; i++)
|
||||
{
|
||||
int bufID = AL10.alSourceUnqueueBuffers(this.id);
|
||||
var buffer = this.buffers.get(bufID);
|
||||
unqueued.add(buffer);
|
||||
}
|
||||
|
||||
return unqueued;
|
||||
|
||||
}
|
||||
|
||||
public boolean update()
|
||||
{
|
||||
if (this.isClosed())
|
||||
return false;
|
||||
|
||||
var unqueued = this.unqueueBuffers();
|
||||
unqueued.forEach(this::stream);
|
||||
|
||||
if (AL10.alGetSourcei(this.id, AL10.AL_SOURCE_STATE) == AL10.AL_STOPPED)
|
||||
{
|
||||
if (this.audioBufferDepleted)
|
||||
return false;
|
||||
|
||||
if (unqueued.size() == DOUBLE_BUFFER)
|
||||
return super.play();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean updateOrClose()
|
||||
{
|
||||
boolean shouldClose = !this.update();
|
||||
|
||||
if (shouldClose)
|
||||
this.close();
|
||||
|
||||
return shouldClose;
|
||||
}
|
||||
|
||||
public boolean isClosed()
|
||||
{
|
||||
return this.closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
if (this.isClosed())
|
||||
return;
|
||||
|
||||
this.closed = true;
|
||||
|
||||
this.stop();
|
||||
|
||||
this.unqueueBuffers();
|
||||
|
||||
super.close();
|
||||
|
||||
this.buffers.values()
|
||||
.forEach(AudioBuffer::close);
|
||||
|
||||
MemoryUtil.memFree(this.pcmTransferBuf);
|
||||
}
|
||||
}
|
|
@ -1,122 +1,78 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.openal.*;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.plutoengine.Pluto;
|
||||
import org.plutoengine.component.AbstractComponent;
|
||||
import org.plutoengine.component.ComponentToken;
|
||||
import org.plutoengine.component.PlutoLocalComponent;
|
||||
import org.plutoengine.logger.Logger;
|
||||
import org.plutoengine.logger.SmartSeverity;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class AudioEngine extends PlutoLocalComponent
|
||||
{
|
||||
private long device = MemoryUtil.NULL;
|
||||
private long context = MemoryUtil.NULL;
|
||||
private ALCapabilities capabilities;
|
||||
|
||||
public static final ComponentToken<AudioEngine> TOKEN = ComponentToken.create(AudioEngine::new);
|
||||
|
||||
private AudioContext context;
|
||||
|
||||
private final List<Pair<AudioClipSource, AudioSourceInfo>> sfx;
|
||||
|
||||
|
||||
private AudioEngine()
|
||||
{
|
||||
|
||||
this.sfx = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMount(ComponentDependencyManager manager)
|
||||
protected void onMount(AbstractComponent<PlutoLocalComponent>.ComponentDependencyManager manager)
|
||||
{
|
||||
var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null);
|
||||
if (devicePtr == MemoryUtil.NULL)
|
||||
this.context = manager.declareDependency(ComponentToken.create(AudioContext::new));
|
||||
}
|
||||
|
||||
public void update()
|
||||
{
|
||||
for (var iterator = this.sfx.listIterator(); iterator.hasNext(); )
|
||||
{
|
||||
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to open the default audio device.");
|
||||
var data = iterator.next();
|
||||
var source = data.getKey();
|
||||
var info = data.getValue();
|
||||
|
||||
// No audio device found, but the game should not crash just because we have no audio
|
||||
return;
|
||||
var kaFunc = info.keepAliveFunction();
|
||||
if (kaFunc != null && !kaFunc.getAsBoolean())
|
||||
source.close();
|
||||
|
||||
var moveFunc = info.moveFunction();
|
||||
if (!source.isClosed() && moveFunc != null)
|
||||
{
|
||||
var prevPos = source.getPosition();
|
||||
var newPos = moveFunc.apply(prevPos);
|
||||
var velocity = newPos.sub(prevPos, new Vector3f());
|
||||
|
||||
source.position(this.context, newPos);
|
||||
source.velocity(this.context, velocity);
|
||||
}
|
||||
|
||||
if (source.updateOrClose())
|
||||
iterator.remove();
|
||||
}
|
||||
|
||||
this.device = devicePtr;
|
||||
|
||||
var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null);
|
||||
if (contextPtr == MemoryUtil.NULL)
|
||||
{
|
||||
ALC10.alcCloseDevice(devicePtr);
|
||||
|
||||
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to create an OpenAL context.");
|
||||
|
||||
// The game should not crash just because we have no audio
|
||||
return;
|
||||
}
|
||||
|
||||
this.context = contextPtr;
|
||||
|
||||
EXTThreadLocalContext.alcSetThreadContext(contextPtr);
|
||||
|
||||
ALCCapabilities deviceCaps = ALC.createCapabilities(devicePtr);
|
||||
var alCapabilities = AL.createCapabilities(deviceCaps);
|
||||
|
||||
if (Pluto.DEBUG_MODE)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO, "OpenAL10: %b\n", alCapabilities.OpenAL10);
|
||||
Logger.logf(SmartSeverity.AUDIO, "OpenAL11: %b\n", alCapabilities.OpenAL11);
|
||||
}
|
||||
|
||||
this.capabilities = alCapabilities;
|
||||
|
||||
Logger.log(SmartSeverity.AUDIO_PLUS, "Audio engine started.");
|
||||
}
|
||||
|
||||
public void setSpeed(Vector3f speed)
|
||||
public void playSound(SoundEffect sfx)
|
||||
{
|
||||
AL10.alListener3f(AL10.AL_VELOCITY, speed.x, speed.y, speed.z);
|
||||
}
|
||||
|
||||
public void setPosition(Vector3f position)
|
||||
{
|
||||
AL10.alListener3f(AL10.AL_POSITION, position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
public void setVolume(float volume)
|
||||
{
|
||||
AL10.alListenerf(AL10.AL_GAIN, volume);
|
||||
}
|
||||
|
||||
public void setOrientation(Vector3f at, Vector3f up)
|
||||
{
|
||||
float[] data = new float[6];
|
||||
data[0] = at.x;
|
||||
data[1] = at.y;
|
||||
data[2] = at.z;
|
||||
data[3] = up.x;
|
||||
data[4] = up.y;
|
||||
data[5] = up.z;
|
||||
AL10.alListenerfv(AL10.AL_ORIENTATION, data);
|
||||
}
|
||||
|
||||
public boolean isReady()
|
||||
{
|
||||
return this.capabilities != null;
|
||||
var soundEffect = new AudioClipSource(sfx.getClip());
|
||||
soundEffect.volume(sfx.getVolume());
|
||||
soundEffect.pitch(sfx.getPitch());
|
||||
soundEffect.position(this.context, sfx.getPosition());
|
||||
soundEffect.play();
|
||||
var info = new AudioSourceInfo(sfx.getMovementMapper(), sfx.getKeepAliveFunction());
|
||||
var data = Pair.of(soundEffect, info);
|
||||
this.sfx.add(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUnmount()
|
||||
{
|
||||
Logger.log(SmartSeverity.AUDIO_MINUS, "Shutting down the audio engine.");
|
||||
|
||||
EXTThreadLocalContext.alcSetThreadContext(MemoryUtil.NULL);
|
||||
|
||||
ALC10.alcDestroyContext(this.context);
|
||||
ALC10.alcCloseDevice(this.device);
|
||||
|
||||
this.context = MemoryUtil.NULL;
|
||||
this.device = MemoryUtil.NULL;
|
||||
this.capabilities = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -124,4 +80,9 @@ public class AudioEngine extends PlutoLocalComponent
|
|||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public AudioContext getContext()
|
||||
{
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +1,73 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.jetbrains.annotations.MustBeInvokedByOverriders;
|
||||
import org.joml.Vector3fc;
|
||||
import org.lwjgl.openal.AL10;
|
||||
|
||||
public abstract class AudioSource implements AutoCloseable
|
||||
{
|
||||
protected final int source;
|
||||
protected final int id;
|
||||
protected Vector3fc position;
|
||||
|
||||
protected AudioSource()
|
||||
{
|
||||
this.source = AL10.alGenSources();
|
||||
this.id = AL10.alGenSources();
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
public boolean play()
|
||||
{
|
||||
AL10.alSourcePlay(this.id);
|
||||
return true;
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
public void pause()
|
||||
{
|
||||
AL10.alSourcePause(this.id);
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
public void stop()
|
||||
{
|
||||
AL10.alSourceStop(this.source);
|
||||
AL10.alSourceStop(this.id);
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
public void close()
|
||||
{
|
||||
this.stop();
|
||||
AL10.alDeleteSources(this.source);
|
||||
AL10.alDeleteSources(this.id);
|
||||
}
|
||||
|
||||
public void position(Vector3fc pos)
|
||||
@MustBeInvokedByOverriders
|
||||
public void position(AudioContext context, Vector3fc pos)
|
||||
{
|
||||
AL10.alSource3f(this.source, AL10.AL_POSITION, pos.x(), pos.y(), pos.z());
|
||||
this.position = pos;
|
||||
var tPos = context.transform(pos);
|
||||
AL10.alSource3f(this.id, AL10.AL_POSITION, tPos.x(), tPos.y(), tPos.z());
|
||||
}
|
||||
|
||||
public void velocity(Vector3fc velocity)
|
||||
public Vector3fc getPosition()
|
||||
{
|
||||
AL10.alSource3f(this.source, AL10.AL_VELOCITY, velocity.x(), velocity.y(), velocity.z());
|
||||
return this.position;
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
public void velocity(AudioContext context, Vector3fc velocity)
|
||||
{
|
||||
var tVelocity = context.transform(velocity);
|
||||
AL10.alSource3f(this.id, AL10.AL_VELOCITY, tVelocity.x(), tVelocity.y(), tVelocity.z());
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
public void pitch(float f)
|
||||
{
|
||||
AL10.alSourcef(this.source, AL10.AL_PITCH, f);
|
||||
AL10.alSourcef(this.id, AL10.AL_PITCH, f);
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
public void volume(float f)
|
||||
{
|
||||
AL10.alSourcef(this.source, AL10.AL_GAIN, f);
|
||||
AL10.alSourcef(this.id, AL10.AL_GAIN, f);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.joml.Vector3fc;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
public record AudioSourceInfo(
|
||||
UnaryOperator<Vector3fc> moveFunction,
|
||||
BooleanSupplier keepAliveFunction
|
||||
)
|
||||
{
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.SOFTDirectChannels;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.plutoengine.audio.IAudioStream;
|
||||
import org.plutoengine.audio.ISeekableAudioTrack;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public class AudioTrack extends AudioSource
|
||||
{
|
||||
private static final int BUFFER_SIZE_PER_CHANNEL = 16384;
|
||||
|
||||
private final IAudioStream track;
|
||||
|
||||
private final int format;
|
||||
|
||||
private static final int DOUBLE_BUFFER = 2;
|
||||
|
||||
private final int[] buffers;
|
||||
|
||||
private boolean closeOnFinish;
|
||||
|
||||
private final ShortBuffer pcm;
|
||||
|
||||
public AudioTrack(IAudioStream track)
|
||||
{
|
||||
this.track = track;
|
||||
|
||||
this.format = switch (track.getChannels()) {
|
||||
case 1 -> AL10.AL_FORMAT_MONO16;
|
||||
case 2 -> AL10.AL_FORMAT_STEREO16;
|
||||
default -> throw new UnsupportedOperationException("Unsupported number of channels: " + track.getChannels());
|
||||
};
|
||||
|
||||
int bufferSize = track.getChannels() * BUFFER_SIZE_PER_CHANNEL;
|
||||
|
||||
this.pcm = MemoryUtil.memAllocShort(bufferSize);
|
||||
|
||||
this.buffers = new int[DOUBLE_BUFFER];
|
||||
AL10.alGenBuffers(this.buffers);
|
||||
|
||||
AL10.alSourcei(this.source, SOFTDirectChannels.AL_DIRECT_CHANNELS_SOFT, AL10.AL_TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
AL10.alDeleteBuffers(this.buffers);
|
||||
AL10.alDeleteSources(this.source);
|
||||
|
||||
MemoryUtil.memFree(this.pcm);
|
||||
}
|
||||
|
||||
public boolean play()
|
||||
{
|
||||
if (this.track instanceof ISeekableAudioTrack seekableAudioTrack)
|
||||
seekableAudioTrack.rewind();
|
||||
|
||||
for (int buf : this.buffers)
|
||||
{
|
||||
this.stream(buf);
|
||||
}
|
||||
|
||||
AL10.alSourcePlay(this.source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setCloseOnFinish()
|
||||
{
|
||||
this.closeOnFinish = true;
|
||||
}
|
||||
|
||||
private void stream(int buffer)
|
||||
{
|
||||
this.pcm.clear();
|
||||
int samplesPerChannel = this.track.getSamples(this.pcm);
|
||||
|
||||
if (samplesPerChannel == 0)
|
||||
return;
|
||||
|
||||
var samples = samplesPerChannel * this.track.getChannels();
|
||||
this.pcm.limit(samples);
|
||||
AL10.alBufferData(buffer, this.format, this.pcm, this.track.getSampleRate());
|
||||
AL10.alSourceQueueBuffers(this.source, buffer);
|
||||
}
|
||||
|
||||
public boolean update()
|
||||
{
|
||||
int processed = AL10.alGetSourcei(this.source, AL10.AL_BUFFERS_PROCESSED);
|
||||
|
||||
for (int i = 0; i < processed; i++)
|
||||
{
|
||||
int buffer = AL10.alSourceUnqueueBuffers(this.source);
|
||||
this.stream(buffer);
|
||||
}
|
||||
|
||||
if (AL10.alGetSourcei(this.source, AL10.AL_SOURCE_STATE) == AL10.AL_STOPPED)
|
||||
{
|
||||
if (this.closeOnFinish)
|
||||
{
|
||||
this.close();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.plutoengine.audio.ISeekableTrack;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
public class AudioTrackSource extends AudioDoubleBufferedSource
|
||||
{
|
||||
private final ISeekableTrack track;
|
||||
|
||||
public AudioTrackSource(ISeekableTrack track)
|
||||
{
|
||||
super(track);
|
||||
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
public boolean play()
|
||||
{
|
||||
this.track.rewind();
|
||||
|
||||
return super.play();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSamples(ShortBuffer pcmTransferBuf)
|
||||
{
|
||||
return this.track.getSamples(pcmTransferBuf);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.lwjgl.openal.AL11;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum EnumDistanceModel implements IOpenALEnum
|
||||
{
|
||||
NONE(AL11.AL_NONE),
|
||||
INVERSE_DISTANCE(AL11.AL_INVERSE_DISTANCE),
|
||||
INVERSE_DISTANCE_CLAMPED(AL11.AL_INVERSE_DISTANCE_CLAMPED),
|
||||
LINEAR_DISTANCE(AL11.AL_LINEAR_DISTANCE),
|
||||
LINEAR_DISTANCE_CLAMPED(AL11.AL_LINEAR_DISTANCE_CLAMPED),
|
||||
EXPONENT_DISTANCE(AL11.AL_EXPONENT_DISTANCE),
|
||||
EXPONENT_DISTANCE_CLAMPED(AL11.AL_EXPONENT_DISTANCE_CLAMPED);
|
||||
|
||||
private final int alID;
|
||||
|
||||
EnumDistanceModel(int alID)
|
||||
{
|
||||
this.alID = alID;
|
||||
}
|
||||
|
||||
public static EnumDistanceModel getByID(int id)
|
||||
{
|
||||
return Arrays.stream(EnumDistanceModel.values()).filter(model -> model.getALID() == id).findAny().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getALID()
|
||||
{
|
||||
return this.alID;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
public interface IOpenALEnum
|
||||
{
|
||||
int getALID();
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package org.plutoengine.audio.al;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joml.Vector3fc;
|
||||
import org.plutoengine.audio.RandomAccessClip;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
public class SoundEffect
|
||||
{
|
||||
private final @NotNull RandomAccessClip clip;
|
||||
private @NotNull Vector3fc position;
|
||||
private float volume;
|
||||
private float pitch;
|
||||
private UnaryOperator<Vector3fc> movementMapper;
|
||||
private BooleanSupplier keepAliveFunction;
|
||||
|
||||
public SoundEffect(@NotNull RandomAccessClip soundEffect, @NotNull Vector3fc position, float volume)
|
||||
{
|
||||
this.clip = soundEffect;
|
||||
this.position = position;
|
||||
this.volume = volume;
|
||||
this.pitch = 1.0f;
|
||||
}
|
||||
|
||||
public SoundEffect position(Vector3fc position)
|
||||
{
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SoundEffect volume(float volume)
|
||||
{
|
||||
this.volume = volume;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SoundEffect pitch(float pitch)
|
||||
{
|
||||
this.pitch = pitch;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SoundEffect movementMapper(UnaryOperator<Vector3fc> movementMapper)
|
||||
{
|
||||
this.movementMapper = movementMapper;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SoundEffect keepAliveFunction(BooleanSupplier keepAliveFunction)
|
||||
{
|
||||
this.keepAliveFunction = keepAliveFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull RandomAccessClip getClip()
|
||||
{
|
||||
return this.clip;
|
||||
}
|
||||
|
||||
@NotNull Vector3fc getPosition()
|
||||
{
|
||||
return this.position;
|
||||
}
|
||||
|
||||
float getVolume()
|
||||
{
|
||||
return this.volume;
|
||||
}
|
||||
|
||||
float getPitch()
|
||||
{
|
||||
return this.pitch;
|
||||
}
|
||||
|
||||
UnaryOperator<Vector3fc> getMovementMapper()
|
||||
{
|
||||
return this.movementMapper;
|
||||
}
|
||||
|
||||
BooleanSupplier getKeepAliveFunction()
|
||||
{
|
||||
return this.keepAliveFunction;
|
||||
}
|
||||
}
|
|
@ -281,6 +281,7 @@ public abstract class PlutoApplication
|
|||
|
||||
this.display.swapBuffers();
|
||||
|
||||
audioEngine.update();
|
||||
inputBus.resetStates();
|
||||
|
||||
this.display.pollEvents();
|
||||
|
|
|
@ -14,10 +14,13 @@ dependencies {
|
|||
api("org.lwjgl", "lwjgl-glfw")
|
||||
api("org.lwjgl", "lwjgl-opengl")
|
||||
api("org.lwjgl", "lwjgl-stb")
|
||||
runtimeOnly("org.lwjgl", "lwjgl", classifier = Versions.lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = Versions.lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = Versions.lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = Versions.lwjglNatives)
|
||||
|
||||
org.plutoengine.Versions.lwjglNatives.forEach {
|
||||
runtimeOnly("org.lwjgl", "lwjgl", classifier = it)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = it)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = it)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = it)
|
||||
}
|
||||
|
||||
api("com.code-disaster.steamworks4j", "steamworks4j", Versions.steamworks4jVersion)
|
||||
api("com.code-disaster.steamworks4j", "steamworks4j-server", Versions.steamworks4jServerVersion)
|
||||
|
|
|
@ -16,7 +16,7 @@ public class Framerate
|
|||
|
||||
private static double animationTimer = 0;
|
||||
|
||||
private static double FPS = Double.NaN;
|
||||
private static double fps = Double.NaN;
|
||||
|
||||
private static int interpolatedFPS;
|
||||
|
||||
|
@ -31,7 +31,7 @@ public class Framerate
|
|||
|
||||
public static double getFPS()
|
||||
{
|
||||
return FPS;
|
||||
return fps;
|
||||
}
|
||||
|
||||
public static int getInterpolatedFPS()
|
||||
|
@ -55,7 +55,7 @@ public class Framerate
|
|||
animationTimer += frameTimeNs / (double) TimeUnit.SECONDS.toNanos(1);
|
||||
// Maintain precision in case the engine runs for many hours
|
||||
animationTimer %= TimeUnit.DAYS.toMinutes(1);
|
||||
FPS = TimeUnit.SECONDS.toMillis(1) / frameTime;
|
||||
fps = TimeUnit.SECONDS.toMillis(1) / frameTime;
|
||||
}
|
||||
|
||||
var nowMs = System.currentTimeMillis();
|
||||
|
@ -77,7 +77,7 @@ public class Framerate
|
|||
}
|
||||
else
|
||||
{
|
||||
interpolatedFPS = (int) Math.round(FPS);
|
||||
interpolatedFPS = (int) Math.round(fps);
|
||||
}
|
||||
|
||||
lastDraw = now;
|
||||
|
|
|
@ -14,7 +14,9 @@ dependencies {
|
|||
implementation("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", "2.12.3")
|
||||
|
||||
implementation("org.lwjgl", "lwjgl-yoga")
|
||||
runtimeOnly("org.lwjgl", "lwjgl-yoga", classifier = org.plutoengine.Versions.lwjglNatives)
|
||||
org.plutoengine.Versions.lwjglNatives.forEach {
|
||||
runtimeOnly("org.lwjgl", "lwjgl-yoga", classifier = it)
|
||||
}
|
||||
|
||||
implementation("org.commonmark", "commonmark", "0.18.1")
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.plutoengine.graphics;
|
||||
|
||||
import org.plutoengine.Pluto;
|
||||
import org.plutoengine.graphics.gui.BitmapFontShader;
|
||||
import org.plutoengine.graphics.gui.FontShader;
|
||||
import org.plutoengine.graphics.texture.MagFilter;
|
||||
import org.plutoengine.graphics.texture.MinFilter;
|
||||
|
@ -28,7 +29,7 @@ public class PlutoGUIMod implements IModEntryPoint
|
|||
|
||||
public static FontShader fontShader;
|
||||
|
||||
public static FontShader bitmapFontShader;
|
||||
public static BitmapFontShader bitmapFontShader;
|
||||
|
||||
public void onLoad(Mod mod)
|
||||
{
|
||||
|
@ -36,7 +37,7 @@ public class PlutoGUIMod implements IModEntryPoint
|
|||
|
||||
fontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexFontShader#glsl"), mod.getResource("shaders.FragmentFontShader#glsl")).build(FontShader.class, false);
|
||||
|
||||
bitmapFontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexBitmapFontShader#glsl"), mod.getResource("shaders.FragmentBitmapFontShader#glsl")).build(FontShader.class, false);
|
||||
bitmapFontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexBitmapFontShader#glsl"), mod.getResource("shaders.FragmentBitmapFontShader#glsl")).build(BitmapFontShader.class, false);
|
||||
|
||||
uiElementsAtlas = new RectangleTexture();
|
||||
uiElementsAtlas.load(mod.getResource("gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.plutoengine.shader.uniform.auto.AutoViewportProjection;
|
|||
import org.plutoengine.util.color.IRGBA;
|
||||
|
||||
@ShaderProgram
|
||||
public final class BitmapTextShader extends ShaderBase implements IGUIShader
|
||||
public final class BitmapFontShader extends ShaderBase implements IGUIShader
|
||||
{
|
||||
@AutoViewportProjection
|
||||
@Uniform(name = "projection")
|
|
@ -38,7 +38,10 @@ dependencies {
|
|||
implementation("org.lwjgl:lwjgl")
|
||||
implementation("org.lwjgl:lwjgl-xxhash")
|
||||
implementation("org.lwjgl:lwjgl-zstd")
|
||||
runtimeOnly("org.lwjgl", "lwjgl", classifier = Versions.lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-xxhash", classifier = Versions.lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-zstd", classifier = Versions.lwjglNatives)
|
||||
|
||||
Versions.lwjglNatives.forEach {
|
||||
runtimeOnly("org.lwjgl", "lwjgl", classifier = it)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-xxhash", classifier = it)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-zstd", classifier = it)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
plugins {
|
||||
id("edu.sc.seis.launch4j") version "2.5.3"
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("cz.tefek.srclone.Main")
|
||||
}
|
||||
|
||||
launch4j {
|
||||
mainClassName = "cz.tefek.srclone.Main"
|
||||
bundledJrePath = "jre"
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
tasks.withType<JavaExec> {
|
||||
jvmArgs = listOf(
|
||||
"-Dcz.tefek.pluto.debug=true",
|
||||
"-Dorg.lwjgl.util.Debug=true"
|
||||
)
|
||||
}
|
||||
|
||||
distributions {
|
||||
main {
|
||||
contents {
|
||||
from("mods") {
|
||||
into("mods")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":plutoengine:plutocore"))
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"displayName": "GLFW",
|
||||
"author": "The GLFW team",
|
||||
"description": "The GLFW library, used for native window creation.",
|
||||
"resourceRoots": {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"displayName": "LWJGL",
|
||||
"description": "Lightweight Java Game Library",
|
||||
"author": "The LWJGL team",
|
||||
"resourceRoots": {
|
||||
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,75 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 uvCoordinates;
|
||||
in vec2 paintUVCoordinates;
|
||||
|
||||
uniform sampler2DRect textureSampler;
|
||||
|
||||
uniform int paintType;
|
||||
uniform vec4 paintColor;
|
||||
|
||||
uniform int paintGradientStopCount;
|
||||
uniform vec4[16] paintGradientColors;
|
||||
uniform float[16] paintGradientPositions;
|
||||
uniform vec2[2] paintGradientEnds;
|
||||
|
||||
out vec4 out_Color;
|
||||
|
||||
vec4 solidColor(void)
|
||||
{
|
||||
return paintColor;
|
||||
}
|
||||
|
||||
vec4 gammaMix(vec4 lCol, vec4 rCol, float ratio)
|
||||
{
|
||||
float gamma = 2.2;
|
||||
float one_over_gamma = 1 / gamma;
|
||||
|
||||
vec4 ilCol = vec4(pow(lCol.r, gamma), pow(lCol.g, gamma), pow(lCol.b, gamma), lCol.a);
|
||||
vec4 irCol = vec4(pow(rCol.r, gamma), pow(rCol.g, gamma), pow(rCol.b, gamma), rCol.a);
|
||||
|
||||
vec4 fCol = mix(ilCol, irCol, ratio);
|
||||
|
||||
return vec4(pow(fCol.r, one_over_gamma), pow(fCol.g, one_over_gamma), pow(fCol.b, one_over_gamma), fCol.a);
|
||||
}
|
||||
|
||||
vec4 gradientColor(void)
|
||||
{
|
||||
float angle = atan(-paintGradientEnds[1].y + paintGradientEnds[0].y, paintGradientEnds[1].x - paintGradientEnds[0].x);
|
||||
float rotatedStartX = paintGradientEnds[0].x * cos(angle) - paintGradientEnds[0].y * sin(angle);
|
||||
float rotatedEndX = paintGradientEnds[1].x * cos(angle) - paintGradientEnds[1].y * sin(angle);
|
||||
float d = rotatedEndX - rotatedStartX;
|
||||
|
||||
float pX = paintUVCoordinates.x * cos(angle) - paintUVCoordinates.y * sin(angle);
|
||||
|
||||
float mr = smoothstep(rotatedStartX + paintGradientPositions[0] * d, rotatedStartX + paintGradientPositions[1] * d, pX);
|
||||
vec4 col = gammaMix(paintGradientColors[0], paintGradientColors[1], mr);
|
||||
|
||||
for (int i = 1; i < paintGradientStopCount - 1; i++)
|
||||
{
|
||||
mr = smoothstep(rotatedStartX + paintGradientPositions[i] * d, rotatedStartX + paintGradientPositions[i + 1] * d, pX);
|
||||
col = gammaMix(col, paintGradientColors[i + 1], mr);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec4 col;
|
||||
|
||||
switch (paintType)
|
||||
{
|
||||
case 0:
|
||||
col = solidColor();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
col = gradientColor();
|
||||
break;
|
||||
}
|
||||
|
||||
col.rgba *= texture(textureSampler, uvCoordinates);
|
||||
|
||||
out_Color = col;
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 uvCoordinates;
|
||||
flat in int atlasPage;
|
||||
in vec2 paintUVCoordinates;
|
||||
|
||||
uniform sampler2DArray textureSampler;
|
||||
|
||||
uniform int paintType;
|
||||
uniform vec4 paintColor;
|
||||
|
||||
uniform int paintGradientStopCount;
|
||||
uniform vec4[16] paintGradientColors;
|
||||
uniform float[16] paintGradientPositions;
|
||||
uniform vec2[2] paintGradientEnds;
|
||||
|
||||
uniform float pxScale;
|
||||
|
||||
out vec4 out_Color;
|
||||
|
||||
vec4 solidColor(void)
|
||||
{
|
||||
return paintColor;
|
||||
}
|
||||
|
||||
vec4 gammaMix(vec4 lCol, vec4 rCol, float ratio)
|
||||
{
|
||||
float gamma = 2.2;
|
||||
float one_over_gamma = 1 / gamma;
|
||||
|
||||
vec4 ilCol = vec4(pow(lCol.r, gamma), pow(lCol.g, gamma), pow(lCol.b, gamma), lCol.a);
|
||||
vec4 irCol = vec4(pow(rCol.r, gamma), pow(rCol.g, gamma), pow(rCol.b, gamma), rCol.a);
|
||||
|
||||
vec4 fCol = mix(ilCol, irCol, ratio);
|
||||
|
||||
return vec4(pow(fCol.r, one_over_gamma), pow(fCol.g, one_over_gamma), pow(fCol.b, one_over_gamma), fCol.a);
|
||||
}
|
||||
|
||||
vec4 gradientColor(void)
|
||||
{
|
||||
float angle = atan(-paintGradientEnds[1].y + paintGradientEnds[0].y, paintGradientEnds[1].x - paintGradientEnds[0].x);
|
||||
float rotatedStartX = paintGradientEnds[0].x * cos(angle) - paintGradientEnds[0].y * sin(angle);
|
||||
float rotatedEndX = paintGradientEnds[1].x * cos(angle) - paintGradientEnds[1].y * sin(angle);
|
||||
float d = rotatedEndX - rotatedStartX;
|
||||
|
||||
float pX = paintUVCoordinates.x * cos(angle) - paintUVCoordinates.y * sin(angle);
|
||||
|
||||
float mr = smoothstep(rotatedStartX + paintGradientPositions[0] * d, rotatedStartX + paintGradientPositions[1] * d, pX);
|
||||
vec4 col = gammaMix(paintGradientColors[0], paintGradientColors[1], mr);
|
||||
|
||||
for (int i = 1; i < paintGradientStopCount - 1; i++)
|
||||
{
|
||||
mr = smoothstep(rotatedStartX + paintGradientPositions[i] * d, rotatedStartX + paintGradientPositions[i + 1] * d, pX);
|
||||
col = gammaMix(col, paintGradientColors[i + 1], mr);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec3 texCoords = vec3(uvCoordinates, atlasPage);
|
||||
|
||||
float threshold = 180.0 / 255.0 - 5.0 / pow(pxScale, 1.6); // Also help small text be readable
|
||||
|
||||
float signedDist = texture(textureSampler, texCoords).r - threshold;
|
||||
|
||||
vec4 col;
|
||||
|
||||
switch (paintType)
|
||||
{
|
||||
case 0:
|
||||
col = solidColor();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
col = gradientColor();
|
||||
break;
|
||||
}
|
||||
|
||||
col.a *= smoothstep(0, 2.4 / pxScale, signedDist);
|
||||
|
||||
out_Color = col;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 position;
|
||||
in vec2 uvCoords;
|
||||
in vec2 paintUVCoords;
|
||||
|
||||
out vec2 uvCoordinates;
|
||||
out vec2 paintUVCoordinates;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat3 transformation;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
uvCoordinates = uvCoords;
|
||||
paintUVCoordinates = paintUVCoords;
|
||||
vec3 transformed = vec3((transformation * vec3(position, 1.0)).xy, 0.0);
|
||||
gl_Position = projection * vec4(transformed, 1.0);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 position;
|
||||
in vec2 uvCoords;
|
||||
in int page;
|
||||
in vec2 paintUVCoords;
|
||||
|
||||
out vec2 uvCoordinates;
|
||||
out vec2 paintUVCoordinates;
|
||||
flat out int atlasPage;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat3 transformation;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
atlasPage = page;
|
||||
uvCoordinates = uvCoords;
|
||||
paintUVCoordinates = paintUVCoords;
|
||||
vec3 transformed = vec3((transformation * vec3(position, 1.0)).xy, 0.0);
|
||||
gl_Position = projection * vec4(transformed, 1.0);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"displayName": "Pluto Engine GUI Renderer",
|
||||
"description": "",
|
||||
"author": "Tefek",
|
||||
"resourceRoots": {
|
||||
"default": {
|
||||
"path": "default",
|
||||
"type": "open"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"displayName": "Pluto Shader",
|
||||
"description": "PlutoEngine's shader manager.",
|
||||
"author": "Tefek",
|
||||
"resourceRoots": {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#version 330 core
|
||||
|
||||
uniform vec4 color;
|
||||
|
||||
out vec4 out_Color;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
out_Color = color;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 uvCoordinates;
|
||||
|
||||
uniform sampler2DRect textureSampler;
|
||||
|
||||
uniform vec4 recolor;
|
||||
|
||||
out vec4 out_Color;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec4 color = texture(textureSampler, uvCoordinates) * recolor;
|
||||
|
||||
out_Color = color;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 uvCoordinates;
|
||||
|
||||
uniform sampler2DRect textureSampler;
|
||||
|
||||
uniform vec4 recolor;
|
||||
|
||||
out vec4 out_Color;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec4 color = texture(textureSampler, uvCoordinates) * recolor;
|
||||
|
||||
out_Color = color;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 uvCoordinates;
|
||||
|
||||
uniform sampler2DRect textureSampler;
|
||||
|
||||
uniform vec4 recolor;
|
||||
|
||||
out vec4 out_Color;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec4 color = texture(textureSampler, uvCoordinates) * recolor;
|
||||
|
||||
out_Color = color;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 position;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat3x2 transformation;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gl_Position = projection * vec4(transformation * vec3(position.x, position.y, 1.0), 0.0, 1.0);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 position;
|
||||
in vec2 uvCoords;
|
||||
|
||||
out vec2 uvCoordinates;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat3x2 transformation;
|
||||
|
||||
uniform vec2 uvBase;
|
||||
uniform vec2 uvDelta;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gl_Position = projection * vec4(transformation * vec3(position.x, position.y, 1.0), 0.0, 1.0);
|
||||
|
||||
uvCoordinates = uvBase + uvCoords * uvDelta;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 position;
|
||||
in vec2 uvCoords;
|
||||
|
||||
out vec2 uvCoordinates;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat3x2 transformation;
|
||||
|
||||
uniform vec2 uvBase;
|
||||
uniform vec2 uvDelta;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gl_Position = projection * vec4(transformation * vec3(position.x, position.y, 1.0), 0.0, 1.0);
|
||||
|
||||
uvCoordinates = uvBase + uvCoords * uvDelta;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 position;
|
||||
in vec2 uvCoords;
|
||||
|
||||
out vec2 uvCoordinates;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat3x2 transformation;
|
||||
|
||||
uniform vec2 uvBase;
|
||||
uniform vec2 uvDelta;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gl_Position = projection * vec4(transformation * vec3(position.x, position.y, 1.0), 0.0, 1.0);
|
||||
|
||||
uvCoordinates = uvBase + uvCoords * uvDelta;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 uvCoordinates;
|
||||
|
||||
uniform sampler2D textureSampler;
|
||||
|
||||
uniform vec4 recolor;
|
||||
|
||||
out vec4 out_Color;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec4 color = texture(textureSampler, uvCoordinates) * recolor;
|
||||
|
||||
out_Color = color;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
#version 330 core
|
||||
|
||||
in vec2 position;
|
||||
in vec2 uvCoords;
|
||||
|
||||
out vec2 uvCoordinates;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat3x2 transformation;
|
||||
|
||||
uniform vec2 uvBase;
|
||||
uniform vec2 uvDelta;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
gl_Position = projection * vec4(transformation * vec3(position.x, position.y, 1.0), 0.0, 1.0);
|
||||
|
||||
uvCoordinates = uvBase + uvCoords * uvDelta;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"displayName": "Pluto SpriteSheet",
|
||||
"description": "A library to manage, store and draw sprites.",
|
||||
"author": "Tefek",
|
||||
"resourceRoots": {
|
||||
"default": {
|
||||
"path": "default",
|
||||
"type": "open"
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 196 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 5.6 KiB |
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"displayName": "SRClone",
|
||||
"description": "Java reimplementation of SRClone.",
|
||||
"author": "Tefek",
|
||||
"resourceRoots": {
|
||||
"default": {
|
||||
"path": "default",
|
||||
"type": "open"
|
||||
},
|
||||
"icons": {
|
||||
"path": "icons",
|
||||
"type": "open"
|
||||
},
|
||||
"plutofonts": {
|
||||
"path": "plutofonts",
|
||||
"type": "open"
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,249 @@
|
|||
name: SRClone
|
||||
meta:
|
||||
scale: 8
|
||||
ascent: 8
|
||||
descent: 0
|
||||
lineGap: 1
|
||||
atlas: "pluto+asl://!font#png"
|
||||
filtering: NEAREST
|
||||
kern:
|
||||
-
|
||||
left: 0
|
||||
right: 0
|
||||
offset: 0
|
||||
|
||||
glyphs:
|
||||
-
|
||||
cp: 48 # 0
|
||||
sprite:
|
||||
x: 0
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 49 # 1
|
||||
sprite:
|
||||
x: 8
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 50 # 2
|
||||
sprite:
|
||||
x: 16
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 51 # 3
|
||||
sprite:
|
||||
x: 24
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 52 # 4
|
||||
sprite:
|
||||
x: 32
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 53 # 5
|
||||
sprite:
|
||||
x: 40
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 54 # 6
|
||||
sprite:
|
||||
x: 48
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 55 # 7
|
||||
sprite:
|
||||
x: 56
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 56 # 8
|
||||
sprite:
|
||||
x: 64
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 57 # 9
|
||||
sprite:
|
||||
x: 72
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 57 # 9
|
||||
sprite:
|
||||
x: 72
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 43 # +
|
||||
sprite:
|
||||
x: 80
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 45 # +
|
||||
sprite:
|
||||
x: 88
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 46 # .
|
||||
sprite:
|
||||
x: 96
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 120 # x
|
||||
sprite:
|
||||
x: 104
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 128150 # 💖 - sparkling heart because the default red one is two codepoints :(
|
||||
sprite:
|
||||
x: 112
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 105 # i
|
||||
sprite:
|
||||
x: 120
|
||||
y: 0
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 83 # S
|
||||
sprite:
|
||||
x: 0
|
||||
y: 8
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 127776 # 🌠 - shooting star
|
||||
sprite:
|
||||
x: 8
|
||||
y: 8
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 128994 # 🟢 - large green circle
|
||||
sprite:
|
||||
x: 16
|
||||
y: 8
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 9728 # ☀ - sun
|
||||
sprite:
|
||||
x: 24
|
||||
y: 8
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 128314 # 🔺 - red up triangle
|
||||
sprite:
|
||||
x: 32
|
||||
y: 8
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 128160 # 💠 - diamond
|
||||
sprite:
|
||||
x: 40
|
||||
y: 8
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 121171 # 𝥓 - spiral arrow thingy
|
||||
sprite:
|
||||
x: 48
|
||||
y: 8
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 8734 # ∞ - infinity
|
||||
sprite:
|
||||
x: 112
|
||||
y: 8
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
||||
-
|
||||
cp: 32 # space
|
||||
sprite:
|
||||
x: 120
|
||||
y: 8
|
||||
w: 8
|
||||
h: 8
|
||||
leftBearing: 0
|
||||
advance: 8
|
|
@ -0,0 +1,7 @@
|
|||
package cz.tefek.srclone;
|
||||
|
||||
public enum EnumTeam
|
||||
{
|
||||
PLAYER,
|
||||
ENEMY
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
package cz.tefek.srclone;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.plutoengine.Pluto;
|
||||
import org.plutoengine.PlutoLocal;
|
||||
import org.plutoengine.display.Display;
|
||||
import org.plutoengine.graphics.ImmediateFontRenderer;
|
||||
import org.plutoengine.libra.paint.LiPaint;
|
||||
import org.plutoengine.libra.text.shaping.TextStyleOptions;
|
||||
import org.plutoengine.logger.Logger;
|
||||
import org.plutoengine.util.color.Color;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cz.tefek.srclone.ammo.EnumAmmo;
|
||||
import cz.tefek.srclone.audio.SRAudioEngine;
|
||||
import cz.tefek.srclone.entity.Entity;
|
||||
import cz.tefek.srclone.entity.EntityPlayer;
|
||||
import cz.tefek.srclone.entity.projectile.EntityProjectile;
|
||||
import cz.tefek.srclone.particle.Particle;
|
||||
|
||||
public class Game
|
||||
{
|
||||
private float camX;
|
||||
private float camY;
|
||||
|
||||
private float viewX;
|
||||
private float viewY;
|
||||
|
||||
private float deathScreenAnimation;
|
||||
|
||||
private final List<Entity> entities;
|
||||
private final List<EntityProjectile> projectiles;
|
||||
private final List<Particle> particles;
|
||||
|
||||
private final EntityPlayer entityPlayer;
|
||||
|
||||
private final List<Entity> newEntities;
|
||||
private final List<EntityProjectile> newProjectiles;
|
||||
private final List<Particle> newParticles;
|
||||
|
||||
private final Random random;
|
||||
|
||||
private final SRAudioEngine audioEngine;
|
||||
|
||||
private final GameDirector director;
|
||||
|
||||
public Game()
|
||||
{
|
||||
Logger.log("==== NEW GAME ====");
|
||||
|
||||
this.camX = 0;
|
||||
this.camY = 0;
|
||||
|
||||
this.entities = new ArrayList<>();
|
||||
this.newEntities = new ArrayList<>();
|
||||
|
||||
this.projectiles = new ArrayList<>();
|
||||
this.newProjectiles = new ArrayList<>();
|
||||
|
||||
this.entityPlayer = new EntityPlayer();
|
||||
this.entityPlayer.init(this, 0.0f, 0.0f);
|
||||
this.entityPlayer.addAmmo(EnumAmmo.P_LASER_BEAM, EnumAmmo.AMMO_INFINITE);
|
||||
|
||||
// DEBUG
|
||||
|
||||
if (Pluto.DEBUG_MODE)
|
||||
{
|
||||
Arrays.stream(EnumAmmo.values()).forEach(a -> this.entityPlayer.addAmmo(a, EnumAmmo.AMMO_INFINITE));
|
||||
}
|
||||
|
||||
this.particles = new ArrayList<>();
|
||||
this.newParticles = new ArrayList<>();
|
||||
|
||||
this.random = new Random();
|
||||
|
||||
this.audioEngine = new SRAudioEngine();
|
||||
|
||||
this.director = new GameDirector(this);
|
||||
}
|
||||
|
||||
public void tick(double frameTime)
|
||||
{
|
||||
if (frameTime != 0 && !Double.isNaN(frameTime))
|
||||
{
|
||||
var ft = (float) frameTime;
|
||||
|
||||
if (!this.isOver())
|
||||
{
|
||||
this.entities.forEach(e -> e.tick(ft));
|
||||
this.projectiles.forEach(p -> p.tick(ft));
|
||||
this.entityPlayer.tick(ft);
|
||||
|
||||
this.director.tick(frameTime);
|
||||
|
||||
this.entities.removeIf(Entity::isDead);
|
||||
this.entities.addAll(this.newEntities);
|
||||
this.newEntities.clear();
|
||||
|
||||
this.projectiles.removeIf(EntityProjectile::isDead);
|
||||
this.projectiles.addAll(this.newProjectiles);
|
||||
this.newProjectiles.clear();
|
||||
}
|
||||
|
||||
this.particles.forEach(p -> p.tick(ft));
|
||||
this.particles.removeIf(Particle::isDead);
|
||||
this.particles.addAll(this.newParticles);
|
||||
this.newParticles.clear();
|
||||
}
|
||||
|
||||
if (this.isOver())
|
||||
{
|
||||
this.deathScreenAnimation += frameTime;
|
||||
this.audioEngine.stopMusic(this);
|
||||
}
|
||||
|
||||
this.audioEngine.tick(this);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
private void render()
|
||||
{
|
||||
this.camX = -this.entityPlayer.getX();
|
||||
this.camY = -this.entityPlayer.getY();
|
||||
|
||||
var display = PlutoLocal.components().getComponent(Display.class);
|
||||
this.viewX = this.camX + display.getWidth() / 2.0f;
|
||||
this.viewY = this.camY + display.getHeight() / 2.0f;
|
||||
|
||||
SRCloneMod.starField.render(this.viewX, this.viewY);
|
||||
|
||||
this.entities.forEach(Entity::render);
|
||||
|
||||
if (!this.isOver())
|
||||
this.entityPlayer.render();
|
||||
|
||||
this.projectiles.forEach(Entity::render);
|
||||
|
||||
this.particles.forEach(Particle::render);
|
||||
|
||||
var selectedAmmo = this.entityPlayer.getSelectedAmmo();
|
||||
long ammoCnt = this.entityPlayer.getAmmo(selectedAmmo);
|
||||
|
||||
String ammoStr;
|
||||
|
||||
if (ammoCnt == EnumAmmo.AMMO_INFINITE)
|
||||
ammoStr = "%sx∞";
|
||||
else
|
||||
ammoStr = "%%sx%06d".formatted(ammoCnt);
|
||||
|
||||
var ammoFont = new TextStyleOptions(24)
|
||||
.setVerticalAlign(TextStyleOptions.TextAlign.START)
|
||||
.setPaint(LiPaint.solidColor(Color.WHITE));
|
||||
|
||||
ImmediateFontRenderer.drawString(5.0f, display.getHeight() - 58.0f, ammoStr.formatted(selectedAmmo.getTextIcon()), SRCloneMod.srCloneFont, ammoFont);
|
||||
|
||||
var healthFont = new TextStyleOptions(48)
|
||||
.setVerticalAlign(TextStyleOptions.TextAlign.START)
|
||||
.setPaint(LiPaint.solidColor(Color.WHITE));
|
||||
ImmediateFontRenderer.drawString(5.0f, display.getHeight() - 5.0f, "\uD83D\uDC96%03.0f".formatted(this.entityPlayer.getHealth()), SRCloneMod.srCloneFont, healthFont);
|
||||
|
||||
var scoreFont = new TextStyleOptions(24)
|
||||
.setPaint(LiPaint.solidColor(Color.WHITE));
|
||||
ImmediateFontRenderer.drawString(5.0f, 5.0f, ("S %010.0f").formatted(this.entityPlayer.getScore()), SRCloneMod.srCloneFont, scoreFont);
|
||||
|
||||
if (this.deathScreenAnimation > 1.0f)
|
||||
{
|
||||
var youDiedStyle = new TextStyleOptions(64)
|
||||
.setHorizontalAlign(TextStyleOptions.TextAlign.CENTER)
|
||||
.setVerticalAlign(TextStyleOptions.TextAlign.START)
|
||||
.setPaint(LiPaint.solidColor(Color.CRIMSON));
|
||||
ImmediateFontRenderer.drawString(display.getWidth() / 2.0f, display.getHeight() / 2.0f - 6.0f, "You Died!", SRCloneMod.font, youDiedStyle);
|
||||
youDiedStyle.setPaint(LiPaint.solidColor(Color.WHITE));
|
||||
ImmediateFontRenderer.drawString(display.getWidth() / 2.0f, display.getHeight() / 2.0f - 8.0f, "You Died!", SRCloneMod.font, youDiedStyle);
|
||||
|
||||
var playAgainKey = String.valueOf(GLFW.glfwGetKeyName(GLFW.GLFW_KEY_R, GLFW.glfwGetKeyScancode(GLFW.GLFW_KEY_R)));
|
||||
var playAgainStr = "Press [%s] to play again...".formatted(playAgainKey.toUpperCase(Locale.ROOT));
|
||||
var playAgainStyle = new TextStyleOptions(40)
|
||||
.setHorizontalAlign(TextStyleOptions.TextAlign.CENTER)
|
||||
.setVerticalAlign(TextStyleOptions.TextAlign.END)
|
||||
.setPaint(LiPaint.solidColor(Color.CRIMSON));
|
||||
ImmediateFontRenderer.drawString(display.getWidth() / 2.0f, display.getHeight() / 2.0f + 20.0f, playAgainStr, SRCloneMod.font, playAgainStyle);
|
||||
playAgainStyle.setPaint(LiPaint.solidColor(Color.WHITE));
|
||||
ImmediateFontRenderer.drawString(display.getWidth() / 2.0f, display.getHeight() / 2.0f + 18.0f, playAgainStr, SRCloneMod.font, playAgainStyle);
|
||||
}
|
||||
}
|
||||
|
||||
public SRAudioEngine getAudioEngine()
|
||||
{
|
||||
return this.audioEngine;
|
||||
}
|
||||
|
||||
public void addEntity(Entity entity, float x, float y)
|
||||
{
|
||||
if (entity instanceof EntityProjectile projectile)
|
||||
{
|
||||
this.addProjectile(projectile, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
this.newEntities.add(entity);
|
||||
entity.init(this, x, y);
|
||||
}
|
||||
|
||||
public void addParticle(Particle particle, float x, float y)
|
||||
{
|
||||
this.newParticles.add(particle);
|
||||
particle.init(this, x, y);
|
||||
}
|
||||
|
||||
private void addProjectile(EntityProjectile projectile, float x, float y)
|
||||
{
|
||||
this.newProjectiles.add(projectile);
|
||||
projectile.init(this, x, y);
|
||||
}
|
||||
|
||||
public boolean isOver()
|
||||
{
|
||||
return this.entityPlayer.isDead();
|
||||
}
|
||||
|
||||
public float getDeathScreenAnimation()
|
||||
{
|
||||
return this.deathScreenAnimation;
|
||||
}
|
||||
|
||||
public List<Entity> getEntities()
|
||||
{
|
||||
return this.entities;
|
||||
}
|
||||
|
||||
public EntityPlayer getEntityPlayer()
|
||||
{
|
||||
return this.entityPlayer;
|
||||
}
|
||||
|
||||
public Random getRandom()
|
||||
{
|
||||
return this.random;
|
||||
}
|
||||
|
||||
public float getViewX()
|
||||
{
|
||||
return this.viewX;
|
||||
}
|
||||
|
||||
public float getViewY()
|
||||
{
|
||||
return this.viewY;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package cz.tefek.srclone;
|
||||
|
||||
import org.plutoengine.PlutoLocal;
|
||||
import org.plutoengine.display.Display;
|
||||
import org.plutoengine.logger.Logger;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import cz.tefek.srclone.entity.EntityPlayer;
|
||||
import cz.tefek.srclone.entity.enemy.EntityEnemy;
|
||||
import cz.tefek.srclone.entity.enemy.EntityEnemyScout;
|
||||
import cz.tefek.srclone.entity.enemy.EntityEnemySmallBomber;
|
||||
|
||||
public class GameDirector
|
||||
{
|
||||
private double gameTime;
|
||||
|
||||
private double spawnTimer;
|
||||
|
||||
private final Game game;
|
||||
|
||||
private double difficulty;
|
||||
|
||||
GameDirector(Game game)
|
||||
{
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public void tick(double delta)
|
||||
{
|
||||
var rand = this.game.getRandom();
|
||||
|
||||
this.spawnTimer -= delta;
|
||||
|
||||
if (this.spawnTimer <= 0)
|
||||
{
|
||||
this.difficulty = 10 + Math.pow(this.gameTime, 1.5f);
|
||||
|
||||
double spawnTimerNext = 160 / (4 + Math.log10(difficulty));
|
||||
|
||||
double variation = 0.75f + rand.nextDouble() / 2.0;
|
||||
double rest = (1.0f + Math.sin(difficulty / 120.0) / 4.0);
|
||||
double difficultyModifier = Math.sqrt(difficulty / 25.0f);
|
||||
int waveSize = (int) (1 + difficultyModifier * variation * rest);
|
||||
|
||||
Logger.logf("Wave size: %d%n", waveSize);
|
||||
Logger.logf("Next wave in: %.0f seconds%n", spawnTimerNext);
|
||||
Logger.log("---------------");
|
||||
|
||||
var player = this.game.getEntityPlayer();
|
||||
|
||||
this.spawn(player, rand, waveSize);
|
||||
|
||||
this.spawnTimer += spawnTimerNext;
|
||||
}
|
||||
|
||||
this.gameTime += delta;
|
||||
}
|
||||
|
||||
public double getDifficulty()
|
||||
{
|
||||
return this.difficulty;
|
||||
}
|
||||
|
||||
private void spawn(EntityPlayer player, Random rand, int budget)
|
||||
{
|
||||
var display = PlutoLocal.components().getComponent(Display.class);
|
||||
|
||||
double minSpawnRadius = 200 + Math.hypot(display.getWidth(), display.getHeight()); // HACK: Spawn enemies just outside the visible radius
|
||||
|
||||
float px = player.getX();
|
||||
float py = player.getY();
|
||||
|
||||
double tdir = rand.nextDouble() * 2 * Math.PI;
|
||||
|
||||
for (int i = 0; i < budget; i++)
|
||||
{
|
||||
EntityEnemy enemy;
|
||||
|
||||
int enemyTypes = 2;
|
||||
|
||||
switch (rand.nextInt(enemyTypes))
|
||||
{
|
||||
case 1:
|
||||
if (budget >= 5)
|
||||
{
|
||||
enemy = new EntityEnemySmallBomber();
|
||||
i += 2;
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
default:
|
||||
enemy = new EntityEnemyScout();
|
||||
}
|
||||
|
||||
double varDir = (rand.nextDouble() - 0.5) * (Math.PI / 8);
|
||||
|
||||
double t = tdir + varDir;
|
||||
double x = px + Math.cos(t) * minSpawnRadius;
|
||||
double y = py + Math.sin(t) * minSpawnRadius;
|
||||
|
||||
this.game.addEntity(enemy, (float) x, (float) y);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package cz.tefek.srclone;
|
||||
|
||||
public interface IGameObject
|
||||
{
|
||||
void init(Game game, float x, float y);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package cz.tefek.srclone;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.plutoengine.PlutoApplication;
|
||||
import org.plutoengine.PlutoLocal;
|
||||
import org.plutoengine.display.Framerate;
|
||||
import org.plutoengine.graphics.ImmediateFontRenderer;
|
||||
import org.plutoengine.input.Keyboard;
|
||||
import org.plutoengine.libra.paint.LiPaint;
|
||||
import org.plutoengine.libra.text.shaping.TextStyleOptions;
|
||||
import org.plutoengine.math.ProjectionMatrix;
|
||||
import org.plutoengine.shader.uniform.auto.AutomaticUniforms;
|
||||
import org.plutoengine.util.color.Color;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Main extends PlutoApplication
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
var app = new Main();
|
||||
var cfg = new PlutoApplication.StartupConfig();
|
||||
cfg.windowInitialDimensions(1280, 720);
|
||||
cfg.windowName("jsr-clone");
|
||||
// cfg.vsync(1);
|
||||
app.run(args, cfg);
|
||||
}
|
||||
|
||||
private Game game;
|
||||
|
||||
@Override
|
||||
protected Class<?> getMainModule()
|
||||
{
|
||||
return SRCloneMod.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init()
|
||||
{
|
||||
this.game = new Game();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loop()
|
||||
{
|
||||
GL33.glEnable(GL33.GL_BLEND);
|
||||
GL33.glBlendFunc(GL33.GL_SRC_ALPHA, GL33.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
GL33.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GL33.glClear(GL33.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
var projection = ProjectionMatrix.createOrtho2D(this.display.getWidth(), this.display.getHeight());
|
||||
AutomaticUniforms.VIEWPORT_PROJECTION.fire(projection);
|
||||
|
||||
var keyboard = PlutoLocal.components().getComponent(Keyboard.class);
|
||||
if (this.game.isOver() && keyboard.pressed(GLFW.GLFW_KEY_R))
|
||||
{
|
||||
this.game = new Game();
|
||||
}
|
||||
|
||||
var delta = Framerate.getFrameTime() / TimeUnit.SECONDS.toMillis(1);
|
||||
|
||||
this.game.tick(delta);
|
||||
|
||||
ImmediateFontRenderer.drawString(this.display.getWidth(), 5, Framerate.getInterpolatedFPS() + " FPS", SRCloneMod.font, new TextStyleOptions(32).setPaint(LiPaint.solidColor(Color.WHITE)).setHorizontalAlign(TextStyleOptions.TextAlign.END));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package cz.tefek.srclone;
|
||||
|
||||
import org.plutoengine.audio.AudioLoader;
|
||||
import org.plutoengine.audio.RandomAccessClip;
|
||||
import org.plutoengine.audio.SeekableTrack;
|
||||
import org.plutoengine.graphics.PlutoGUIMod;
|
||||
import org.plutoengine.graphics.gl.vao.QuadPresets;
|
||||
import org.plutoengine.graphics.gl.vao.VertexArray;
|
||||
import org.plutoengine.graphics.gui.font.bitmap.BitmapFont;
|
||||
import org.plutoengine.graphics.gui.font.stbttf.STBTTFont;
|
||||
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
|
||||
import org.plutoengine.libra.text.font.LiFontFamily;
|
||||
import org.plutoengine.libra.text.shaping.TextStyleOptions;
|
||||
import org.plutoengine.mod.IModEntryPoint;
|
||||
import org.plutoengine.mod.Mod;
|
||||
import org.plutoengine.mod.ModEntry;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import cz.tefek.srclone.graphics.DirectionalSprite;
|
||||
import cz.tefek.srclone.graphics.StarField;
|
||||
|
||||
@ModEntry(modID = "tefek.srclone", version = "0.1", dependencies = PlutoGUIMod.class)
|
||||
public class SRCloneMod implements IModEntryPoint
|
||||
{
|
||||
public static LiFontFamily<STBTTFont> font;
|
||||
|
||||
public static LiFontFamily<BitmapFont> srCloneFont;
|
||||
|
||||
public static SeekableTrack playingMusic;
|
||||
|
||||
public static RandomAccessClip[] explosionSound;
|
||||
public static RandomAccessClip impactSound;
|
||||
public static Map<String, RandomAccessClip> shootSounds;
|
||||
|
||||
public static VertexArray centeredQuad;
|
||||
|
||||
public static RectangleTexture[] stars;
|
||||
public static StarField starField;
|
||||
|
||||
public static RectangleTexture projectilesBase;
|
||||
|
||||
public static DirectionalSprite player;
|
||||
public static RectangleTexture rocketNozzle;
|
||||
|
||||
public static DirectionalSprite enemyScout;
|
||||
public static DirectionalSprite enemySmallBomber;
|
||||
|
||||
public static DirectionalSprite pickupBox;
|
||||
|
||||
public static DirectionalSprite parExplosion;
|
||||
|
||||
public static DirectionalSprite parImpact;
|
||||
|
||||
@Override
|
||||
public void onLoad(Mod mod)
|
||||
{
|
||||
font = new LiFontFamily<>();
|
||||
font.add(TextStyleOptions.STYLE_REGULAR, STBTTFont.load(mod.getResource("plutofonts$plutostardust#ttf")));
|
||||
|
||||
srCloneFont = new LiFontFamily<>();
|
||||
srCloneFont.add(TextStyleOptions.STYLE_REGULAR, BitmapFont.load(mod.getResource("plutofonts$srclone.info#yaml")));
|
||||
|
||||
playingMusic = AudioLoader.loadMemoryDecoded(mod.getResource("sound.game_st#ogg"));
|
||||
|
||||
explosionSound = IntStream.range(0, 5)
|
||||
.mapToObj("sound.explosion%d#ogg"::formatted)
|
||||
.map(mod::getResource)
|
||||
.map(AudioLoader::loadMemoryPCM)
|
||||
.toArray(RandomAccessClip[]::new);
|
||||
impactSound = AudioLoader.loadMemoryPCM(mod.getResource("sound.hit#ogg"));
|
||||
|
||||
shootSounds = Stream.of("laser", "heatStar", "polySwarm", "electronFlare", "plasma", "tachyon")
|
||||
.collect(Collectors.toMap(Function.identity(), name -> {
|
||||
var path = "sound.shoot.%s#ogg".formatted(name);
|
||||
var resource = mod.getResource(path);
|
||||
assert resource != null;
|
||||
var audio = AudioLoader.loadMemoryPCM(resource);
|
||||
assert audio != null;
|
||||
return audio;
|
||||
}));
|
||||
|
||||
centeredQuad = QuadPresets.halvedSize();
|
||||
|
||||
stars = Stream.of("blue", "red", "redDwarf", "whiteDwarf", "blue").map(starName -> {
|
||||
var tex = new RectangleTexture();
|
||||
tex.load(mod.getResource("textures.stars.%s#png".formatted(starName)));
|
||||
return tex;
|
||||
}).toList().toArray(RectangleTexture[]::new);
|
||||
|
||||
starField = new StarField(0, 0, 8192, 8192, stars);
|
||||
|
||||
projectilesBase = new RectangleTexture();
|
||||
projectilesBase.load(mod.getResource("textures.particles.projectilesBase#png"));
|
||||
|
||||
player = DirectionalSprite.create(mod.getResource("textures.entities.ship#png"), 32, 64, 64, 32);
|
||||
rocketNozzle = new RectangleTexture();
|
||||
rocketNozzle.load(mod.getResource("textures.particles.rocketNozzle#png"));
|
||||
|
||||
enemyScout = DirectionalSprite.create(mod.getResource("textures.entities.e_scout#png"), 16, 128, 128, 16);
|
||||
enemySmallBomber = DirectionalSprite.create(mod.getResource("textures.entities.e_small_bomber#png"), 16, 128, 128, 16);
|
||||
|
||||
parExplosion = DirectionalSprite.create(mod.getResource("textures.particles.explosion1#png"), 16, 128, 128, 16);
|
||||
parImpact = DirectionalSprite.create(mod.getResource("textures.particles.impact1#png"), 10, 64, 64, 10);
|
||||
|
||||
pickupBox = DirectionalSprite.create(mod.getResource("textures.entities.box#png"), 16, 64, 64, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnload()
|
||||
{
|
||||
pickupBox.close();
|
||||
|
||||
parImpact.close();
|
||||
parExplosion.close();
|
||||
|
||||
enemySmallBomber.close();
|
||||
enemyScout.close();
|
||||
|
||||
rocketNozzle.close();
|
||||
player.close();
|
||||
|
||||
projectilesBase.close();
|
||||
|
||||
for (var star : stars)
|
||||
star.close();
|
||||
|
||||
centeredQuad.close();
|
||||
|
||||
shootSounds.values()
|
||||
.forEach(RandomAccessClip::close);
|
||||
|
||||
impactSound.close();
|
||||
|
||||
for (var track : explosionSound)
|
||||
track.close();
|
||||
|
||||
playingMusic.close();
|
||||
|
||||
srCloneFont.close();
|
||||
|
||||
font.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package cz.tefek.srclone.ammo;
|
||||
|
||||
import org.plutoengine.audio.RandomAccessClip;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import cz.tefek.srclone.EnumTeam;
|
||||
import cz.tefek.srclone.SRCloneMod;
|
||||
import cz.tefek.srclone.entity.projectile.*;
|
||||
|
||||
public enum EnumAmmo
|
||||
{
|
||||
P_LASER_BEAM("☀", "laser", 0.2f, EnumTeam.PLAYER, EntityProjectilePlayerLaserBeam::new),
|
||||
P_HEAT_STAR("\uD83C\uDF20", "heatStar", 0.4f, EnumTeam.PLAYER, EntityProjectilePlayerHeatStar::new),
|
||||
P_ELECTRON_FLARE("\uD83D\uDCA0", "electronFlare", 2.5f, EnumTeam.PLAYER, EntityProjectilePlayerElectronFlare::new),
|
||||
P_POLY_SWARM("\uD83D\uDD3A", "polySwarm", 0.1f, EnumTeam.PLAYER, EntityProjectilePlayerPolySwarm::new),
|
||||
P_PLASMA_DISC("\uD83D\uDFE2", "plasma", 2.5f, EnumTeam.PLAYER, EntityProjectilePlayerPlasmaDisc::new),
|
||||
P_TACHYON_DISC("\uD836\uDD53", "tachyon", 2.0f, EnumTeam.PLAYER, EntityProjectilePlayerTachyonDisc::new),
|
||||
|
||||
|
||||
E_LASER_BEAM("☀", "laser", 2.0f, EnumTeam.ENEMY, EntityProjectileEnemyLaserBeam::new),
|
||||
|
||||
E_HEAT_STAR("\uD83C\uDF20", "heatStar", 3.0f, EnumTeam.ENEMY, EntityProjectileEnemyHeatStar::new);
|
||||
|
||||
private final EnumTeam team;
|
||||
|
||||
private final String shootSound;
|
||||
|
||||
private final float cooldown;
|
||||
|
||||
private final String textIcon;
|
||||
|
||||
private final Supplier<EntityProjectileAmmo> projectileSupplier;
|
||||
|
||||
EnumAmmo(String textIcon, String shootSound, float cooldown, EnumTeam team, Supplier<EntityProjectileAmmo> projectileCreateFunc)
|
||||
{
|
||||
this.textIcon = textIcon;
|
||||
this.shootSound = shootSound;
|
||||
this.cooldown = cooldown;
|
||||
this.team = team;
|
||||
this.projectileSupplier = projectileCreateFunc;
|
||||
}
|
||||
|
||||
private static final List<EnumAmmo> PLAYER_SELECTABLE = Arrays.stream(EnumAmmo.values())
|
||||
.filter(EnumAmmo::isPlayerSelectable)
|
||||
.toList();
|
||||
|
||||
public static final long AMMO_INFINITE = Long.MAX_VALUE;
|
||||
|
||||
public static EnumAmmo next(EnumAmmo ammo)
|
||||
{
|
||||
int idx = PLAYER_SELECTABLE.indexOf(ammo);
|
||||
return PLAYER_SELECTABLE.get((idx + 1) % PLAYER_SELECTABLE.size());
|
||||
}
|
||||
|
||||
public static EnumAmmo previous(EnumAmmo ammo)
|
||||
{
|
||||
int idx = PLAYER_SELECTABLE.indexOf(ammo);
|
||||
int size = PLAYER_SELECTABLE.size();
|
||||
return PLAYER_SELECTABLE.get((idx - 1 + size) % size);
|
||||
}
|
||||
|
||||
public float getCooldown()
|
||||
{
|
||||
return this.cooldown;
|
||||
}
|
||||
|
||||
public RandomAccessClip getShootSound()
|
||||
{
|
||||
return SRCloneMod.shootSounds.get(this.shootSound);
|
||||
}
|
||||
|
||||
public EntityProjectileAmmo createProjectile()
|
||||
{
|
||||
return this.projectileSupplier.get();
|
||||
}
|
||||
|
||||
public boolean isPlayerSelectable()
|
||||
{
|
||||
return this.team == EnumTeam.PLAYER;
|
||||
}
|
||||
|
||||
public String getTextIcon()
|
||||
{
|
||||
return this.textIcon;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package cz.tefek.srclone.audio;
|
||||
|
||||
import org.joml.Matrix4x3f;
|
||||
import org.joml.Vector3f;
|
||||
import org.plutoengine.PlutoLocal;
|
||||
import org.plutoengine.audio.RandomAccessClip;
|
||||
import org.plutoengine.audio.al.AudioContext;
|
||||
import org.plutoengine.audio.al.AudioEngine;
|
||||
import org.plutoengine.audio.al.AudioTrackSource;
|
||||
import org.plutoengine.audio.al.SoundEffect;
|
||||
|
||||
import cz.tefek.srclone.Game;
|
||||
import cz.tefek.srclone.SRCloneMod;
|
||||
|
||||
public class SRAudioEngine
|
||||
{
|
||||
private AudioTrackSource music;
|
||||
|
||||
private final AudioEngine audioEngine;
|
||||
private final AudioContext context;
|
||||
|
||||
public SRAudioEngine()
|
||||
{
|
||||
this.audioEngine = PlutoLocal.components().getComponent(AudioEngine.class);
|
||||
this.context = this.audioEngine.getContext();
|
||||
this.context.setTransformation(new Matrix4x3f().scale(1 / 1000.0f, 1 / 1000.0f, 1.0f));
|
||||
}
|
||||
|
||||
public void tick(Game game)
|
||||
{
|
||||
var player = game.getEntityPlayer();
|
||||
|
||||
this.context.setPosition(new Vector3f(player.getX(), player.getY(), -1));
|
||||
|
||||
if (this.music == null && !game.isOver())
|
||||
{
|
||||
this.music = new AudioTrackSource(SRCloneMod.playingMusic);
|
||||
this.music.volume(0.1f);
|
||||
this.music.play();
|
||||
}
|
||||
else if (this.music != null && !this.music.update())
|
||||
this.music.play();
|
||||
}
|
||||
|
||||
public void stopMusic(Game game)
|
||||
{
|
||||
if (this.music != null)
|
||||
{
|
||||
var mute = 1 - game.getDeathScreenAnimation();
|
||||
|
||||
if (mute < 0)
|
||||
{
|
||||
this.music.stop();
|
||||
this.music = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.music.volume(0.2f * mute);
|
||||
}
|
||||
}
|
||||
|
||||
public AudioEngine getAudioEngine()
|
||||
{
|
||||
return this.audioEngine;
|
||||
}
|
||||
|
||||
public void playSoundEffect(RandomAccessClip seekableAudioTrack, float x, float y, float volume)
|
||||
{
|
||||
var soundEffect = new SoundEffect(seekableAudioTrack, new Vector3f(x, y, 0), volume);
|
||||
this.audioEngine.playSound(soundEffect);
|
||||
}
|
||||
|
||||
public void playSoundEffect(RandomAccessClip seekableAudioTrack, float x, float y, float volume, float pitch)
|
||||
{
|
||||
var soundEffect = new SoundEffect(seekableAudioTrack, new Vector3f(x, y, 0), volume);
|
||||
soundEffect.pitch(pitch);
|
||||
this.audioEngine.playSound(soundEffect);
|
||||
}
|
||||
}
|