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
|
|
23
README.md
|
@ -25,7 +25,7 @@ repositories {
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
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 {
|
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
|
### Versioning
|
||||||
|
|
||||||
All submodules share a version number for simplicity reasons.
|
All submodules share a version number for simplicity reasons.
|
||||||
|
@ -64,6 +71,9 @@ version numbers.*
|
||||||
|
|
||||||
## Usability status of submodules
|
## 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
|
### Safe submodules
|
||||||
* **PlutoCore** - Stable
|
* **PlutoCore** - Stable
|
||||||
* **PlutoFramebuffer** - Stable
|
* **PlutoFramebuffer** - Stable
|
||||||
|
@ -74,11 +84,11 @@ version numbers.*
|
||||||
* **PlutoDisplay** - Stable, collision API nowhere near completion
|
* **PlutoDisplay** - Stable, collision API nowhere near completion
|
||||||
* **PlutoUSS2** - Stable
|
* **PlutoUSS2** - Stable
|
||||||
* **PlutoLib** - Mostly stable
|
* **PlutoLib** - Mostly stable
|
||||||
|
* **PlutoRuntime** - Mostly stable
|
||||||
|
|
||||||
### Unstable submodules
|
### Unstable submodules
|
||||||
* **PlutoGUI** - Recently rewritten, the API is highly unstable
|
* **PlutoAudio** - Very tentative, work in progress
|
||||||
* **PlutoRuntime** - Somewhat tentative, the module API has been rewritten and might contain bugs
|
* **PlutoGUI** - Recently rewritten, the API is highly unstable, work in progress
|
||||||
* **PlutoAudio** - Somewhat usable, unfinished
|
|
||||||
|
|
||||||
|
|
||||||
## Current priorities
|
## Current priorities
|
||||||
|
@ -92,12 +102,11 @@ See `NEXT_RELEASE_DRAFT.md` for details.
|
||||||
|
|
||||||
### Very high priority
|
### Very high priority
|
||||||
[ *Implemented in the current release.* ]
|
[ *Implemented in the current release.* ]
|
||||||
|
* Implement the layer system and integrate all existing systems with it
|
||||||
* Improve image loading capabilities, possibly rewrite PlutoLib#TPL
|
* Improve image loading capabilities, possibly rewrite PlutoLib#TPL
|
||||||
|
|
||||||
### High priority
|
### High priority
|
||||||
[ *Implemented in the next release.* ]
|
[ *Implemented in the next release.* ]
|
||||||
* Finish PlutoAudio
|
|
||||||
* Depends on the stage system
|
|
||||||
* Expand upon the Color API
|
* Expand upon the Color API
|
||||||
* Color mixing and blending
|
* Color mixing and blending
|
||||||
* Color transformation
|
* Color transformation
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
## 22.2.0.0-alpha.2
|
## 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`
|
* `[PlutoSpritesheet]` Renamed `TemporalSprite#getSideCount` to `getFrameCount`
|
||||||
|
|
||||||
## 22.2.0.0-alpha.1
|
## 22.2.0.0-alpha.1
|
||||||
|
|
|
@ -5,11 +5,16 @@ import org.gradle.api.JavaVersion
|
||||||
|
|
||||||
object Versions {
|
object Versions {
|
||||||
const val lwjglVersion = "3.3.1"
|
const val lwjglVersion = "3.3.1"
|
||||||
val lwjglNatives = when (OperatingSystem.current()) {
|
val lwjglNatives = listOf(
|
||||||
OperatingSystem.LINUX -> "natives-linux"
|
"natives-linux-arm64",
|
||||||
OperatingSystem.WINDOWS -> "natives-windows"
|
"natives-linux-arm32",
|
||||||
else -> throw Error("Unsupported operating system!")
|
"natives-linux",
|
||||||
}
|
"natives-macos-arm64",
|
||||||
|
"natives-macos",
|
||||||
|
"natives-windows-arm64",
|
||||||
|
"natives-windows",
|
||||||
|
"natives-windows-x86"
|
||||||
|
)
|
||||||
|
|
||||||
const val jomlVersion = "1.10.2"
|
const val jomlVersion = "1.10.2"
|
||||||
const val jomlPrimitivesVersion = "1.10.0"
|
const val jomlPrimitivesVersion = "1.10.0"
|
||||||
|
@ -23,7 +28,7 @@ object Versions {
|
||||||
|
|
||||||
const val isPrerelease = true
|
const val isPrerelease = true
|
||||||
const val prereleaseName = "alpha"
|
const val prereleaseName = "alpha"
|
||||||
const val prerealeaseUpdate = 1
|
const val prerealeaseUpdate = 2
|
||||||
|
|
||||||
val versionFull =
|
val versionFull =
|
||||||
if (isPrerelease)
|
if (isPrerelease)
|
||||||
|
|
|
@ -12,5 +12,7 @@ dependencies {
|
||||||
|
|
||||||
api("org.lwjgl:lwjgl-openal")
|
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;
|
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.lwjgl.system.MemoryUtil;
|
||||||
import org.plutoengine.buffer.BufferHelper;
|
import org.plutoengine.buffer.BufferHelper;
|
||||||
import org.plutoengine.logger.Logger;
|
import org.plutoengine.logger.Logger;
|
||||||
|
@ -10,8 +7,6 @@ import org.plutoengine.logger.SmartSeverity;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.IntBuffer;
|
|
||||||
import java.nio.ShortBuffer;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
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
|
* 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.
|
* 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);
|
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
|
* for from-memory PCM streaming. Good for frequently used small audio
|
||||||
* files.
|
* files.
|
||||||
*/
|
*/
|
||||||
public static ISeekableAudioTrack loadMemoryPCM(Path path)
|
public static RandomAccessClip loadMemoryPCM(Path path)
|
||||||
{
|
{
|
||||||
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
|
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", path);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new MemoryPCMTrack(path);
|
return new MemoryPCMClip(path);
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
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);
|
var size = Files.size(path);
|
||||||
|
|
||||||
|
@ -73,202 +68,4 @@ public class AudioLoader
|
||||||
return BufferHelper.readToByteBuffer(path, readData);
|
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;
|
package org.plutoengine.audio;
|
||||||
|
|
||||||
public interface ISeekableAudioTrack extends IAudioStream
|
public interface ISeekableClip extends ISeekableTrack, IClip
|
||||||
{
|
{
|
||||||
int getSampleOffset();
|
|
||||||
|
|
||||||
int getLengthInSamples();
|
|
||||||
|
|
||||||
default void skip(int sampleCount)
|
default void skip(int sampleCount)
|
||||||
{
|
{
|
||||||
this.seek(Math.min(Math.max(0, this.getSampleOffset() + sampleCount), this.getLengthInSamples()));
|
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));
|
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;
|
package org.plutoengine.audio.al;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.lwjgl.openal.*;
|
import org.plutoengine.component.AbstractComponent;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
import org.plutoengine.Pluto;
|
|
||||||
import org.plutoengine.component.ComponentToken;
|
import org.plutoengine.component.ComponentToken;
|
||||||
import org.plutoengine.component.PlutoLocalComponent;
|
import org.plutoengine.component.PlutoLocalComponent;
|
||||||
import org.plutoengine.logger.Logger;
|
|
||||||
import org.plutoengine.logger.SmartSeverity;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.util.ArrayList;
|
||||||
import java.nio.IntBuffer;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 493msi
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class AudioEngine extends PlutoLocalComponent
|
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);
|
public static final ComponentToken<AudioEngine> TOKEN = ComponentToken.create(AudioEngine::new);
|
||||||
|
|
||||||
|
private AudioContext context;
|
||||||
|
|
||||||
|
private final List<Pair<AudioClipSource, AudioSourceInfo>> sfx;
|
||||||
|
|
||||||
|
|
||||||
private AudioEngine()
|
private AudioEngine()
|
||||||
{
|
{
|
||||||
|
this.sfx = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onMount(ComponentDependencyManager manager)
|
protected void onMount(AbstractComponent<PlutoLocalComponent>.ComponentDependencyManager manager)
|
||||||
{
|
{
|
||||||
var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null);
|
this.context = manager.declareDependency(ComponentToken.create(AudioContext::new));
|
||||||
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;
|
public void update()
|
||||||
|
|
||||||
var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null);
|
|
||||||
if (contextPtr == MemoryUtil.NULL)
|
|
||||||
{
|
{
|
||||||
ALC10.alcCloseDevice(devicePtr);
|
for (var iterator = this.sfx.listIterator(); iterator.hasNext(); )
|
||||||
|
{
|
||||||
|
var data = iterator.next();
|
||||||
|
var source = data.getKey();
|
||||||
|
var info = data.getValue();
|
||||||
|
|
||||||
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to create an OpenAL context.");
|
var kaFunc = info.keepAliveFunction();
|
||||||
|
if (kaFunc != null && !kaFunc.getAsBoolean())
|
||||||
|
source.close();
|
||||||
|
|
||||||
// The game should not crash just because we have no audio
|
var moveFunc = info.moveFunction();
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.context = contextPtr;
|
if (source.updateOrClose())
|
||||||
|
iterator.remove();
|
||||||
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;
|
public void playSound(SoundEffect sfx)
|
||||||
|
|
||||||
Logger.log(SmartSeverity.AUDIO_PLUS, "Audio engine started.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSpeed(Vector3f speed)
|
|
||||||
{
|
{
|
||||||
AL10.alListener3f(AL10.AL_VELOCITY, speed.x, speed.y, speed.z);
|
var soundEffect = new AudioClipSource(sfx.getClip());
|
||||||
}
|
soundEffect.volume(sfx.getVolume());
|
||||||
|
soundEffect.pitch(sfx.getPitch());
|
||||||
public void setPosition(Vector3f position)
|
soundEffect.position(this.context, sfx.getPosition());
|
||||||
{
|
soundEffect.play();
|
||||||
AL10.alListener3f(AL10.AL_POSITION, position.x, position.y, position.z);
|
var info = new AudioSourceInfo(sfx.getMovementMapper(), sfx.getKeepAliveFunction());
|
||||||
}
|
var data = Pair.of(soundEffect, info);
|
||||||
|
this.sfx.add(data);
|
||||||
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
|
@Override
|
||||||
protected void onUnmount()
|
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
|
@Override
|
||||||
|
@ -124,4 +80,9 @@ public class AudioEngine extends PlutoLocalComponent
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AudioContext getContext()
|
||||||
|
{
|
||||||
|
return this.context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,73 @@
|
||||||
package org.plutoengine.audio.al;
|
package org.plutoengine.audio.al;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.MustBeInvokedByOverriders;
|
||||||
import org.joml.Vector3fc;
|
import org.joml.Vector3fc;
|
||||||
import org.lwjgl.openal.AL10;
|
import org.lwjgl.openal.AL10;
|
||||||
|
|
||||||
public abstract class AudioSource implements AutoCloseable
|
public abstract class AudioSource implements AutoCloseable
|
||||||
{
|
{
|
||||||
protected final int source;
|
protected final int id;
|
||||||
|
protected Vector3fc position;
|
||||||
|
|
||||||
protected AudioSource()
|
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()
|
public void stop()
|
||||||
{
|
{
|
||||||
AL10.alSourceStop(this.source);
|
AL10.alSourceStop(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MustBeInvokedByOverriders
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
this.stop();
|
AL10.alDeleteSources(this.id);
|
||||||
AL10.alDeleteSources(this.source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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)
|
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();
|
this.display.swapBuffers();
|
||||||
|
|
||||||
|
audioEngine.update();
|
||||||
inputBus.resetStates();
|
inputBus.resetStates();
|
||||||
|
|
||||||
this.display.pollEvents();
|
this.display.pollEvents();
|
||||||
|
|
|
@ -14,10 +14,13 @@ dependencies {
|
||||||
api("org.lwjgl", "lwjgl-glfw")
|
api("org.lwjgl", "lwjgl-glfw")
|
||||||
api("org.lwjgl", "lwjgl-opengl")
|
api("org.lwjgl", "lwjgl-opengl")
|
||||||
api("org.lwjgl", "lwjgl-stb")
|
api("org.lwjgl", "lwjgl-stb")
|
||||||
runtimeOnly("org.lwjgl", "lwjgl", classifier = Versions.lwjglNatives)
|
|
||||||
runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = Versions.lwjglNatives)
|
org.plutoengine.Versions.lwjglNatives.forEach {
|
||||||
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = Versions.lwjglNatives)
|
runtimeOnly("org.lwjgl", "lwjgl", classifier = it)
|
||||||
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = Versions.lwjglNatives)
|
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", Versions.steamworks4jVersion)
|
||||||
api("com.code-disaster.steamworks4j", "steamworks4j-server", Versions.steamworks4jServerVersion)
|
api("com.code-disaster.steamworks4j", "steamworks4j-server", Versions.steamworks4jServerVersion)
|
||||||
|
|
|
@ -16,7 +16,7 @@ public class Framerate
|
||||||
|
|
||||||
private static double animationTimer = 0;
|
private static double animationTimer = 0;
|
||||||
|
|
||||||
private static double FPS = Double.NaN;
|
private static double fps = Double.NaN;
|
||||||
|
|
||||||
private static int interpolatedFPS;
|
private static int interpolatedFPS;
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ public class Framerate
|
||||||
|
|
||||||
public static double getFPS()
|
public static double getFPS()
|
||||||
{
|
{
|
||||||
return FPS;
|
return fps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getInterpolatedFPS()
|
public static int getInterpolatedFPS()
|
||||||
|
@ -55,7 +55,7 @@ public class Framerate
|
||||||
animationTimer += frameTimeNs / (double) TimeUnit.SECONDS.toNanos(1);
|
animationTimer += frameTimeNs / (double) TimeUnit.SECONDS.toNanos(1);
|
||||||
// Maintain precision in case the engine runs for many hours
|
// Maintain precision in case the engine runs for many hours
|
||||||
animationTimer %= TimeUnit.DAYS.toMinutes(1);
|
animationTimer %= TimeUnit.DAYS.toMinutes(1);
|
||||||
FPS = TimeUnit.SECONDS.toMillis(1) / frameTime;
|
fps = TimeUnit.SECONDS.toMillis(1) / frameTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nowMs = System.currentTimeMillis();
|
var nowMs = System.currentTimeMillis();
|
||||||
|
@ -77,7 +77,7 @@ public class Framerate
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
interpolatedFPS = (int) Math.round(FPS);
|
interpolatedFPS = (int) Math.round(fps);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastDraw = now;
|
lastDraw = now;
|
||||||
|
|
|
@ -14,7 +14,9 @@ dependencies {
|
||||||
implementation("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", "2.12.3")
|
implementation("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", "2.12.3")
|
||||||
|
|
||||||
implementation("org.lwjgl", "lwjgl-yoga")
|
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")
|
implementation("org.commonmark", "commonmark", "0.18.1")
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package org.plutoengine.graphics;
|
package org.plutoengine.graphics;
|
||||||
|
|
||||||
import org.plutoengine.Pluto;
|
import org.plutoengine.Pluto;
|
||||||
|
import org.plutoengine.graphics.gui.BitmapFontShader;
|
||||||
import org.plutoengine.graphics.gui.FontShader;
|
import org.plutoengine.graphics.gui.FontShader;
|
||||||
import org.plutoengine.graphics.texture.MagFilter;
|
import org.plutoengine.graphics.texture.MagFilter;
|
||||||
import org.plutoengine.graphics.texture.MinFilter;
|
import org.plutoengine.graphics.texture.MinFilter;
|
||||||
|
@ -28,7 +29,7 @@ public class PlutoGUIMod implements IModEntryPoint
|
||||||
|
|
||||||
public static FontShader fontShader;
|
public static FontShader fontShader;
|
||||||
|
|
||||||
public static FontShader bitmapFontShader;
|
public static BitmapFontShader bitmapFontShader;
|
||||||
|
|
||||||
public void onLoad(Mod mod)
|
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);
|
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 = new RectangleTexture();
|
||||||
uiElementsAtlas.load(mod.getResource("gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
|
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;
|
import org.plutoengine.util.color.IRGBA;
|
||||||
|
|
||||||
@ShaderProgram
|
@ShaderProgram
|
||||||
public final class BitmapTextShader extends ShaderBase implements IGUIShader
|
public final class BitmapFontShader extends ShaderBase implements IGUIShader
|
||||||
{
|
{
|
||||||
@AutoViewportProjection
|
@AutoViewportProjection
|
||||||
@Uniform(name = "projection")
|
@Uniform(name = "projection")
|
|
@ -38,7 +38,10 @@ dependencies {
|
||||||
implementation("org.lwjgl:lwjgl")
|
implementation("org.lwjgl:lwjgl")
|
||||||
implementation("org.lwjgl:lwjgl-xxhash")
|
implementation("org.lwjgl:lwjgl-xxhash")
|
||||||
implementation("org.lwjgl:lwjgl-zstd")
|
implementation("org.lwjgl:lwjgl-zstd")
|
||||||
runtimeOnly("org.lwjgl", "lwjgl", classifier = Versions.lwjglNatives)
|
|
||||||
runtimeOnly("org.lwjgl", "lwjgl-xxhash", classifier = Versions.lwjglNatives)
|
Versions.lwjglNatives.forEach {
|
||||||
runtimeOnly("org.lwjgl", "lwjgl-zstd", classifier = Versions.lwjglNatives)
|
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);
|
||||||
|
}
|
||||||
|
}
|