misc: import libtrx codebase

This commit is contained in:
Marcin Kurczewski 2024-10-02 09:33:43 +02:00
parent d46069af22
commit 61ae36ee44
193 changed files with 29958 additions and 0 deletions

62
src/libtrx/benchmark.c Normal file
View file

@ -0,0 +1,62 @@
#include "benchmark.h"
#include "log.h"
#include "memory.h"
#include <SDL2/SDL_timer.h>
static void M_Log(
BENCHMARK *const b, const char *file, int32_t line, const char *func,
Uint64 current, const char *message)
{
const Uint64 freq = SDL_GetPerformanceFrequency();
const double elapsed_start =
(double)(current - b->start) * 1000.0 / (double)freq;
const double elapsed_last =
(double)(current - b->last) * 1000.0 / (double)freq;
if (b->last != b->start) {
if (message == NULL) {
Log_Message(
file, line, func, "took %.02f ms (%.02f ms)", elapsed_start,
elapsed_last);
} else {
Log_Message(
file, line, func, "%s: took %.02f ms (%.02f ms)", message,
elapsed_start, elapsed_last);
}
} else {
if (message == NULL) {
Log_Message(file, line, func, "took %.02f ms", elapsed_start);
} else {
Log_Message(
file, line, func, "%s: took %.02f ms (%.02f ms)", message,
elapsed_start);
}
}
}
BENCHMARK *Benchmark_Start(void)
{
BENCHMARK *const b = Memory_Alloc(sizeof(BENCHMARK));
b->start = SDL_GetPerformanceCounter();
b->last = b->start;
return b;
}
void Benchmark_Tick_Impl(
BENCHMARK *const b, const char *const file, const int32_t line,
const char *const func, const char *const message)
{
const Uint64 current = SDL_GetPerformanceCounter();
M_Log(b, file, line, func, current, message);
b->last = current;
}
void Benchmark_End_Impl(
BENCHMARK *b, const char *const file, const int32_t line,
const char *const func, const char *const message)
{
Benchmark_Tick_Impl(b, file, line, func, message);
Memory_FreePointer(&b);
}

View file

@ -0,0 +1,60 @@
#include "config/common.h"
#include "config/file.h"
#include <assert.h>
EVENT_MANAGER *m_EventManager = NULL;
void Config_Init(void)
{
m_EventManager = EventManager_Create();
}
void Config_Shutdown(void)
{
EventManager_Free(m_EventManager);
m_EventManager = NULL;
}
bool Config_Read(void)
{
const bool result = ConfigFile_Read(Config_GetPath(), &Config_LoadFromJSON);
if (result) {
Config_Sanitize();
Config_ApplyChanges();
}
return result;
}
bool Config_Write(void)
{
Config_Sanitize();
const bool updated = ConfigFile_Write(Config_GetPath(), &Config_DumpToJSON);
if (updated) {
Config_ApplyChanges();
if (m_EventManager != NULL) {
const EVENT event = {
.name = "write",
.sender = NULL,
.data = NULL,
};
EventManager_Fire(m_EventManager, &event);
}
}
return updated;
}
int32_t Config_SubscribeChanges(
const EVENT_LISTENER listener, void *const user_data)
{
assert(m_EventManager != NULL);
return EventManager_Subscribe(
m_EventManager, "write", NULL, listener, user_data);
}
void Config_UnsubscribeChanges(const int32_t listener_id)
{
assert(m_EventManager != NULL);
return EventManager_Unsubscribe(m_EventManager, listener_id);
}

197
src/libtrx/config/file.c Normal file
View file

@ -0,0 +1,197 @@
#include "config/file.h"
#include "filesystem.h"
#include "log.h"
#include "memory.h"
#include <string.h>
static bool M_ReadFromJSON(
const char *json, void (*load)(JSON_OBJECT *root_obj));
static char *M_WriteToJSON(void (*dump)(JSON_OBJECT *root_obj));
static const char *M_ResolveOptionName(const char *option_name);
static bool M_ReadFromJSON(
const char *cfg_data, void (*load)(JSON_OBJECT *root_obj))
{
bool result = false;
JSON_PARSE_RESULT parse_result;
JSON_VALUE *root = JSON_ParseEx(
cfg_data, strlen(cfg_data), JSON_PARSE_FLAGS_ALLOW_JSON5, NULL, NULL,
&parse_result);
if (root != NULL) {
result = true;
} else {
LOG_ERROR(
"failed to parse config file: %s in line %d, char %d",
JSON_GetErrorDescription(parse_result.error),
parse_result.error_line_no, parse_result.error_row_no);
// continue to supply the default values
}
JSON_OBJECT *root_obj = JSON_ValueAsObject(root);
load(root_obj);
if (root) {
JSON_ValueFree(root);
}
return result;
}
static char *M_WriteToJSON(void (*dump)(JSON_OBJECT *root_obj))
{
JSON_OBJECT *root_obj = JSON_ObjectNew();
dump(root_obj);
JSON_VALUE *root = JSON_ValueFromObject(root_obj);
size_t size;
char *data = JSON_WritePretty(root, " ", "\n", &size);
JSON_ValueFree(root);
return data;
}
static const char *M_ResolveOptionName(const char *option_name)
{
const char *dot = strrchr(option_name, '.');
if (dot) {
return dot + 1;
}
return option_name;
}
bool ConfigFile_Read(const char *path, void (*load)(JSON_OBJECT *root_obj))
{
bool result = false;
char *cfg_data = NULL;
if (!File_Load(path, &cfg_data, NULL)) {
LOG_WARNING("'%s' not loaded - default settings will apply", path);
result = M_ReadFromJSON("{}", load);
} else {
result = M_ReadFromJSON(cfg_data, load);
}
Memory_FreePointer(&cfg_data);
return result;
}
bool ConfigFile_Write(const char *path, void (*dump)(JSON_OBJECT *root_obj))
{
LOG_INFO("Saving user settings");
char *old_data;
File_Load(path, &old_data, NULL);
bool updated = false;
char *data = M_WriteToJSON(dump);
if (old_data == NULL || strcmp(data, old_data) != 0) {
MYFILE *const fp = File_Open(path, FILE_OPEN_WRITE);
if (fp == NULL) {
LOG_ERROR("Failed to write settings!");
} else {
File_WriteData(fp, data, strlen(data));
File_Close(fp);
updated = true;
}
}
Memory_FreePointer(&data);
return updated;
}
void ConfigFile_LoadOptions(JSON_OBJECT *root_obj, const CONFIG_OPTION *options)
{
const CONFIG_OPTION *opt = options;
while (opt->target) {
switch (opt->type) {
case COT_BOOL:
*(bool *)opt->target = JSON_ObjectGetBool(
root_obj, M_ResolveOptionName(opt->name),
*(bool *)opt->default_value);
break;
case COT_INT32:
*(int32_t *)opt->target = JSON_ObjectGetInt(
root_obj, M_ResolveOptionName(opt->name),
*(int32_t *)opt->default_value);
break;
case COT_FLOAT:
*(float *)opt->target = JSON_ObjectGetDouble(
root_obj, M_ResolveOptionName(opt->name),
*(float *)opt->default_value);
break;
case COT_DOUBLE:
*(double *)opt->target = JSON_ObjectGetDouble(
root_obj, M_ResolveOptionName(opt->name),
*(double *)opt->default_value);
break;
case COT_ENUM:
*(int *)opt->target = ConfigFile_ReadEnum(
root_obj, M_ResolveOptionName(opt->name),
*(int *)opt->default_value, opt->param);
}
opt++;
}
}
void ConfigFile_DumpOptions(JSON_OBJECT *root_obj, const CONFIG_OPTION *options)
{
const CONFIG_OPTION *opt = options;
while (opt->target) {
switch (opt->type) {
case COT_BOOL:
JSON_ObjectAppendBool(
root_obj, M_ResolveOptionName(opt->name), *(bool *)opt->target);
break;
case COT_INT32:
JSON_ObjectAppendInt(
root_obj, M_ResolveOptionName(opt->name),
*(int32_t *)opt->target);
break;
case COT_FLOAT:
JSON_ObjectAppendDouble(
root_obj, M_ResolveOptionName(opt->name),
*(float *)opt->target);
break;
case COT_DOUBLE:
JSON_ObjectAppendDouble(
root_obj, M_ResolveOptionName(opt->name),
*(double *)opt->target);
break;
case COT_ENUM:
ConfigFile_WriteEnum(
root_obj, M_ResolveOptionName(opt->name), *(int *)opt->target,
(const char *)opt->param);
break;
}
opt++;
}
}
int ConfigFile_ReadEnum(
JSON_OBJECT *const obj, const char *const name, const int default_value,
const char *const enum_name)
{
const char *value_str = JSON_ObjectGetString(obj, name, NULL);
if (value_str != NULL) {
return EnumMap_Get(enum_name, value_str, default_value);
}
return default_value;
}
void ConfigFile_WriteEnum(
JSON_OBJECT *obj, const char *name, int value, const char *enum_name)
{
JSON_ObjectAppendString(obj, name, EnumMap_ToString(enum_name, value));
}

128
src/libtrx/engine/audio.c Normal file
View file

@ -0,0 +1,128 @@
#include "audio_internal.h"
#include "log.h"
#include "memory.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_error.h>
#include <SDL2/SDL_stdinc.h>
#include <stdint.h>
#include <string.h>
SDL_AudioDeviceID g_AudioDeviceID = 0;
static int32_t m_RefCount = 0;
static size_t m_MixBufferCapacity = 0;
static float *m_MixBuffer = NULL;
static Uint8 m_Silence = 0;
static void M_MixerCallback(void *userdata, Uint8 *stream_data, int32_t len);
static void M_MixerCallback(void *userdata, Uint8 *stream_data, int32_t len)
{
memset(m_MixBuffer, m_Silence, len);
Audio_Stream_Mix(m_MixBuffer, len);
Audio_Sample_Mix(m_MixBuffer, len);
memcpy(stream_data, m_MixBuffer, len);
}
bool Audio_Init(void)
{
m_RefCount++;
if (g_AudioDeviceID) {
// already initialized
return true;
}
int32_t result = SDL_Init(SDL_INIT_AUDIO);
if (result < 0) {
LOG_ERROR("Error while calling SDL_Init: 0x%lx", result);
return false;
}
SDL_AudioSpec desired;
SDL_memset(&desired, 0, sizeof(desired));
desired.freq = AUDIO_WORKING_RATE;
desired.format = AUDIO_WORKING_FORMAT;
desired.channels = AUDIO_WORKING_CHANNELS;
desired.samples = AUDIO_SAMPLES;
desired.callback = M_MixerCallback;
desired.userdata = NULL;
SDL_AudioSpec delivered;
g_AudioDeviceID = SDL_OpenAudioDevice(NULL, 0, &desired, &delivered, 0);
if (!g_AudioDeviceID) {
LOG_ERROR("Failed to open audio device: %s", SDL_GetError());
return false;
}
m_Silence = desired.silence;
m_MixBufferCapacity = desired.samples * desired.channels
* SDL_AUDIO_BITSIZE(desired.format) / 8;
m_MixBuffer = Memory_Alloc(m_MixBufferCapacity);
SDL_PauseAudioDevice(g_AudioDeviceID, 0);
Audio_Sample_Init();
Audio_Stream_Init();
return true;
}
bool Audio_Shutdown(void)
{
m_RefCount--;
if (m_RefCount > 0) {
return false;
}
if (g_AudioDeviceID) {
SDL_PauseAudioDevice(g_AudioDeviceID, 1);
SDL_CloseAudioDevice(g_AudioDeviceID);
g_AudioDeviceID = 0;
}
Memory_FreePointer(&m_MixBuffer);
Audio_Sample_Shutdown();
Audio_Stream_Shutdown();
return true;
}
int32_t Audio_GetAVChannelLayout(const int32_t channels)
{
switch (channels) {
// clang-format off
case 1: return AV_CH_LAYOUT_MONO;
case 2: return AV_CH_LAYOUT_STEREO;
default: return AV_CH_LAYOUT_MONO;
// clang-format on
}
}
int32_t Audio_GetAVAudioFormat(const int32_t sample_fmt)
{
switch (sample_fmt) {
// clang-format off
case AUDIO_U8: return AV_SAMPLE_FMT_U8;
case AUDIO_S16: return AV_SAMPLE_FMT_S16;
case AUDIO_S32: return AV_SAMPLE_FMT_S32;
case AUDIO_F32: return AV_SAMPLE_FMT_FLT;
default: return -1;
// clang-format on
}
}
int32_t Audio_GetSDLAudioFormat(const enum AVSampleFormat sample_fmt)
{
// clang-format off
switch (sample_fmt) {
case AV_SAMPLE_FMT_U8: return AUDIO_U8;
case AV_SAMPLE_FMT_S16: return AUDIO_S16;
case AV_SAMPLE_FMT_S32: return AUDIO_S32;
case AV_SAMPLE_FMT_FLT: return AUDIO_F32;
default: return -1;
}
// clang-format on
}

View file

@ -0,0 +1,25 @@
#pragma once
#include "engine/audio.h"
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
#define AUDIO_WORKING_RATE 44100
#define AUDIO_WORKING_FORMAT AUDIO_F32
#define AUDIO_SAMPLES 500
#define AUDIO_WORKING_CHANNELS 2
extern SDL_AudioDeviceID g_AudioDeviceID;
int32_t Audio_GetAVChannelLayout(int32_t sample_fmt);
int32_t Audio_GetAVAudioFormat(int32_t sample_fmt);
int32_t Audio_GetSDLAudioFormat(enum AVSampleFormat sample_fmt);
void Audio_Sample_Init(void);
void Audio_Sample_Shutdown(void);
void Audio_Sample_Mix(float *dst_buffer, size_t len);
void Audio_Stream_Init(void);
void Audio_Stream_Shutdown(void);
void Audio_Stream_Mix(float *dst_buffer, size_t len);

View file

@ -0,0 +1,776 @@
#include "audio_internal.h"
#include "log.h"
#include "memory.h"
#include <SDL2/SDL_audio.h>
#include <assert.h>
#include <errno.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/codec.h>
#include <libavcodec/packet.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/avutil.h>
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
typedef struct {
char *original_data;
size_t original_size;
float *sample_data;
int32_t channels;
int32_t num_samples;
} AUDIO_SAMPLE;
typedef struct {
bool is_used;
bool is_looped;
bool is_playing;
float volume_l; // sample gain multiplier
float volume_r; // sample gain multiplier
float pitch;
int32_t volume; // volume specified in hundredths of decibel
int32_t pan; // pan specified in hundredths of decibel
// pitch shift means the same samples can be reused twice, hence float
float current_sample;
AUDIO_SAMPLE *sample;
} AUDIO_SAMPLE_SOUND;
typedef struct {
const char *data;
const char *ptr;
int32_t size;
int32_t remaining;
} AUDIO_AV_BUFFER;
static int32_t m_LoadedSamplesCount = 0;
static AUDIO_SAMPLE m_LoadedSamples[AUDIO_MAX_SAMPLES] = { 0 };
static AUDIO_SAMPLE_SOUND m_Samples[AUDIO_MAX_ACTIVE_SAMPLES] = { 0 };
static double M_DecibelToMultiplier(double db_gain);
static bool M_RecalculateChannelVolumes(int32_t sound_id);
static int32_t M_ReadAVBuffer(void *opaque, uint8_t *dst, int32_t dst_size);
static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence);
static bool M_Convert(const int32_t sample_id);
static double M_DecibelToMultiplier(double db_gain)
{
return pow(2.0, db_gain / 600.0);
}
static bool M_RecalculateChannelVolumes(int32_t sound_id)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_SAMPLES) {
return false;
}
AUDIO_SAMPLE_SOUND *sound = &m_Samples[sound_id];
sound->volume_l = M_DecibelToMultiplier(
sound->volume - (sound->pan > 0 ? sound->pan : 0));
sound->volume_r = M_DecibelToMultiplier(
sound->volume + (sound->pan < 0 ? sound->pan : 0));
return true;
}
static int32_t M_ReadAVBuffer(void *opaque, uint8_t *dst, int32_t dst_size)
{
assert(opaque != NULL);
assert(dst != NULL);
AUDIO_AV_BUFFER *src = opaque;
int32_t read = dst_size >= src->remaining ? src->remaining : dst_size;
if (!read) {
return AVERROR_EOF;
}
memcpy(dst, src->ptr, read);
src->ptr += read;
src->remaining -= read;
return read;
}
static int64_t M_SeekAVBuffer(void *opaque, int64_t offset, int32_t whence)
{
assert(opaque != NULL);
AUDIO_AV_BUFFER *src = opaque;
if (whence & AVSEEK_SIZE) {
return src->size;
}
switch (whence) {
case SEEK_SET:
if (src->size - offset < 0) {
return AVERROR_EOF;
}
src->ptr = src->data + offset;
src->remaining = src->size - offset;
break;
case SEEK_CUR:
if (src->remaining - offset < 0) {
return AVERROR_EOF;
}
src->ptr += offset;
src->remaining -= offset;
break;
case SEEK_END:
if (src->size + offset < 0) {
return AVERROR_EOF;
}
src->ptr = src->data - offset;
src->remaining = src->size + offset;
break;
}
return src->ptr - src->data;
}
static bool M_Convert(const int32_t sample_id)
{
assert(sample_id >= 0 && sample_id < m_LoadedSamplesCount);
bool result = false;
AUDIO_SAMPLE *const sample = &m_LoadedSamples[sample_id];
if (sample->sample_data != NULL) {
return true;
}
const clock_t time_start = clock();
size_t working_buffer_size = 0;
float *working_buffer = NULL;
struct {
size_t read_buffer_size;
AVIOContext *avio_context;
AVStream *stream;
AVFormatContext *format_ctx;
const AVCodec *codec;
AVCodecContext *codec_ctx;
AVPacket *packet;
AVFrame *frame;
} av = {
.read_buffer_size = 8192,
.avio_context = NULL,
.stream = NULL,
.format_ctx = NULL,
.codec = NULL,
.codec_ctx = NULL,
.packet = NULL,
.frame = NULL,
};
struct {
int32_t src_format;
int32_t src_channels;
int32_t src_sample_rate;
int32_t dst_format;
int32_t dst_channels;
int32_t dst_sample_rate;
SwrContext *ctx;
} swr = { 0 };
int32_t error_code;
unsigned char *read_buffer = av_malloc(av.read_buffer_size);
if (!read_buffer) {
error_code = AVERROR(ENOMEM);
goto cleanup;
}
AUDIO_AV_BUFFER av_buf = {
.data = sample->original_data,
.ptr = sample->original_data,
.size = sample->original_size,
.remaining = sample->original_size,
};
av.avio_context = avio_alloc_context(
read_buffer, av.read_buffer_size, 0, &av_buf, M_ReadAVBuffer, NULL,
M_SeekAVBuffer);
av.format_ctx = avformat_alloc_context();
av.format_ctx->pb = av.avio_context;
error_code =
avformat_open_input(&av.format_ctx, "dummy_filename", NULL, NULL);
if (error_code != 0) {
goto cleanup;
}
error_code = avformat_find_stream_info(av.format_ctx, NULL);
if (error_code < 0) {
goto cleanup;
}
av.stream = NULL;
for (uint32_t i = 0; i < av.format_ctx->nb_streams; i++) {
AVStream *current_stream = av.format_ctx->streams[i];
if (current_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
av.stream = current_stream;
break;
}
}
if (!av.stream) {
error_code = AVERROR_STREAM_NOT_FOUND;
goto cleanup;
}
av.codec = avcodec_find_decoder(av.stream->codecpar->codec_id);
if (!av.codec) {
error_code = AVERROR_DEMUXER_NOT_FOUND;
goto cleanup;
}
av.codec_ctx = avcodec_alloc_context3(av.codec);
if (!av.codec_ctx) {
error_code = AVERROR(ENOMEM);
goto cleanup;
}
error_code =
avcodec_parameters_to_context(av.codec_ctx, av.stream->codecpar);
if (error_code) {
goto cleanup;
}
error_code = avcodec_open2(av.codec_ctx, av.codec, NULL);
if (error_code < 0) {
goto cleanup;
}
av.packet = av_packet_alloc();
if (!av.packet) {
error_code = AVERROR(ENOMEM);
goto cleanup;
}
av.frame = av_frame_alloc();
if (!av.frame) {
error_code = AVERROR(ENOMEM);
goto cleanup;
}
while (1) {
error_code = av_read_frame(av.format_ctx, av.packet);
if (error_code == AVERROR_EOF) {
av_packet_unref(av.packet);
error_code = 0;
break;
}
if (error_code < 0) {
av_packet_unref(av.packet);
goto cleanup;
}
error_code = avcodec_send_packet(av.codec_ctx, av.packet);
if (error_code < 0) {
av_packet_unref(av.packet);
goto cleanup;
}
if (!swr.ctx) {
swr.src_sample_rate = av.codec_ctx->sample_rate;
swr.src_channels = av.codec_ctx->channels;
swr.src_format = av.codec_ctx->sample_fmt;
swr.dst_sample_rate = AUDIO_WORKING_RATE;
swr.dst_channels = 1;
swr.dst_format = Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT);
swr.ctx = swr_alloc_set_opts(
swr.ctx, swr.dst_channels, swr.dst_format, swr.dst_sample_rate,
swr.src_channels, swr.src_format, swr.src_sample_rate, 0, 0);
if (!swr.ctx) {
av_packet_unref(av.packet);
error_code = AVERROR(ENOMEM);
goto cleanup;
}
error_code = swr_init(swr.ctx);
if (error_code != 0) {
av_packet_unref(av.packet);
goto cleanup;
}
}
while (1) {
error_code = avcodec_receive_frame(av.codec_ctx, av.frame);
if (error_code == AVERROR(EAGAIN)) {
av_frame_unref(av.frame);
break;
}
if (error_code < 0) {
av_packet_unref(av.packet);
av_frame_unref(av.frame);
goto cleanup;
}
uint8_t *out_buffer = NULL;
const int32_t out_samples =
swr_get_out_samples(swr.ctx, av.frame->nb_samples);
av_samples_alloc(
&out_buffer, NULL, swr.dst_channels, out_samples,
swr.dst_format, 1);
int32_t resampled_size = swr_convert(
swr.ctx, &out_buffer, out_samples,
(const uint8_t **)av.frame->data, av.frame->nb_samples);
while (resampled_size > 0) {
int32_t out_buffer_size = av_samples_get_buffer_size(
NULL, swr.dst_channels, resampled_size, swr.dst_format, 1);
if (out_buffer_size > 0) {
working_buffer = Memory_Realloc(
working_buffer, working_buffer_size + out_buffer_size);
if (out_buffer) {
memcpy(
(uint8_t *)working_buffer + working_buffer_size,
out_buffer, out_buffer_size);
}
working_buffer_size += out_buffer_size;
}
resampled_size =
swr_convert(swr.ctx, &out_buffer, out_samples, NULL, 0);
}
av_freep(&out_buffer);
av_frame_unref(av.frame);
}
av_packet_unref(av.packet);
}
int32_t sample_format_bytes = av_get_bytes_per_sample(swr.dst_format);
sample->num_samples =
working_buffer_size / sample_format_bytes / swr.dst_channels;
sample->channels = swr.src_channels;
sample->sample_data = working_buffer;
result = true;
const clock_t time_end = clock();
const double time_delta =
(((double)(time_end - time_start)) / CLOCKS_PER_SEC) * 1000.0f;
LOG_DEBUG(
"Sample %d decoded (%.0f ms)", sample_id, sample->original_size,
time_delta);
cleanup:
if (error_code != 0) {
LOG_ERROR(
"Error while opening sample ID %d: %s", sample_id,
av_err2str(error_code));
}
if (swr.ctx) {
swr_free(&swr.ctx);
}
if (av.frame) {
av_frame_free(&av.frame);
}
if (av.packet) {
av_packet_free(&av.packet);
}
av.codec = NULL;
if (!result) {
sample->sample_data = NULL;
sample->original_data = NULL;
sample->original_size = 0;
sample->num_samples = 0;
sample->channels = 0;
Memory_FreePointer(&working_buffer);
}
if (av.codec_ctx) {
avcodec_close(av.codec_ctx);
av_freep(&av.codec_ctx);
}
if (av.format_ctx) {
avformat_close_input(&av.format_ctx);
}
if (av.avio_context) {
av_freep(&av.avio_context->buffer);
avio_context_free(&av.avio_context);
}
return result;
}
void Audio_Sample_Init(void)
{
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_SAMPLES;
sound_id++) {
AUDIO_SAMPLE_SOUND *sound = &m_Samples[sound_id];
sound->is_used = false;
sound->is_playing = false;
sound->volume = 0.0f;
sound->pitch = 1.0f;
sound->pan = 0.0f;
sound->current_sample = 0.0f;
sound->sample = NULL;
}
}
void Audio_Sample_Shutdown(void)
{
if (!g_AudioDeviceID) {
return;
}
Audio_Sample_CloseAll();
Audio_Sample_UnloadAll();
}
bool Audio_Sample_Unload(const int32_t sample_id)
{
if (!g_AudioDeviceID) {
LOG_ERROR("Unitialized audio device");
return false;
}
if (sample_id < 0 || sample_id >= AUDIO_MAX_SAMPLES) {
LOG_ERROR("Maximum allowed samples: %d", AUDIO_MAX_SAMPLES);
return false;
}
bool result = false;
AUDIO_SAMPLE *const sample = &m_LoadedSamples[sample_id];
if (sample->sample_data == NULL) {
LOG_ERROR("Sample %d is already unloaded", sample_id);
return false;
}
Memory_FreePointer(&sample->sample_data);
Memory_FreePointer(&sample->original_data);
m_LoadedSamplesCount--;
return true;
}
bool Audio_Sample_UnloadAll(void)
{
if (!g_AudioDeviceID) {
LOG_ERROR("Unitialized audio device");
return false;
}
m_LoadedSamplesCount = 0;
for (int32_t i = 0; i < AUDIO_MAX_SAMPLES; i++) {
AUDIO_SAMPLE *const sample = &m_LoadedSamples[i];
Memory_FreePointer(&sample->sample_data);
Memory_FreePointer(&sample->original_data);
}
return true;
}
bool Audio_Sample_LoadSingle(
const int32_t sample_id, const char *const data, const size_t size)
{
assert(data != NULL);
if (!g_AudioDeviceID) {
LOG_ERROR("Unitialized audio device");
return false;
}
if (sample_id < 0 || sample_id >= AUDIO_MAX_SAMPLES) {
LOG_ERROR("Maximum allowed samples: %d", AUDIO_MAX_SAMPLES);
return false;
}
AUDIO_SAMPLE *const sample = &m_LoadedSamples[sample_id];
if (sample->original_data != NULL) {
LOG_ERROR(
"Sample %d is already loaded (trying to overwrite with %d bytes)",
sample_id, size);
return false;
}
sample->original_data = Memory_Alloc(size);
sample->original_size = size;
memcpy(sample->original_data, data, size);
m_LoadedSamplesCount++;
LOG_ERROR("Sample %d loaded (%d bytes)", sample_id, size);
return true;
}
bool Audio_Sample_LoadMany(size_t count, const char **contents, size_t *sizes)
{
assert(contents != NULL);
assert(sizes != NULL);
if (!g_AudioDeviceID) {
return false;
}
assert(count <= AUDIO_MAX_SAMPLES);
Audio_Sample_CloseAll();
Audio_Sample_UnloadAll();
bool result = true;
for (int32_t i = 0; i < (int32_t)count; i++) {
result &= Audio_Sample_LoadSingle(i, contents[i], sizes[i]);
}
if (!result) {
Audio_Sample_UnloadAll();
}
return result;
}
int32_t Audio_Sample_Play(
int32_t sample_id, int32_t volume, float pitch, int32_t pan, bool is_looped)
{
if (!g_AudioDeviceID) {
LOG_ERROR("audio device is unavailable");
return false;
}
if (sample_id < 0 || sample_id >= m_LoadedSamplesCount) {
LOG_DEBUG("Invalid sample id: %d", sample_id);
return AUDIO_NO_SOUND;
}
int32_t result = AUDIO_NO_SOUND;
SDL_LockAudioDevice(g_AudioDeviceID);
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_SAMPLES;
sound_id++) {
AUDIO_SAMPLE_SOUND *sound = &m_Samples[sound_id];
if (sound->is_used) {
continue;
}
M_Convert(sample_id);
sound->is_used = true;
sound->is_playing = true;
sound->volume = volume;
sound->pitch = pitch;
sound->pan = pan;
sound->is_looped = is_looped;
sound->current_sample = 0.0f;
sound->sample = &m_LoadedSamples[sample_id];
M_RecalculateChannelVolumes(sound_id);
result = sound_id;
break;
}
SDL_UnlockAudioDevice(g_AudioDeviceID);
if (result == AUDIO_NO_SOUND) {
LOG_ERROR("All sample buffers are used!");
}
return result;
}
bool Audio_Sample_IsPlaying(int32_t sound_id)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_SAMPLES) {
return false;
}
return m_Samples[sound_id].is_playing;
}
bool Audio_Sample_Pause(int32_t sound_id)
{
if (!g_AudioDeviceID) {
return false;
}
if (m_Samples[sound_id].is_playing) {
SDL_LockAudioDevice(g_AudioDeviceID);
m_Samples[sound_id].is_playing = false;
SDL_UnlockAudioDevice(g_AudioDeviceID);
}
return true;
}
bool Audio_Sample_PauseAll(void)
{
if (!g_AudioDeviceID) {
return false;
}
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_SAMPLES;
sound_id++) {
if (m_Samples[sound_id].is_used) {
Audio_Sample_Pause(sound_id);
}
}
return true;
}
bool Audio_Sample_Unpause(int32_t sound_id)
{
if (!g_AudioDeviceID) {
return false;
}
if (!m_Samples[sound_id].is_playing) {
SDL_LockAudioDevice(g_AudioDeviceID);
m_Samples[sound_id].is_playing = true;
SDL_UnlockAudioDevice(g_AudioDeviceID);
}
return true;
}
bool Audio_Sample_UnpauseAll(void)
{
if (!g_AudioDeviceID) {
return false;
}
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_SAMPLES;
sound_id++) {
if (m_Samples[sound_id].is_used) {
Audio_Sample_Unpause(sound_id);
}
}
return true;
}
bool Audio_Sample_Close(int32_t sound_id)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_SAMPLES) {
return false;
}
SDL_LockAudioDevice(g_AudioDeviceID);
m_Samples[sound_id].is_used = false;
m_Samples[sound_id].is_playing = false;
SDL_UnlockAudioDevice(g_AudioDeviceID);
return true;
}
bool Audio_Sample_CloseAll(void)
{
if (!g_AudioDeviceID) {
return false;
}
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_SAMPLES;
sound_id++) {
if (m_Samples[sound_id].is_used) {
Audio_Sample_Close(sound_id);
}
}
return true;
}
bool Audio_Sample_SetPan(int32_t sound_id, int32_t pan)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_SAMPLES) {
return false;
}
SDL_LockAudioDevice(g_AudioDeviceID);
m_Samples[sound_id].pan = pan;
M_RecalculateChannelVolumes(sound_id);
SDL_UnlockAudioDevice(g_AudioDeviceID);
return true;
}
bool Audio_Sample_SetVolume(int32_t sound_id, int32_t volume)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_SAMPLES) {
return false;
}
SDL_LockAudioDevice(g_AudioDeviceID);
m_Samples[sound_id].volume = volume;
M_RecalculateChannelVolumes(sound_id);
SDL_UnlockAudioDevice(g_AudioDeviceID);
return true;
}
bool Audio_Sample_SetPitch(int32_t sound_id, float pitch)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_SAMPLES) {
return false;
}
SDL_LockAudioDevice(g_AudioDeviceID);
m_Samples[sound_id].pitch = pitch;
M_RecalculateChannelVolumes(sound_id);
SDL_UnlockAudioDevice(g_AudioDeviceID);
return true;
}
void Audio_Sample_Mix(float *dst_buffer, size_t len)
{
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_SAMPLES;
sound_id++) {
AUDIO_SAMPLE_SOUND *sound = &m_Samples[sound_id];
if (!sound->is_playing) {
continue;
}
int32_t samples_requested =
len / sizeof(AUDIO_WORKING_FORMAT) / AUDIO_WORKING_CHANNELS;
float src_sample_idx = sound->current_sample;
const float *src_buffer = sound->sample->sample_data;
float *dst_ptr = dst_buffer;
while ((dst_ptr - dst_buffer) / AUDIO_WORKING_CHANNELS
< samples_requested) {
// because we handle 3d sound ourselves, downmix to mono
float src_sample = 0.0f;
for (int32_t i = 0; i < sound->sample->channels; i++) {
src_sample += src_buffer
[(int32_t)src_sample_idx * sound->sample->channels + i];
}
src_sample /= (float)sound->sample->channels;
*dst_ptr++ += src_sample * sound->volume_l;
*dst_ptr++ += src_sample * sound->volume_r;
src_sample_idx += sound->pitch;
if ((int32_t)src_sample_idx >= sound->sample->num_samples) {
if (sound->is_looped) {
src_sample_idx = 0.0f;
} else {
break;
}
}
}
sound->current_sample = src_sample_idx;
if (sound->current_sample >= sound->sample->num_samples
&& !sound->is_looped) {
Audio_Sample_Close(sound_id);
}
}
}

View file

@ -0,0 +1,719 @@
#include "audio_internal.h"
#include "filesystem.h"
#include "log.h"
#include "memory.h"
#include <SDL2/SDL_audio.h>
#include <SDL2/SDL_error.h>
#include <assert.h>
#include <errno.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/codec.h>
#include <libavcodec/packet.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/avutil.h>
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavutil/rational.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define READ_BUFFER_SIZE \
(AUDIO_SAMPLES * AUDIO_WORKING_CHANNELS * sizeof(AUDIO_WORKING_FORMAT))
typedef struct {
bool is_used;
bool is_playing;
bool is_read_done;
bool is_looped;
float volume;
double duration;
double timestamp;
double start_at;
double stop_at;
void (*finish_callback)(int32_t sound_id, void *user_data);
void *finish_callback_user_data;
struct {
AVStream *stream;
AVFormatContext *format_ctx;
const AVCodec *codec;
AVCodecContext *codec_ctx;
AVPacket *packet;
AVFrame *frame;
} av;
struct {
int32_t src_format;
int32_t src_channels;
int32_t src_sample_rate;
int32_t dst_format;
int32_t dst_channels;
int32_t dst_sample_rate;
SwrContext *ctx;
} swr;
struct {
SDL_AudioStream *stream;
} sdl;
} AUDIO_STREAM_SOUND;
extern SDL_AudioDeviceID g_AudioDeviceID;
static AUDIO_STREAM_SOUND m_Streams[AUDIO_MAX_ACTIVE_STREAMS] = { 0 };
static float m_MixBuffer[AUDIO_SAMPLES * AUDIO_WORKING_CHANNELS] = { 0 };
static size_t m_DecodeBufferCapacity = 0;
static float *m_DecodeBuffer = NULL;
static void M_SeekToStart(AUDIO_STREAM_SOUND *stream);
static bool M_DecodeFrame(AUDIO_STREAM_SOUND *stream);
static bool M_EnqueueFrame(AUDIO_STREAM_SOUND *stream);
static bool M_InitialiseFromPath(int32_t sound_id, const char *file_path);
static void M_Clear(AUDIO_STREAM_SOUND *stream);
static void M_SeekToStart(AUDIO_STREAM_SOUND *stream)
{
assert(stream != NULL);
stream->timestamp = stream->start_at;
if (stream->start_at <= 0.0) {
// reset to start of file
avio_seek(stream->av.format_ctx->pb, 0, SEEK_SET);
avformat_seek_file(
stream->av.format_ctx, -1, 0, 0, 0, AVSEEK_FLAG_FRAME);
} else {
// seek to specific timestamp
const double time_base_sec = av_q2d(stream->av.stream->time_base);
av_seek_frame(
stream->av.format_ctx, 0, stream->start_at / time_base_sec,
AVSEEK_FLAG_ANY);
}
}
static bool M_DecodeFrame(AUDIO_STREAM_SOUND *stream)
{
assert(stream != NULL);
if (stream->stop_at > 0.0 && stream->timestamp >= stream->stop_at) {
if (stream->is_looped) {
M_SeekToStart(stream);
return M_DecodeFrame(stream);
} else {
return false;
}
}
int32_t error_code =
av_read_frame(stream->av.format_ctx, stream->av.packet);
if (error_code == AVERROR_EOF && stream->is_looped) {
M_SeekToStart(stream);
return M_DecodeFrame(stream);
}
if (error_code < 0) {
LOG_ERROR("error while decoding audio stream: %d", error_code);
return false;
}
if (stream->av.packet->stream_index != stream->av.stream->index) {
return true;
}
error_code = avcodec_send_packet(stream->av.codec_ctx, stream->av.packet);
if (error_code < 0) {
av_packet_unref(stream->av.packet);
LOG_ERROR(
"Got an error when decoding frame: %s", av_err2str(error_code));
return false;
}
return true;
}
static bool M_EnqueueFrame(AUDIO_STREAM_SOUND *stream)
{
assert(stream != NULL);
int32_t error_code;
if (!stream->swr.ctx) {
stream->swr.src_sample_rate = stream->av.codec_ctx->sample_rate;
stream->swr.src_channels = stream->av.codec_ctx->channels;
stream->swr.src_format = stream->av.codec_ctx->sample_fmt;
stream->swr.dst_sample_rate = AUDIO_WORKING_RATE;
stream->swr.dst_channels = AUDIO_WORKING_CHANNELS;
stream->swr.dst_format = Audio_GetAVAudioFormat(AUDIO_WORKING_FORMAT);
stream->swr.ctx = swr_alloc_set_opts(
stream->swr.ctx, Audio_GetAVChannelLayout(stream->swr.dst_channels),
stream->swr.dst_format, stream->swr.dst_sample_rate,
Audio_GetAVChannelLayout(stream->swr.src_channels),
stream->swr.src_format, stream->swr.src_sample_rate, 0, 0);
if (!stream->swr.ctx) {
av_packet_unref(stream->av.packet);
error_code = AVERROR(ENOMEM);
goto cleanup;
}
error_code = swr_init(stream->swr.ctx);
if (error_code != 0) {
av_packet_unref(stream->av.packet);
goto cleanup;
}
}
while (1) {
error_code =
avcodec_receive_frame(stream->av.codec_ctx, stream->av.frame);
if (error_code == AVERROR(EAGAIN)) {
av_frame_unref(stream->av.frame);
error_code = 0;
break;
}
if (error_code < 0) {
av_frame_unref(stream->av.frame);
break;
}
uint8_t *out_buffer = NULL;
const int32_t out_samples =
swr_get_out_samples(stream->swr.ctx, stream->av.frame->nb_samples);
av_samples_alloc(
&out_buffer, NULL, stream->swr.dst_channels, out_samples,
stream->swr.dst_format, 1);
int32_t resampled_size = swr_convert(
stream->swr.ctx, &out_buffer, out_samples,
(const uint8_t **)stream->av.frame->data,
stream->av.frame->nb_samples);
size_t out_pos = 0;
while (resampled_size > 0) {
const size_t out_buffer_size = av_samples_get_buffer_size(
NULL, stream->swr.dst_channels, resampled_size,
stream->swr.dst_format, 1);
if (out_pos + out_buffer_size > m_DecodeBufferCapacity) {
m_DecodeBufferCapacity = out_pos + out_buffer_size;
m_DecodeBuffer =
Memory_Realloc(m_DecodeBuffer, m_DecodeBufferCapacity);
}
if (out_buffer) {
memcpy(
(uint8_t *)m_DecodeBuffer + out_pos, out_buffer,
out_buffer_size);
}
out_pos += out_buffer_size;
resampled_size =
swr_convert(stream->swr.ctx, &out_buffer, out_samples, NULL, 0);
}
if (SDL_AudioStreamPut(stream->sdl.stream, m_DecodeBuffer, out_pos)) {
LOG_ERROR("Got an error when decoding frame: %s", SDL_GetError());
av_frame_unref(stream->av.frame);
break;
}
double time_base_sec = av_q2d(stream->av.stream->time_base);
stream->timestamp =
stream->av.frame->best_effort_timestamp * time_base_sec;
av_freep(&out_buffer);
av_frame_unref(stream->av.frame);
}
av_packet_unref(stream->av.packet);
cleanup:
if (error_code > 0) {
LOG_ERROR(
"Got an error when decoding frame: %d, %s", error_code,
av_err2str(error_code));
}
return true;
}
static bool M_InitialiseFromPath(int32_t sound_id, const char *file_path)
{
assert(file_path != NULL);
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
bool ret = false;
SDL_LockAudioDevice(g_AudioDeviceID);
int32_t error_code;
char *full_path = File_GetFullPath(file_path);
AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id];
error_code =
avformat_open_input(&stream->av.format_ctx, full_path, NULL, NULL);
if (error_code != 0) {
goto cleanup;
}
error_code = avformat_find_stream_info(stream->av.format_ctx, NULL);
if (error_code < 0) {
goto cleanup;
}
stream->av.stream = NULL;
for (uint32_t i = 0; i < stream->av.format_ctx->nb_streams; i++) {
AVStream *current_stream = stream->av.format_ctx->streams[i];
if (current_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
stream->av.stream = current_stream;
break;
}
}
if (!stream->av.stream) {
error_code = AVERROR_STREAM_NOT_FOUND;
goto cleanup;
}
stream->av.codec =
avcodec_find_decoder(stream->av.stream->codecpar->codec_id);
if (!stream->av.codec) {
error_code = AVERROR_DEMUXER_NOT_FOUND;
goto cleanup;
}
stream->av.codec_ctx = avcodec_alloc_context3(stream->av.codec);
if (!stream->av.codec_ctx) {
error_code = AVERROR(ENOMEM);
goto cleanup;
}
error_code = avcodec_parameters_to_context(
stream->av.codec_ctx, stream->av.stream->codecpar);
if (error_code) {
goto cleanup;
}
error_code = avcodec_open2(stream->av.codec_ctx, stream->av.codec, NULL);
if (error_code < 0) {
goto cleanup;
}
stream->av.packet = av_packet_alloc();
if (!stream->av.packet) {
error_code = AVERROR(ENOMEM);
goto cleanup;
}
stream->av.frame = av_frame_alloc();
if (!stream->av.frame) {
error_code = AVERROR(ENOMEM);
goto cleanup;
}
M_DecodeFrame(stream);
int32_t sdl_sample_rate = stream->av.codec_ctx->sample_rate;
int32_t sdl_channels = stream->av.codec_ctx->channels;
stream->is_read_done = false;
stream->is_used = true;
stream->is_playing = true;
stream->is_looped = false;
stream->volume = 1.0f;
stream->timestamp = 0.0;
stream->finish_callback = NULL;
stream->finish_callback_user_data = NULL;
stream->duration =
(double)stream->av.format_ctx->duration / (double)AV_TIME_BASE;
stream->start_at = -1.0; // negative value means unset
stream->stop_at = -1.0; // negative value means unset
stream->sdl.stream = SDL_NewAudioStream(
AUDIO_WORKING_FORMAT, sdl_channels, AUDIO_WORKING_RATE,
AUDIO_WORKING_FORMAT, sdl_channels, AUDIO_WORKING_RATE);
if (!stream->sdl.stream) {
LOG_ERROR("Failed to create SDL stream: %s", SDL_GetError());
goto cleanup;
}
ret = true;
M_EnqueueFrame(stream);
cleanup:
if (error_code) {
LOG_ERROR(
"Error while opening audio %s: %s", file_path,
av_err2str(error_code));
}
if (!ret) {
Audio_Stream_Close(sound_id);
}
SDL_UnlockAudioDevice(g_AudioDeviceID);
Memory_FreePointer(&full_path);
return ret;
}
static void M_Clear(AUDIO_STREAM_SOUND *stream)
{
assert(stream != NULL);
stream->is_used = false;
stream->is_playing = false;
stream->is_read_done = true;
stream->is_looped = false;
stream->volume = 0.0f;
stream->duration = 0.0;
stream->timestamp = 0.0;
stream->sdl.stream = NULL;
stream->finish_callback = NULL;
stream->finish_callback_user_data = NULL;
}
void Audio_Stream_Init(void)
{
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_STREAMS;
sound_id++) {
M_Clear(&m_Streams[sound_id]);
}
}
void Audio_Stream_Shutdown(void)
{
Memory_FreePointer(&m_DecodeBuffer);
if (!g_AudioDeviceID) {
return;
}
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_STREAMS;
sound_id++) {
if (m_Streams[sound_id].is_used) {
Audio_Stream_Close(sound_id);
}
}
}
bool Audio_Stream_Pause(int32_t sound_id)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
if (m_Streams[sound_id].is_playing) {
SDL_LockAudioDevice(g_AudioDeviceID);
m_Streams[sound_id].is_playing = false;
SDL_UnlockAudioDevice(g_AudioDeviceID);
}
return true;
}
bool Audio_Stream_Unpause(int32_t sound_id)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
if (!m_Streams[sound_id].is_playing) {
SDL_LockAudioDevice(g_AudioDeviceID);
m_Streams[sound_id].is_playing = true;
SDL_UnlockAudioDevice(g_AudioDeviceID);
}
return true;
}
int32_t Audio_Stream_CreateFromFile(const char *file_path)
{
if (!g_AudioDeviceID) {
return AUDIO_NO_SOUND;
}
assert(file_path != NULL);
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_STREAMS;
sound_id++) {
AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id];
if (stream->is_used) {
continue;
}
if (!M_InitialiseFromPath(sound_id, file_path)) {
return AUDIO_NO_SOUND;
}
return sound_id;
}
return AUDIO_NO_SOUND;
}
bool Audio_Stream_Close(int32_t sound_id)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
SDL_LockAudioDevice(g_AudioDeviceID);
AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id];
if (stream->av.codec_ctx) {
avcodec_close(stream->av.codec_ctx);
av_free(stream->av.codec_ctx);
stream->av.codec_ctx = NULL;
}
if (stream->av.format_ctx) {
avformat_close_input(&stream->av.format_ctx);
stream->av.format_ctx = NULL;
}
if (stream->swr.ctx) {
swr_free(&stream->swr.ctx);
}
if (stream->av.frame) {
av_frame_free(&stream->av.frame);
stream->av.frame = NULL;
}
if (stream->av.packet) {
av_packet_free(&stream->av.packet);
stream->av.packet = NULL;
}
stream->av.stream = NULL;
stream->av.codec = NULL;
if (stream->sdl.stream) {
SDL_FreeAudioStream(stream->sdl.stream);
}
void (*finish_callback)(int32_t, void *) = stream->finish_callback;
void *finish_callback_user_data = stream->finish_callback_user_data;
M_Clear(stream);
SDL_UnlockAudioDevice(g_AudioDeviceID);
if (finish_callback) {
finish_callback(sound_id, finish_callback_user_data);
}
return true;
}
bool Audio_Stream_SetVolume(int32_t sound_id, float volume)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
m_Streams[sound_id].volume = volume;
return true;
}
bool Audio_Stream_IsLooped(int32_t sound_id)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
return m_Streams[sound_id].is_looped;
}
bool Audio_Stream_SetIsLooped(int32_t sound_id, bool is_looped)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
m_Streams[sound_id].is_looped = is_looped;
return true;
}
bool Audio_Stream_SetFinishCallback(
int32_t sound_id, void (*callback)(int32_t sound_id, void *user_data),
void *user_data)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
m_Streams[sound_id].finish_callback = callback;
m_Streams[sound_id].finish_callback_user_data = user_data;
return true;
}
void Audio_Stream_Mix(float *dst_buffer, size_t len)
{
for (int32_t sound_id = 0; sound_id < AUDIO_MAX_ACTIVE_STREAMS;
sound_id++) {
AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id];
if (!stream->is_playing) {
continue;
}
while ((SDL_AudioStreamAvailable(stream->sdl.stream) < (int32_t)len)
&& !stream->is_read_done) {
if (M_DecodeFrame(stream)) {
M_EnqueueFrame(stream);
} else {
stream->is_read_done = true;
}
}
memset(m_MixBuffer, 0, READ_BUFFER_SIZE);
int32_t bytes_gotten = SDL_AudioStreamGet(
stream->sdl.stream, m_MixBuffer, READ_BUFFER_SIZE);
if (bytes_gotten < 0) {
LOG_ERROR("Error reading from sdl.stream: %s", SDL_GetError());
stream->is_playing = false;
stream->is_used = false;
stream->is_read_done = true;
} else if (bytes_gotten == 0) {
// legit end of stream. looping is handled in
// M_DecodeFrame
stream->is_playing = false;
stream->is_used = false;
stream->is_read_done = true;
} else {
int32_t samples_gotten = bytes_gotten
/ (AUDIO_WORKING_CHANNELS * sizeof(AUDIO_WORKING_FORMAT));
const float *src_ptr = &m_MixBuffer[0];
float *dst_ptr = dst_buffer;
if (stream->av.codec_ctx->channels == AUDIO_WORKING_CHANNELS) {
for (int32_t s = 0; s < samples_gotten; s++) {
for (int32_t c = 0; c < AUDIO_WORKING_CHANNELS; c++) {
*dst_ptr++ += *src_ptr++ * stream->volume;
}
}
} else if (stream->av.codec_ctx->channels == 1) {
for (int32_t s = 0; s < samples_gotten; s++) {
for (int32_t c = 0; c < AUDIO_WORKING_CHANNELS; c++) {
*dst_ptr++ += *src_ptr * stream->volume;
}
src_ptr++;
}
} else {
for (int32_t s = 0; s < samples_gotten; s++) {
// downmix to mono
float src_sample = 0.0f;
for (int32_t i = 0; i < stream->av.codec_ctx->channels;
i++) {
src_sample += *src_ptr++;
}
src_sample /= (float)stream->av.codec_ctx->channels;
for (int32_t c = 0; c < AUDIO_WORKING_CHANNELS; c++) {
*dst_ptr++ += src_sample * stream->volume;
}
}
}
}
if (!stream->is_used) {
Audio_Stream_Close(sound_id);
}
}
}
double Audio_Stream_GetTimestamp(int32_t sound_id)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return -1.0;
}
double timestamp = -1.0;
AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id];
if (stream->duration > 0.0) {
SDL_LockAudioDevice(g_AudioDeviceID);
timestamp = stream->timestamp;
SDL_UnlockAudioDevice(g_AudioDeviceID);
}
return timestamp;
}
double Audio_Stream_GetDuration(int32_t sound_id)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return -1.0;
}
SDL_LockAudioDevice(g_AudioDeviceID);
AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id];
double duration = stream->duration;
SDL_UnlockAudioDevice(g_AudioDeviceID);
return duration;
}
bool Audio_Stream_SeekTimestamp(int32_t sound_id, double timestamp)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
if (m_Streams[sound_id].is_playing) {
SDL_LockAudioDevice(g_AudioDeviceID);
AUDIO_STREAM_SOUND *stream = &m_Streams[sound_id];
const double time_base_sec = av_q2d(stream->av.stream->time_base);
av_seek_frame(
stream->av.format_ctx, 0, timestamp / time_base_sec,
AVSEEK_FLAG_ANY);
SDL_UnlockAudioDevice(g_AudioDeviceID);
return true;
}
return false;
}
bool Audio_Stream_SetStartTimestamp(int32_t sound_id, double timestamp)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
m_Streams[sound_id].start_at = timestamp;
return true;
}
bool Audio_Stream_SetStopTimestamp(int32_t sound_id, double timestamp)
{
if (!g_AudioDeviceID || sound_id < 0
|| sound_id >= AUDIO_MAX_ACTIVE_STREAMS) {
return false;
}
m_Streams[sound_id].stop_at = timestamp;
return true;
}

560
src/libtrx/engine/image.c Normal file
View file

@ -0,0 +1,560 @@
#include "engine/image.h"
#include "filesystem.h"
#include "log.h"
#include "memory.h"
#include <assert.h>
#include <errno.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/codec.h>
#include <libavcodec/codec_id.h>
#include <libavcodec/packet.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/imgutils.h>
#include <libavutil/mem.h>
#include <libavutil/pixfmt.h>
#include <libavutil/rational.h>
#include <libswscale/swscale.h>
#include <stdint.h>
#include <string.h>
typedef struct {
struct {
int32_t x;
int32_t y;
int32_t width;
int32_t height;
} src, dst;
} IMAGE_BLIT;
typedef struct {
int error_code;
AVFormatContext *format_ctx;
AVCodecContext *codec_ctx;
const AVCodec *codec;
AVFrame *frame;
AVPacket *packet;
} IMAGE_READER_CONTEXT;
static bool M_Init(const char *path, IMAGE_READER_CONTEXT *ctx);
static void M_Free(IMAGE_READER_CONTEXT *ctx);
static IMAGE *M_ConstructImage(
IMAGE_READER_CONTEXT *ctx, int32_t target_width, int32_t target_height,
IMAGE_FIT_MODE fit_mode);
static IMAGE_BLIT M_GetBlit(
int32_t source_width, int32_t source_height, int32_t target_width,
int32_t target_height, IMAGE_FIT_MODE fit_mode);
static bool M_Init(const char *const path, IMAGE_READER_CONTEXT *const ctx)
{
assert(ctx != NULL);
ctx->format_ctx = NULL;
ctx->codec = NULL;
ctx->codec_ctx = NULL;
ctx->frame = NULL;
ctx->packet = NULL;
char *full_path = File_GetFullPath(path);
int32_t error_code =
avformat_open_input(&ctx->format_ctx, full_path, NULL, NULL);
Memory_FreePointer(&full_path);
if (error_code != 0) {
goto finish;
}
#if 0
error_code = avformat_find_stream_info(format_ctx, NULL);
if (error_code < 0) {
goto finish;
}
#endif
AVStream *video_stream = NULL;
for (unsigned int i = 0; i < ctx->format_ctx->nb_streams; i++) {
AVStream *current_stream = ctx->format_ctx->streams[i];
if (current_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream = current_stream;
break;
}
}
if (video_stream == NULL) {
error_code = AVERROR_STREAM_NOT_FOUND;
goto finish;
}
ctx->codec = avcodec_find_decoder(video_stream->codecpar->codec_id);
if (ctx->codec == NULL) {
error_code = AVERROR_DEMUXER_NOT_FOUND;
goto finish;
}
ctx->codec_ctx = avcodec_alloc_context3(ctx->codec);
if (ctx->codec_ctx == NULL) {
error_code = AVERROR(ENOMEM);
goto finish;
}
error_code =
avcodec_parameters_to_context(ctx->codec_ctx, video_stream->codecpar);
if (error_code) {
goto finish;
}
#if 0
ctx->codec_ctx->thread_count = 0;
if (ctx->codec->capabilities & AV_CODEC_CAP_FRAME_THREADS)
ctx->codec_ctx->thread_type = FF_THREAD_FRAME;
else if (ctx->codec->capabilities & AV_CODEC_CAP_SLICE_THREADS)
ctx->codec_ctx->thread_type = FF_THREAD_SLICE;
else
ctx->codec_ctx->thread_count = 1; //don't use multithreading
#endif
error_code = avcodec_open2(ctx->codec_ctx, ctx->codec, NULL);
if (error_code < 0) {
goto finish;
}
ctx->packet = av_packet_alloc();
av_new_packet(ctx->packet, 0);
error_code = av_read_frame(ctx->format_ctx, ctx->packet);
if (error_code < 0) {
goto finish;
}
error_code = avcodec_send_packet(ctx->codec_ctx, ctx->packet);
if (error_code < 0) {
goto finish;
}
ctx->frame = av_frame_alloc();
if (ctx->frame == NULL) {
error_code = AVERROR(ENOMEM);
goto finish;
}
error_code = avcodec_receive_frame(ctx->codec_ctx, ctx->frame);
if (error_code < 0) {
goto finish;
}
error_code = 0;
finish:
if (error_code != 0) {
LOG_ERROR(
"Error while opening image %s: %s", path, av_err2str(error_code));
M_Free(ctx);
return false;
}
return true;
}
static void M_Free(IMAGE_READER_CONTEXT *const ctx)
{
if (ctx->packet != NULL) {
av_packet_free(&ctx->packet);
}
if (ctx->frame != NULL) {
av_frame_free(&ctx->frame);
}
if (ctx->codec_ctx != NULL) {
avcodec_close(ctx->codec_ctx);
av_free(ctx->codec_ctx);
ctx->codec_ctx = NULL;
}
if (ctx->format_ctx != NULL) {
avformat_close_input(&ctx->format_ctx);
}
}
IMAGE *Image_Create(const int width, const int height)
{
IMAGE *image = Memory_Alloc(sizeof(IMAGE));
image->width = width;
image->height = height;
image->data = Memory_Alloc(width * height * sizeof(IMAGE_PIXEL));
return image;
}
static IMAGE *M_ConstructImage(
IMAGE_READER_CONTEXT *const ctx, const int32_t target_width,
const int32_t target_height, IMAGE_FIT_MODE fit_mode)
{
assert(ctx != NULL);
assert(target_width > 0);
assert(target_height > 0);
IMAGE_BLIT blit = M_GetBlit(
ctx->frame->width, ctx->frame->height, target_width, target_height,
fit_mode);
if (blit.src.y != 0 || blit.src.x != 0) {
ctx->frame->crop_top = blit.src.y;
ctx->frame->crop_left = blit.src.x;
av_frame_apply_cropping(ctx->frame, AV_FRAME_CROP_UNALIGNED);
}
struct SwsContext *const sws_ctx = sws_getContext(
blit.src.width, blit.src.height, ctx->frame->format, blit.dst.width,
blit.dst.height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);
if (sws_ctx == NULL) {
LOG_ERROR("Failed to get SWS context");
return NULL;
}
IMAGE *const target_image = Image_Create(target_width, target_height);
uint8_t *dst_planes[4] = { (uint8_t *)target_image->data
+ (blit.dst.y * target_image->width
+ blit.dst.x)
* sizeof(IMAGE_PIXEL),
NULL, NULL, NULL };
int dst_linesize[4] = { target_image->width * sizeof(IMAGE_PIXEL), 0, 0,
0 };
sws_scale(
sws_ctx, (const uint8_t *const *)ctx->frame->data, ctx->frame->linesize,
0, blit.src.height, dst_planes, dst_linesize);
sws_freeContext(sws_ctx);
return target_image;
}
static IMAGE_BLIT M_GetBlit(
const int32_t source_width, const int32_t source_height,
const int32_t target_width, const int32_t target_height,
IMAGE_FIT_MODE fit_mode)
{
const float source_ratio = source_width / (float)source_height;
const float target_ratio = target_width / (float)target_height;
if (fit_mode == IMAGE_FIT_SMART) {
const float ar_diff =
(source_ratio > target_ratio ? source_ratio / target_ratio
: target_ratio / source_ratio)
- 1.0f;
if (ar_diff <= 0.1f) {
// if the difference between aspect ratios is under 10%, just
// stretch it
fit_mode = IMAGE_FIT_STRETCH;
} else if (source_ratio <= target_ratio) {
// if the viewport is too wide, center the image
fit_mode = IMAGE_FIT_LETTERBOX;
} else {
// if the image is too wide, crop the image
fit_mode = IMAGE_FIT_CROP;
}
}
IMAGE_BLIT blit;
switch (fit_mode) {
case IMAGE_FIT_STRETCH:
blit.src.width = source_width;
blit.src.height = source_height;
blit.src.x = 0;
blit.src.y = 0;
blit.dst.width = target_width;
blit.dst.height = target_height;
blit.dst.x = 0;
blit.dst.y = 0;
break;
case IMAGE_FIT_CROP:
blit.src.width = source_ratio < target_ratio
? source_width
: source_height * target_ratio;
blit.src.height = source_ratio < target_ratio
? source_width / target_ratio
: source_height;
blit.src.x = (source_width - blit.src.width) / 2;
blit.src.y = (source_height - blit.src.height) / 2;
blit.dst.width = target_width;
blit.dst.height = target_height;
blit.dst.x = 0;
blit.dst.y = 0;
break;
case IMAGE_FIT_LETTERBOX:
blit.src.width = source_width;
blit.src.height = source_height;
blit.src.x = 0;
blit.src.y = 0;
blit.dst.width = (source_ratio > target_ratio)
? target_width
: target_height * source_ratio;
blit.dst.height = (source_ratio > target_ratio)
? target_width / source_ratio
: target_height;
blit.dst.x = (target_width - blit.dst.width) / 2;
blit.dst.y = (target_height - blit.dst.height) / 2;
break;
default:
assert(false);
break;
}
return blit;
}
IMAGE *Image_CreateFromFile(const char *const path)
{
assert(path != NULL);
IMAGE_READER_CONTEXT ctx;
if (!M_Init(path, &ctx)) {
return NULL;
}
IMAGE *target_image = M_ConstructImage(
&ctx, ctx.frame->width, ctx.frame->height, IMAGE_FIT_STRETCH);
M_Free(&ctx);
return target_image;
}
IMAGE *Image_CreateFromFileInto(
const char *const path, const int32_t target_width,
const int32_t target_height, const IMAGE_FIT_MODE fit_mode)
{
assert(path != NULL);
IMAGE_READER_CONTEXT ctx;
if (!M_Init(path, &ctx)) {
return NULL;
}
IMAGE *target_image =
M_ConstructImage(&ctx, target_width, target_height, fit_mode);
M_Free(&ctx);
return target_image;
}
bool Image_SaveToFile(const IMAGE *const image, const char *const path)
{
assert(image);
assert(path);
bool result = false;
int error_code = 0;
const AVCodec *codec = NULL;
AVCodecContext *codec_ctx = NULL;
AVFrame *frame = NULL;
AVPacket *packet = NULL;
struct SwsContext *sws_ctx = NULL;
MYFILE *fp = NULL;
enum AVPixelFormat src_pix_fmt = AV_PIX_FMT_RGB24;
enum AVPixelFormat dst_pix_fmt;
enum AVCodecID codec_id;
if (strstr(path, ".jpg")) {
dst_pix_fmt = AV_PIX_FMT_YUVJ420P;
codec_id = AV_CODEC_ID_MJPEG;
} else if (strstr(path, ".png")) {
dst_pix_fmt = AV_PIX_FMT_RGB24;
codec_id = AV_CODEC_ID_PNG;
} else {
LOG_ERROR("Cannot determine image format based on path '%s'", path);
goto cleanup;
}
fp = File_Open(path, FILE_OPEN_WRITE);
if (fp == NULL) {
LOG_ERROR("Cannot create image file: %s", path);
goto cleanup;
}
codec = avcodec_find_encoder(codec_id);
if (codec == NULL) {
error_code = AVERROR_MUXER_NOT_FOUND;
goto cleanup;
}
codec_ctx = avcodec_alloc_context3(codec);
if (codec_ctx == NULL) {
error_code = AVERROR(ENOMEM);
goto cleanup;
}
codec_ctx->bit_rate = 400000;
codec_ctx->width = image->width;
codec_ctx->height = image->height;
codec_ctx->time_base = (AVRational) { 1, 25 };
codec_ctx->pix_fmt = dst_pix_fmt;
if (codec_id == AV_CODEC_ID_MJPEG) {
// 9 JPEG quality
codec_ctx->flags |= AV_CODEC_FLAG_QSCALE;
codec_ctx->global_quality = FF_QP2LAMBDA * 9;
}
error_code = avcodec_open2(codec_ctx, codec, NULL);
if (error_code < 0) {
goto cleanup;
}
frame = av_frame_alloc();
if (frame == NULL) {
error_code = AVERROR(ENOMEM);
goto cleanup;
}
frame->format = codec_ctx->pix_fmt;
frame->width = codec_ctx->width;
frame->height = codec_ctx->height;
frame->pts = 0;
error_code = av_image_alloc(
frame->data, frame->linesize, codec_ctx->width, codec_ctx->height,
codec_ctx->pix_fmt, 32);
if (error_code < 0) {
goto cleanup;
}
packet = av_packet_alloc();
av_new_packet(packet, 0);
sws_ctx = sws_getContext(
image->width, image->height, src_pix_fmt, frame->width, frame->height,
dst_pix_fmt, SWS_BILINEAR, NULL, NULL, NULL);
if (sws_ctx == NULL) {
LOG_ERROR("Failed to get SWS context");
error_code = AVERROR_EXTERNAL;
goto cleanup;
}
uint8_t *src_planes[4];
int src_linesize[4];
av_image_fill_arrays(
src_planes, src_linesize, (const uint8_t *)image->data, src_pix_fmt,
image->width, image->height, 1);
sws_scale(
sws_ctx, (const uint8_t *const *)src_planes, src_linesize, 0,
image->height, frame->data, frame->linesize);
error_code = avcodec_send_frame(codec_ctx, frame);
if (error_code < 0) {
goto cleanup;
}
while (error_code >= 0) {
error_code = avcodec_receive_packet(codec_ctx, packet);
if (error_code == AVERROR(EAGAIN) || error_code == AVERROR_EOF) {
error_code = 0;
break;
}
if (error_code < 0) {
goto cleanup;
}
File_WriteData(fp, packet->data, packet->size);
av_packet_unref(packet);
}
cleanup:
if (error_code) {
LOG_ERROR(
"Error while saving image %s: %s", path, av_err2str(error_code));
} else {
result = true;
}
if (fp) {
File_Close(fp);
fp = NULL;
}
if (sws_ctx) {
sws_freeContext(sws_ctx);
}
if (packet) {
av_packet_free(&packet);
}
if (codec) {
avcodec_close(codec_ctx);
av_free(codec_ctx);
codec_ctx = NULL;
}
if (frame) {
av_freep(&frame->data[0]);
av_frame_free(&frame);
}
return result;
}
IMAGE *Image_Scale(
const IMAGE *const source_image, size_t target_width, size_t target_height,
IMAGE_FIT_MODE fit_mode)
{
assert(source_image);
assert(source_image->data);
assert(target_width > 0);
assert(target_height > 0);
IMAGE_BLIT blit = M_GetBlit(
source_image->width, source_image->height, target_width, target_height,
fit_mode);
struct SwsContext *const sws_ctx = sws_getContext(
blit.src.width, blit.src.height, AV_PIX_FMT_RGB24, blit.dst.width,
blit.dst.height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);
if (sws_ctx == NULL) {
LOG_ERROR("Failed to get SWS context");
return NULL;
}
IMAGE *const target_image = Image_Create(target_width, target_height);
uint8_t *src_planes[4] = { (uint8_t *)source_image->data
+ (blit.src.y * source_image->width
+ blit.src.x)
* sizeof(IMAGE_PIXEL),
NULL, NULL, NULL };
int src_linesize[4] = { source_image->width * sizeof(IMAGE_PIXEL), 0, 0,
0 };
uint8_t *dst_planes[4] = { (uint8_t *)target_image->data
+ (blit.dst.y * target_image->width
+ blit.dst.x)
* sizeof(IMAGE_PIXEL),
NULL, NULL, NULL };
int dst_linesize[4] = { target_image->width * sizeof(IMAGE_PIXEL), 0, 0,
0 };
sws_scale(
sws_ctx, (const uint8_t *const *)src_planes, src_linesize, 0,
blit.src.height, (uint8_t *const *)dst_planes, dst_linesize);
sws_freeContext(sws_ctx);
return target_image;
}
void Image_Free(IMAGE *image)
{
if (image) {
Memory_FreePointer(&image->data);
}
Memory_FreePointer(&image);
}

99
src/libtrx/enum_map.c Normal file
View file

@ -0,0 +1,99 @@
#include "enum_map.h"
#include "memory.h"
#include <stdio.h>
#include <uthash.h>
typedef struct {
char *key;
int32_t value;
UT_hash_handle hh;
} M_ENTRY;
typedef struct {
char *key;
char *str_value;
UT_hash_handle hh;
} M_INVERSE_ENTRY;
static M_ENTRY *m_Map = NULL;
static M_INVERSE_ENTRY *m_InverseMap = NULL;
void EnumMap_Define(
const char *const enum_name, const int32_t enum_value,
const char *const str_value)
{
{
const size_t key_len = strlen(enum_name) + strlen(str_value) + 2;
char *const key = Memory_Alloc(key_len);
snprintf(key, key_len, "%s|%s", enum_name, str_value);
M_ENTRY *const entry = Memory_Alloc(sizeof(M_ENTRY));
entry->key = key;
entry->value = enum_value;
HASH_ADD_KEYPTR(hh, m_Map, entry->key, strlen(entry->key), entry);
}
{
const size_t key_len =
snprintf(NULL, 0, "%s|%d", enum_name, enum_value) + 1;
char *const key = Memory_Alloc(key_len);
snprintf(key, key_len, "%s|%d", enum_name, enum_value);
M_INVERSE_ENTRY *const entry = Memory_Alloc(sizeof(M_INVERSE_ENTRY));
entry->key = key;
entry->str_value = Memory_DupStr(str_value);
HASH_ADD_KEYPTR(
hh, m_InverseMap, entry->key, strlen(entry->key), entry);
}
}
int32_t EnumMap_Get(
const char *const enum_name, const char *const str_value,
int32_t default_value)
{
size_t key_len = strlen(enum_name) + strlen(str_value) + 2;
char key[key_len];
snprintf(key, key_len, "%s|%s", enum_name, str_value);
M_ENTRY *entry;
HASH_FIND_STR(m_Map, key, entry);
return entry != NULL ? entry->value : default_value;
}
const char *EnumMap_ToString(
const char *const enum_name, const int32_t enum_value)
{
size_t key_len = snprintf(NULL, 0, "%s|%d", enum_name, enum_value) + 1;
char key[key_len];
snprintf(key, key_len, "%s|%d", enum_name, enum_value);
M_INVERSE_ENTRY *entry;
HASH_FIND_STR(m_InverseMap, key, entry);
return entry != NULL ? entry->str_value : NULL;
}
void EnumMap_Shutdown(void)
{
{
M_ENTRY *current, *tmp;
HASH_ITER(hh, m_Map, current, tmp)
{
HASH_DEL(m_Map, current);
Memory_Free(current->key);
Memory_Free(current);
}
}
{
M_INVERSE_ENTRY *current, *tmp;
HASH_ITER(hh, m_InverseMap, current, tmp)
{
HASH_DEL(m_InverseMap, current);
Memory_Free(current->str_value);
Memory_Free(current->key);
Memory_Free(current);
}
}
}

View file

@ -0,0 +1,73 @@
#include "event_manager.h"
#include "memory.h"
#include "vector.h"
#include <stdlib.h>
#include <string.h>
typedef struct {
int32_t listener_id;
const char *event_name;
const void *sender;
EVENT_LISTENER listener;
void *user_data;
} M_LISTENER;
typedef struct EVENT_MANAGER {
VECTOR *listeners;
int32_t listener_id;
} EVENT_MANAGER;
EVENT_MANAGER *EventManager_Create(void)
{
EVENT_MANAGER *manager = Memory_Alloc(sizeof(EVENT_MANAGER));
manager->listeners = Vector_Create(sizeof(M_LISTENER));
manager->listener_id = 0;
return manager;
}
void EventManager_Free(EVENT_MANAGER *const manager)
{
Vector_Free(manager->listeners);
Memory_Free(manager);
}
int32_t EventManager_Subscribe(
EVENT_MANAGER *const manager, const char *const event_name,
const void *const sender, const EVENT_LISTENER listener,
void *const user_data)
{
M_LISTENER entry = {
.listener_id = manager->listener_id++,
.event_name = event_name,
.sender = sender,
.listener = listener,
.user_data = user_data,
};
Vector_Add(manager->listeners, &entry);
return entry.listener_id;
}
void EventManager_Unsubscribe(
EVENT_MANAGER *const manager, const int32_t listener_id)
{
for (int32_t i = 0; i < manager->listeners->count; i++) {
M_LISTENER entry = *(M_LISTENER *)Vector_Get(manager->listeners, i);
if (entry.listener_id == listener_id) {
Vector_RemoveAt(manager->listeners, i);
return;
}
}
}
void EventManager_Fire(EVENT_MANAGER *const manager, const EVENT *const event)
{
for (int32_t i = 0; i < manager->listeners->count; i++) {
M_LISTENER entry = *(M_LISTENER *)Vector_Get(manager->listeners, i);
if (strcmp(entry.event_name, event->name) == 0
&& entry.sender == event->sender) {
entry.listener(event, entry.user_data);
}
}
}

436
src/libtrx/filesystem.c Normal file
View file

@ -0,0 +1,436 @@
#include "filesystem.h"
#include "log.h"
#include "memory.h"
#include "strings.h"
#include "utils.h"
#include <SDL2/SDL_filesystem.h>
#include <assert.h>
#include <dirent.h>
#include <stdbool.h>
#if defined(_WIN32)
#include <direct.h>
#define PATH_SEPARATOR "\\"
#else
#include <sys/stat.h>
#define PATH_SEPARATOR "/"
#endif
struct MYFILE {
FILE *fp;
const char *path;
};
const char *m_GameDir = NULL;
static void M_PathAppendSeparator(char *path);
static void M_PathAppendPart(char *path, const char *part);
static char *M_CasePath(char const *path);
static bool M_ExistsRaw(const char *path);
static void M_PathAppendSeparator(char *path)
{
if (!String_EndsWith(path, PATH_SEPARATOR)) {
strcat(path, PATH_SEPARATOR);
}
}
static void M_PathAppendPart(char *path, const char *part)
{
M_PathAppendSeparator(path);
strcat(path, part);
}
static char *M_CasePath(char const *path)
{
assert(path);
char *path_copy = Memory_DupStr(path);
if (M_ExistsRaw(path)) {
return path_copy;
}
char *path_piece = path_copy;
char *current_path = Memory_Alloc(strlen(path) + 2);
if (path_copy[0] == '/') {
strcpy(current_path, "/");
path_piece++;
} else if (strstr(path_copy, ":\\")) {
strcpy(current_path, path_copy);
strstr(current_path, ":\\")[1] = '\0';
path_piece += 3;
} else {
strcpy(current_path, ".");
}
while (path_piece) {
char *delim = strpbrk(path_piece, "/\\");
char old_delim = delim ? *delim : '\0';
if (delim) {
*delim = '\0';
}
DIR *path_dir = opendir(current_path);
if (!path_dir) {
Memory_FreePointer(&path_copy);
Memory_FreePointer(&current_path);
return NULL;
}
struct dirent *cur_file = readdir(path_dir);
while (cur_file) {
if (String_Equivalent(path_piece, cur_file->d_name)) {
M_PathAppendPart(current_path, cur_file->d_name);
break;
}
cur_file = readdir(path_dir);
}
closedir(path_dir);
if (!cur_file) {
M_PathAppendPart(current_path, path_piece);
}
if (delim) {
*delim = old_delim;
path_piece = delim + 1;
} else {
break;
}
}
Memory_FreePointer(&path_copy);
char *result;
if (current_path[0] == '.'
&& strcmp(current_path + 1, PATH_SEPARATOR)
== 0) { /* strip leading ./ */
result = Memory_DupStr(current_path + 1 + strlen(PATH_SEPARATOR));
} else {
result = Memory_DupStr(current_path);
}
Memory_FreePointer(&current_path);
return result;
}
static bool M_ExistsRaw(const char *path)
{
FILE *fp = fopen(path, "rb");
if (fp) {
fclose(fp);
return true;
}
return false;
}
bool File_IsAbsolute(const char *path)
{
return path && (path[0] == '/' || strstr(path, ":\\"));
}
bool File_IsRelative(const char *path)
{
return path && !File_IsAbsolute(path);
}
const char *File_GetGameDirectory(void)
{
if (m_GameDir == NULL) {
m_GameDir = SDL_GetBasePath();
if (!m_GameDir) {
LOG_ERROR("Can't get module handle");
return NULL;
}
}
return m_GameDir;
}
bool File_DirExists(const char *path)
{
char *full_path = File_GetFullPath(path);
DIR *dir = opendir(path);
Memory_FreePointer(&full_path);
if (dir != NULL) {
closedir(dir);
return true;
}
return false;
}
bool File_Exists(const char *path)
{
char *full_path = File_GetFullPath(path);
bool ret = M_ExistsRaw(full_path);
Memory_FreePointer(&full_path);
return ret;
}
char *File_GetFullPath(const char *path)
{
char *full_path = NULL;
if (File_IsRelative(path)) {
const char *game_dir = File_GetGameDirectory();
if (game_dir) {
full_path = Memory_Alloc(strlen(game_dir) + strlen(path) + 1);
sprintf(full_path, "%s%s", game_dir, path);
}
}
if (!full_path) {
full_path = Memory_DupStr(path);
}
char *case_path = M_CasePath(full_path);
if (case_path) {
Memory_FreePointer(&full_path);
return case_path;
}
return full_path;
}
char *File_GetParentDirectory(const char *path)
{
char *full_path = File_GetFullPath(path);
char *const last_delim =
MAX(strrchr(full_path, '/'), strrchr(full_path, '\\'));
if (last_delim != NULL) {
*last_delim = '\0';
}
return full_path;
}
char *File_GuessExtension(const char *path, const char **extensions)
{
if (!File_Exists(path)) {
const char *dot = strrchr(path, '.');
if (dot) {
for (const char **ext = &extensions[0]; *ext; ext++) {
size_t out_size = dot - path + strlen(*ext) + 1;
char *out = Memory_Alloc(out_size);
strncpy(out, path, dot - path);
out[dot - path] = '\0';
strcat(out, *ext);
if (File_Exists(out)) {
return out;
}
Memory_FreePointer(&out);
}
}
}
return Memory_DupStr(path);
}
MYFILE *File_Open(const char *path, FILE_OPEN_MODE mode)
{
char *full_path = File_GetFullPath(path);
MYFILE *file = Memory_Alloc(sizeof(MYFILE));
file->path = Memory_DupStr(path);
switch (mode) {
case FILE_OPEN_WRITE:
file->fp = fopen(full_path, "wb");
break;
case FILE_OPEN_READ:
file->fp = fopen(full_path, "rb");
break;
case FILE_OPEN_READ_WRITE:
file->fp = fopen(full_path, "r+b");
break;
default:
file->fp = NULL;
break;
}
Memory_FreePointer(&full_path);
if (!file->fp) {
Memory_FreePointer(&file->path);
Memory_FreePointer(&file);
}
return file;
}
void File_ReadData(MYFILE *const file, void *const data, const size_t size)
{
fread(data, size, 1, file->fp);
}
void File_ReadItems(
MYFILE *const file, void *data, const size_t count, const size_t item_size)
{
fread(data, item_size, count, file->fp);
}
int8_t File_ReadS8(MYFILE *const file)
{
int8_t result;
fread(&result, sizeof(result), 1, file->fp);
return result;
}
int16_t File_ReadS16(MYFILE *const file)
{
int16_t result;
fread(&result, sizeof(result), 1, file->fp);
return result;
}
int32_t File_ReadS32(MYFILE *const file)
{
int32_t result;
fread(&result, sizeof(result), 1, file->fp);
return result;
}
uint8_t File_ReadU8(MYFILE *const file)
{
uint8_t result;
fread(&result, sizeof(result), 1, file->fp);
return result;
}
uint16_t File_ReadU16(MYFILE *const file)
{
uint16_t result;
fread(&result, sizeof(result), 1, file->fp);
return result;
}
uint32_t File_ReadU32(MYFILE *const file)
{
uint32_t result;
fread(&result, sizeof(result), 1, file->fp);
return result;
}
void File_WriteData(
MYFILE *const file, const void *const data, const size_t size)
{
fwrite(data, size, 1, file->fp);
}
void File_WriteItems(
MYFILE *const file, const void *const data, const size_t count,
const size_t item_size)
{
fwrite(data, item_size, count, file->fp);
}
void File_WriteS8(MYFILE *const file, const int8_t value)
{
fwrite(&value, sizeof(value), 1, file->fp);
}
void File_WriteS16(MYFILE *const file, const int16_t value)
{
fwrite(&value, sizeof(value), 1, file->fp);
}
void File_WriteS32(MYFILE *const file, const int32_t value)
{
fwrite(&value, sizeof(value), 1, file->fp);
}
void File_WriteU8(MYFILE *const file, const uint8_t value)
{
fwrite(&value, sizeof(value), 1, file->fp);
}
void File_WriteU16(MYFILE *const file, const uint16_t value)
{
fwrite(&value, sizeof(value), 1, file->fp);
}
void File_WriteU32(MYFILE *const file, const uint32_t value)
{
fwrite(&value, sizeof(value), 1, file->fp);
}
void File_Skip(MYFILE *file, size_t bytes)
{
File_Seek(file, bytes, FILE_SEEK_CUR);
}
void File_Seek(MYFILE *file, size_t pos, FILE_SEEK_MODE mode)
{
switch (mode) {
case FILE_SEEK_SET:
fseek(file->fp, pos, SEEK_SET);
break;
case FILE_SEEK_CUR:
fseek(file->fp, pos, SEEK_CUR);
break;
case FILE_SEEK_END:
fseek(file->fp, pos, SEEK_END);
break;
}
}
size_t File_Pos(MYFILE *file)
{
return ftell(file->fp);
}
size_t File_Size(MYFILE *file)
{
size_t old = ftell(file->fp);
fseek(file->fp, 0, SEEK_END);
size_t size = ftell(file->fp);
fseek(file->fp, old, SEEK_SET);
return size;
}
const char *File_GetPath(MYFILE *file)
{
return file->path;
}
void File_Close(MYFILE *file)
{
fclose(file->fp);
Memory_FreePointer(&file->path);
Memory_FreePointer(&file);
}
bool File_Load(const char *path, char **output_data, size_t *output_size)
{
assert(output_data != NULL);
MYFILE *fp = File_Open(path, FILE_OPEN_READ);
if (!fp) {
LOG_ERROR("Can't open file %s", path);
*output_data = NULL;
return false;
}
size_t data_size = File_Size(fp);
char *data = Memory_Alloc(data_size + 1);
File_ReadData(fp, data, data_size);
if (File_Pos(fp) != data_size) {
*output_data = NULL;
LOG_ERROR("Can't read file %s", path);
Memory_FreePointer(&data);
File_Close(fp);
return false;
}
File_Close(fp);
data[data_size] = '\0';
*output_data = data;
if (output_size != NULL) {
*output_size = data_size;
}
return true;
}
void File_CreateDirectory(const char *path)
{
char *full_path = File_GetFullPath(path);
assert(full_path);
#if defined(_WIN32)
_mkdir(full_path);
#else
mkdir(full_path, 0775);
#endif
Memory_FreePointer(&full_path);
}

View file

@ -0,0 +1,10 @@
#include "game/backpack.h"
bool Backpack_AddItemNTimes(const GAME_OBJECT_ID object_id, const int32_t n)
{
bool result = false;
for (int32_t i = 0; i < n; i++) {
result |= Backpack_AddItem(object_id);
}
return result;
}

View file

@ -0,0 +1,278 @@
#include "game/console/cmd/config.h"
#include "config/common.h"
#include "config/map.h"
#include "game/game_string.h"
#include "memory.h"
#include "strings.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
static const char *M_Resolve(const char *option_name);
static bool M_SameKey(const char *key1, const char *key2);
static char *M_NormalizeKey(const char *key);
static bool M_GetCurrentValue(
const CONFIG_OPTION *option, char *target, size_t target_size);
static bool M_SetCurrentValue(
const CONFIG_OPTION *option, const char *new_value);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static const char *M_Resolve(const char *const option_name)
{
const char *dot = strrchr(option_name, '.');
if (dot) {
return dot + 1;
}
return option_name;
}
static bool M_SameKey(const char *key1, const char *key2)
{
key1 = M_Resolve(key1);
key2 = M_Resolve(key2);
const size_t len1 = strlen(key1);
const size_t len2 = strlen(key2);
if (len1 != len2) {
return false;
}
for (uint32_t i = 0; i < len1; i++) {
char c1 = key1[i];
char c2 = key2[i];
if (c1 == '_') {
c1 = '-';
}
if (c2 == '_') {
c2 = '-';
}
if (c1 != c2) {
return false;
}
}
return true;
}
static char *M_NormalizeKey(const char *key)
{
// TODO: Once we support arbitrary glyphs, this conversion should
// no longer be necessary.
char *result = Memory_DupStr(key);
for (uint32_t i = 0; i < strlen(result); i++) {
if (result[i] == '_') {
result[i] = '-';
}
}
return result;
}
static bool M_GetCurrentValue(
const CONFIG_OPTION *const option, char *target, const size_t target_size)
{
if (option == NULL) {
return false;
}
assert(option->target != NULL);
switch (option->type) {
case COT_BOOL:
snprintf(
target, target_size, "%s",
*(bool *)option->target ? GS(MISC_ON) : GS(MISC_OFF));
break;
case COT_INT32:
snprintf(target, target_size, "%d", *(int32_t *)option->target);
break;
case COT_FLOAT:
snprintf(target, target_size, "%.2f", *(float *)option->target);
break;
case COT_DOUBLE:
snprintf(target, target_size, "%.2f", *(double *)option->target);
break;
case COT_ENUM:
snprintf(
target, target_size, "%s",
EnumMap_ToString(option->param, *(int32_t *)option->target));
break;
}
return true;
}
static bool M_SetCurrentValue(
const CONFIG_OPTION *const option, const char *const new_value)
{
if (option == NULL) {
return CR_BAD_INVOCATION;
}
assert(option->target != NULL);
switch (option->type) {
case COT_BOOL:
if (String_Match(new_value, "on|true|1")) {
*(bool *)option->target = true;
return true;
} else if (String_Match(new_value, "off|false|0")) {
*(bool *)option->target = false;
return true;
}
break;
case COT_INT32: {
int32_t new_value_typed;
if (sscanf(new_value, "%d", &new_value_typed) == 1) {
*(int32_t *)option->target = new_value_typed;
return true;
}
break;
}
case COT_FLOAT: {
float new_value_typed;
if (sscanf(new_value, "%f", &new_value_typed) == 1) {
*(float *)option->target = new_value_typed;
return true;
}
break;
}
case COT_DOUBLE: {
double new_value_typed;
if (sscanf(new_value, "%lf", &new_value_typed) == 1) {
*(double *)option->target = new_value_typed;
return true;
}
break;
}
case COT_ENUM: {
const int32_t new_value_typed =
EnumMap_Get(option->param, new_value, -1);
if (new_value_typed != -1) {
*(int32_t *)option->target = new_value_typed;
return true;
}
break;
}
}
return false;
}
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
COMMAND_RESULT result = CR_BAD_INVOCATION;
char *key = Memory_DupStr(ctx->args);
char *const space = strchr(key, ' ');
const char *new_value = NULL;
if (space != NULL) {
new_value = space + 1;
space[0] = '\0'; // NULL-terminate the key
}
const CONFIG_OPTION *const option =
Console_Cmd_Config_GetOptionFromKey(key);
if (option == NULL) {
result = CR_FAILURE;
} else {
result = Console_Cmd_Config_Helper(option, new_value);
}
cleanup:
Memory_FreePointer(&key);
return result;
}
const CONFIG_OPTION *Console_Cmd_Config_GetOptionFromKey(const char *const key)
{
VECTOR *source = Vector_Create(sizeof(STRING_FUZZY_SOURCE));
for (const CONFIG_OPTION *option = Config_GetOptionMap();
option->name != NULL; option++) {
STRING_FUZZY_SOURCE source_item = {
.key = (const char *)M_NormalizeKey(option->name),
.value = (void *)option,
.weight = 1,
};
Vector_Add(source, &source_item);
}
VECTOR *matches = String_FuzzyMatch(key, source);
const CONFIG_OPTION *result = NULL;
if (matches->count == 0) {
Console_Log(GS(OSD_CONFIG_OPTION_UNKNOWN_OPTION), key);
} else if (matches->count == 1) {
const STRING_FUZZY_MATCH *const match = Vector_Get(matches, 0);
result = match->value;
} else if (matches->count == 2) {
const STRING_FUZZY_MATCH *const match1 = Vector_Get(matches, 0);
const STRING_FUZZY_MATCH *const match2 = Vector_Get(matches, 1);
Console_Log(GS(OSD_AMBIGUOUS_INPUT_2), match1->key, match2->key);
} else if (matches->count >= 3) {
const STRING_FUZZY_MATCH *const match1 = Vector_Get(matches, 0);
const STRING_FUZZY_MATCH *const match2 = Vector_Get(matches, 1);
Console_Log(GS(OSD_AMBIGUOUS_INPUT_3), match1->key, match2->key);
}
for (int32_t i = 0; i < source->count; i++) {
const STRING_FUZZY_SOURCE *const source_item = Vector_Get(source, i);
Memory_Free((char *)source_item->key);
}
Vector_Free(matches);
Vector_Free(source);
return result;
}
const CONFIG_OPTION *Console_Cmd_Config_GetOptionFromTarget(
const void *const target)
{
for (const CONFIG_OPTION *option = Config_GetOptionMap();
option->name != NULL; option++) {
if (option->target == target) {
return option;
}
}
return NULL;
}
COMMAND_RESULT Console_Cmd_Config_Helper(
const CONFIG_OPTION *const option, const char *const new_value)
{
assert(option != NULL);
char *normalized_name = M_NormalizeKey(option->name);
COMMAND_RESULT result = CR_BAD_INVOCATION;
if (new_value == NULL || String_IsEmpty(new_value)) {
char cur_value[128];
if (M_GetCurrentValue(option, cur_value, 128)) {
Console_Log(GS(OSD_CONFIG_OPTION_GET), normalized_name, cur_value);
result = CR_SUCCESS;
} else {
result = CR_FAILURE;
}
return result;
}
if (M_SetCurrentValue(option, new_value)) {
Config_Write();
char final_value[128];
assert(M_GetCurrentValue(option, final_value, 128));
Console_Log(GS(OSD_CONFIG_OPTION_SET), normalized_name, final_value);
result = CR_SUCCESS;
}
cleanup:
Memory_FreePointer(&normalized_name);
return result;
}
CONSOLE_COMMAND g_Console_Cmd_Config = {
.prefix = "set",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,41 @@
#include "game/console/cmd/die.h"
#include "game/effects/exploding_death.h"
#include "game/items.h"
#include "game/lara/common.h"
#include "game/objects/common.h"
#include "game/objects/ids.h"
#include "game/sound.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
if (!Object_GetObject(O_LARA)->loaded) {
return CR_UNAVAILABLE;
}
LARA_INFO *const lara = Lara_GetLaraInfo();
ITEM *const lara_item = Lara_GetItem();
if (lara_item->hit_points <= 0) {
return CR_UNAVAILABLE;
}
Sound_Effect(SFX_LARA_FALL, &lara_item->pos, SPM_NORMAL);
Sound_Effect(SFX_EXPLOSION_CHEAT, &lara_item->pos, SPM_NORMAL);
Effect_ExplodingDeath(lara->item_num, -1, 1);
lara_item->hit_points = 0;
lara_item->flags |= IF_INVISIBLE;
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_Die = {
.prefix = "abortion|natla-?s(uc|tin)ks",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,26 @@
#include "game/console/cmd/end_level.h"
#include "game/game.h"
#include "game/lara/cheat.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
if (Game_GetCurrentLevelType() == GFL_TITLE) {
return CR_UNAVAILABLE;
}
Lara_Cheat_EndLevel();
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_EndLevel = {
.prefix = "end-?level|next-?level",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,21 @@
#include "game/console/cmd/exit_game.h"
#include "game/gameflow/common.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
Gameflow_OverrideCommand((GAMEFLOW_COMMAND) { .action = GF_EXIT_GAME });
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_ExitGame = {
.prefix = "exit|quit",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,21 @@
#include "game/console/cmd/exit_to_title.h"
#include "game/gameflow/common.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
Gameflow_OverrideCommand((GAMEFLOW_COMMAND) { .action = GF_EXIT_TO_TITLE });
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_ExitToTitle = {
.prefix = "title",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,38 @@
#include "game/console/cmd/flipmap.h"
#include "game/game.h"
#include "game/game_string.h"
#include "game/rooms.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (Game_GetCurrentLevelType() == GFL_TITLE) {
return CR_UNAVAILABLE;
}
bool new_state = Room_GetFlipStatus();
if (String_IsEmpty(ctx->args)) {
new_state = !new_state;
} else if (!String_ParseBool(ctx->args, &new_state)) {
return CR_BAD_INVOCATION;
}
if (Room_GetFlipStatus() == new_state) {
Console_Log(
new_state ? GS(OSD_FLIPMAP_FAIL_ALREADY_ON)
: GS(OSD_FLIPMAP_FAIL_ALREADY_OFF));
return CR_SUCCESS;
}
Room_FlipMap();
Console_Log(new_state ? GS(OSD_FLIPMAP_ON) : GS(OSD_FLIPMAP_OFF));
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_FlipMap = {
.prefix = "flip|flipmap",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,43 @@
#include "game/console/cmd/fly.h"
#include "game/game.h"
#include "game/game_string.h"
#include "game/lara/cheat.h"
#include "game/lara/common.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!Game_IsPlayable()) {
return CR_UNAVAILABLE;
}
bool enable;
if (String_ParseBool(ctx->args, &enable)) {
if (enable) {
Lara_Cheat_EnterFlyMode();
} else {
Lara_Cheat_ExitFlyMode();
}
return CR_SUCCESS;
}
if (!String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
const LARA_INFO *const lara = Lara_GetLaraInfo();
if (lara->water_status == LWS_CHEAT) {
Lara_Cheat_ExitFlyMode();
} else {
Lara_Cheat_EnterFlyMode();
}
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_Fly = {
.prefix = "fly",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,86 @@
#include "game/console/cmd/give_item.h"
#include "game/backpack.h"
#include "game/game.h"
#include "game/game_string.h"
#include "game/lara/cheat.h"
#include "game/objects/common.h"
#include "game/objects/names.h"
#include "game/objects/vars.h"
#include "memory.h"
#include "strings.h"
#include <stdio.h>
#include <string.h>
static bool M_CanTargetObjectPickup(GAME_OBJECT_ID object_id);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static bool M_CanTargetObjectPickup(const GAME_OBJECT_ID object_id)
{
return Object_IsObjectType(object_id, g_PickupObjects)
&& Object_GetObject(object_id)->loaded;
}
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!Game_IsPlayable()) {
return CR_UNAVAILABLE;
}
if (String_Equivalent(ctx->args, "keys")) {
return Lara_Cheat_GiveAllKeys() ? CR_SUCCESS : CR_FAILURE;
}
if (String_Equivalent(ctx->args, "guns")) {
return Lara_Cheat_GiveAllGuns() ? CR_SUCCESS : CR_FAILURE;
}
if (String_Equivalent(ctx->args, "all")) {
return Lara_Cheat_GiveAllItems() ? CR_SUCCESS : CR_FAILURE;
}
int32_t num = 1;
const char *args = ctx->args;
if (sscanf(ctx->args, "%d ", &num) == 1) {
args = strstr(args, " ");
if (args == NULL) {
return CR_BAD_INVOCATION;
}
args++;
}
if (String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
bool found = false;
int32_t match_count = 0;
GAME_OBJECT_ID *matching_objs =
Object_IdsFromName(args, &match_count, M_CanTargetObjectPickup);
for (int32_t i = 0; i < match_count; i++) {
const GAME_OBJECT_ID object_id = matching_objs[i];
if (Object_GetObject(object_id)->loaded) {
const char *obj_name = Object_GetName(object_id);
if (obj_name == NULL) {
obj_name = args;
}
Backpack_AddItemNTimes(object_id, num);
Console_Log(GS(OSD_GIVE_ITEM), obj_name);
found = true;
}
}
Memory_FreePointer(&matching_objs);
if (!found) {
Console_Log(GS(OSD_INVALID_ITEM), args);
return CR_FAILURE;
}
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_GiveItem = {
.prefix = "give",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,37 @@
#include "game/console/cmd/heal.h"
#include "game/game.h"
#include "game/game_string.h"
#include "game/lara/common.h"
#include "game/lara/const.h"
#include "game/lara/misc.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
if (!Game_IsPlayable()) {
return CR_UNAVAILABLE;
}
ITEM *const lara_item = Lara_GetItem();
if (lara_item->hit_points == LARA_MAX_HITPOINTS) {
Console_Log(GS(OSD_HEAL_ALREADY_FULL_HP));
return CR_SUCCESS;
}
lara_item->hit_points = LARA_MAX_HITPOINTS;
Lara_Extinguish();
Console_Log(GS(OSD_HEAL_SUCCESS));
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_Heal = {
.prefix = "heal",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,135 @@
#include "game/console/cmd/kill.h"
#include "game/const.h"
#include "game/creature.h"
#include "game/game_string.h"
#include "game/items.h"
#include "game/lara/cheat.h"
#include "game/lara/common.h"
#include "game/lara/misc.h"
#include "game/objects/common.h"
#include "game/objects/ids.h"
#include "game/objects/names.h"
#include "game/objects/vars.h"
#include "memory.h"
#include "strings.h"
static bool M_CanTargetObjectCreature(GAME_OBJECT_ID object_id);
static COMMAND_RESULT M_KillAllEnemies(void);
static COMMAND_RESULT M_KillNearestEnemies(void);
static COMMAND_RESULT M_KillEnemyType(const char *enemy_name);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static bool M_CanTargetObjectCreature(const GAME_OBJECT_ID object_id)
{
return Object_IsObjectType(object_id, g_EnemyObjects)
|| Object_IsObjectType(object_id, g_AllyObjects);
}
static COMMAND_RESULT M_KillAllEnemies(void)
{
int32_t num_killed = 0;
for (int16_t item_num = 0; item_num < Item_GetTotalCount(); item_num++) {
const ITEM *const item = Item_Get(item_num);
if (!Creature_IsEnemy(item)) {
continue;
}
if (Lara_Cheat_KillEnemy(item_num)) {
num_killed++;
}
}
if (num_killed == 0) {
Console_Log(GS(OSD_KILL_ALL_FAIL));
return CR_FAILURE;
}
Console_Log(GS(OSD_KILL_ALL), num_killed);
return CR_SUCCESS;
}
static COMMAND_RESULT M_KillNearestEnemies(void)
{
bool found = false;
while (true) {
const int16_t best_item_num = Lara_GetNearestEnemy();
if (best_item_num == NO_ITEM) {
break;
}
const ITEM *const lara_item = Lara_GetItem();
const ITEM *const item = Item_Get(best_item_num);
const int32_t distance = Item_GetDistance(item, &lara_item->pos);
found |= Lara_Cheat_KillEnemy(best_item_num);
if (distance >= WALL_L) {
break;
}
}
if (!found) {
Console_Log(GS(OSD_KILL_FAIL));
return CR_FAILURE;
}
Console_Log(GS(OSD_KILL));
return CR_SUCCESS;
}
static COMMAND_RESULT M_KillEnemyType(const char *const enemy_name)
{
bool matches_found = false;
int32_t num_killed = 0;
int32_t match_count = 0;
GAME_OBJECT_ID *matching_objs =
Object_IdsFromName(enemy_name, &match_count, M_CanTargetObjectCreature);
for (int16_t item_num = 0; item_num < Item_GetTotalCount(); item_num++) {
const ITEM *const item = Item_Get(item_num);
bool is_matched = false;
for (int32_t i = 0; i < match_count; i++) {
if (matching_objs[i] == item->object_id) {
is_matched = true;
break;
}
}
if (!is_matched) {
continue;
}
matches_found = true;
if (Lara_Cheat_KillEnemy(item_num)) {
num_killed++;
}
}
Memory_FreePointer(&matching_objs);
if (!matches_found) {
Console_Log(GS(OSD_INVALID_OBJECT), enemy_name);
return CR_FAILURE;
}
if (num_killed == 0) {
Console_Log(GS(OSD_OBJECT_NOT_FOUND), enemy_name);
return CR_FAILURE;
}
Console_Log(GS(OSD_KILL_ALL), num_killed);
return CR_SUCCESS;
}
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (String_Equivalent(ctx->args, "all")) {
return M_KillAllEnemies();
}
if (String_IsEmpty(ctx->args)) {
return M_KillNearestEnemies();
}
return M_KillEnemyType(ctx->args);
}
CONSOLE_COMMAND g_Console_Cmd_Kill = {
.prefix = "kill",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,41 @@
#include "game/console/cmd/load_game.h"
#include "config.h"
#include "game/game_string.h"
#include "game/gameflow/common.h"
#include "game/savegame.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
int32_t slot_num;
if (!String_ParseInteger(ctx->args, &slot_num)) {
return CR_BAD_INVOCATION;
}
const int32_t slot_idx = slot_num - 1; // convert 1-indexing to 0-indexing
if (slot_idx < 0 || slot_idx >= Savegame_GetSlotCount()) {
Console_Log(GS(OSD_LOAD_GAME_FAIL_INVALID_SLOT), slot_num);
return CR_FAILURE;
}
if (Savegame_IsSlotFree(slot_idx)) {
Console_Log(GS(OSD_LOAD_GAME_FAIL_UNAVAILABLE_SLOT), slot_num);
return CR_FAILURE;
}
Gameflow_OverrideCommand((GAMEFLOW_COMMAND) {
.action = GF_START_SAVED_GAME,
.param = slot_idx,
});
Console_Log(GS(OSD_LOAD_GAME), slot_num);
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_LoadGame = {
.prefix = "load",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,21 @@
#include "game/console/cmd/play_demo.h"
#include "game/gameflow/common.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
Gameflow_OverrideCommand((GAMEFLOW_COMMAND) { .action = GF_START_DEMO });
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_PlayDemo = {
.prefix = "demo",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,86 @@
#include "game/console/cmd/play_level.h"
#include "game/game_string.h"
#include "game/gameflow/common.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
VECTOR *source = NULL;
VECTOR *matches = NULL;
int32_t level_to_load = -1;
if (String_ParseInteger(ctx->args, &level_to_load)) {
goto matched;
}
source = Vector_Create(sizeof(STRING_FUZZY_SOURCE));
for (int32_t level_num = 0; level_num < Gameflow_GetLevelCount();
level_num++) {
STRING_FUZZY_SOURCE source_item = {
.key = Gameflow_GetLevelTitle(level_num),
.value = (void *)(intptr_t)level_num,
.weight = 1,
};
Vector_Add(source, &source_item);
}
const int32_t gym_level_num = Gameflow_GetGymLevelNumber();
if (gym_level_num != -1) {
STRING_FUZZY_SOURCE source_item = {
.key = "gym",
.value = (void *)(intptr_t)gym_level_num,
.weight = 1,
};
Vector_Add(source, &source_item);
}
COMMAND_RESULT result;
matches = String_FuzzyMatch(ctx->args, source);
if (matches->count == 0) {
Console_Log(GS(OSD_INVALID_LEVEL));
result = CR_BAD_INVOCATION;
goto cleanup;
} else if (matches->count >= 1) {
const STRING_FUZZY_MATCH *const match = Vector_Get(matches, 0);
level_to_load = (int32_t)(intptr_t)match->value;
goto matched;
}
matched:
if (level_to_load >= 0 && level_to_load < Gameflow_GetLevelCount()) {
Gameflow_OverrideCommand((GAMEFLOW_COMMAND) {
.action = GF_START_GAME,
.param = level_to_load,
});
Console_Log(GS(OSD_PLAY_LEVEL), Gameflow_GetLevelTitle(level_to_load));
result = CR_SUCCESS;
} else {
Console_Log(GS(OSD_INVALID_LEVEL));
result = CR_FAILURE;
}
cleanup:
if (matches != NULL) {
Vector_Free(matches);
matches = NULL;
}
if (source != NULL) {
Vector_Free(source);
source = NULL;
}
return result;
}
CONSOLE_COMMAND g_Console_Cmd_PlayLevel = {
.prefix = "play|level",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,43 @@
#include "game/console/cmd/pos.h"
#include "game/console/common.h"
#include "game/const.h"
#include "game/game_string.h"
#include "game/lara/common.h"
#include "game/objects/common.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!String_IsEmpty(ctx->args)) {
return CR_BAD_INVOCATION;
}
const OBJECT *const object = Object_GetObject(O_LARA);
if (!object->loaded) {
return CR_UNAVAILABLE;
}
const ITEM *const lara_item = Lara_GetItem();
// clang-format off
Console_Log(
GS(OSD_POS_GET),
lara_item->room_num,
lara_item->pos.x / (float)WALL_L,
lara_item->pos.y / (float)WALL_L,
lara_item->pos.z / (float)WALL_L,
lara_item->rot.x * 360.0f / (float)PHD_ONE,
lara_item->rot.y * 360.0f / (float)PHD_ONE,
lara_item->rot.z * 360.0f / (float)PHD_ONE);
// clang-format on
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_Pos = {
.prefix = "pos",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,37 @@
#include "game/console/cmd/save_game.h"
#include "config.h"
#include "game/game.h"
#include "game/game_string.h"
#include "game/gameflow/common.h"
#include "game/savegame.h"
#include "strings.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!Game_IsPlayable()) {
return CR_UNAVAILABLE;
}
int32_t slot_num;
if (!String_ParseInteger(ctx->args, &slot_num)) {
return CR_BAD_INVOCATION;
}
const int32_t slot_idx = slot_num - 1; // convert 1-indexing to 0-indexing
if (slot_idx < 0 || slot_idx >= Savegame_GetSlotCount()) {
Console_Log(GS(OSD_SAVE_GAME_FAIL_INVALID_SLOT), slot_num);
return CR_BAD_INVOCATION;
}
Savegame_Save(slot_idx);
Console_Log(GS(OSD_SAVE_GAME), slot_num);
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_SaveGame = {
.prefix = "save",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,40 @@
#include "game/console/cmd/pos.h"
#include "game/console/common.h"
#include "game/game.h"
#include "game/game_string.h"
#include "game/lara/common.h"
#include "game/lara/const.h"
#include "game/objects/common.h"
#include "strings.h"
#include "utils.h"
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!Game_IsPlayable()) {
return CR_UNAVAILABLE;
}
ITEM *const lara_item = Lara_GetItem();
if (String_IsEmpty(ctx->args)) {
Console_Log(GS(OSD_CURRENT_HEALTH_GET), lara_item->hit_points);
return CR_SUCCESS;
}
int32_t hp;
if (!String_ParseInteger(ctx->args, &hp)) {
return CR_BAD_INVOCATION;
}
CLAMP(hp, 0, LARA_MAX_HITPOINTS);
lara_item->hit_points = hp;
Console_Log(GS(OSD_CURRENT_HEALTH_SET), hp);
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_SetHealth = {
.prefix = "hp",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,85 @@
#include "game/console/cmd/sfx.h"
#include "game/console/common.h"
#include "game/game_string.h"
#include "game/sound.h"
#include "memory.h"
#include "strings.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>
static char *M_CreateRangeString(void);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static char *M_CreateRangeString(void)
{
size_t buffer_size = 64;
char *result = Memory_Alloc(buffer_size);
int32_t prev = -1;
int32_t start = -1;
for (int32_t i = 0; i <= SFX_NUMBER_OF; i++) {
const bool valid = Sound_IsAvailable(i);
if (valid && start == -1) {
start = i;
}
if (!valid && start != -1) {
char temp[32];
if (start == prev) {
sprintf(temp, "%d, ", prev);
} else {
sprintf(temp, "%d-%d, ", start, prev);
}
const int32_t len = strlen(temp);
if (strlen(result) + len >= buffer_size) {
buffer_size *= 2;
result = Memory_Realloc(result, buffer_size);
}
strcat(result, temp);
start = -1;
}
if (valid) {
prev = i;
}
}
// Remove the trailing comma and space
result[strlen(result) - 2] = '\0';
return result;
}
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (String_IsEmpty(ctx->args)) {
char *ranges = M_CreateRangeString();
Console_Log(GS(OSD_SOUND_AVAILABLE_SAMPLES), ranges);
Memory_FreePointer(&ranges);
return CR_SUCCESS;
}
int32_t sfx_id;
if (!String_ParseInteger(ctx->args, &sfx_id)) {
return CR_BAD_INVOCATION;
}
if (!Sound_IsAvailable(sfx_id)) {
Console_Log(GS(OSD_INVALID_SAMPLE), sfx_id);
return CR_FAILURE;
}
Console_Log(GS(OSD_SOUND_PLAYING_SAMPLE), sfx_id);
Sound_Effect(sfx_id, NULL, SPM_ALWAYS);
return CR_SUCCESS;
}
CONSOLE_COMMAND g_Console_Cmd_SFX = {
.prefix = "sfx",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,184 @@
#include "game/console/cmd/teleport.h"
#include "game/const.h"
#include "game/game.h"
#include "game/game_string.h"
#include "game/items.h"
#include "game/lara/cheat.h"
#include "game/lara/common.h"
#include "game/objects/common.h"
#include "game/objects/names.h"
#include "game/objects/vars.h"
#include "game/random.h"
#include "game/rooms.h"
#include "strings.h"
#include <math.h>
#include <stdio.h>
static bool M_CanTargetObject(GAME_OBJECT_ID object_id);
static bool M_IsFloatRound(float num);
static COMMAND_RESULT M_TeleportToXYZ(float x, float y, float z);
static COMMAND_RESULT M_TeleportToRoom(int16_t room_num);
static COMMAND_RESULT M_TeleportToObject(const char *user_input);
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *ctx);
static bool M_CanTargetObject(const GAME_OBJECT_ID object_id)
{
return !Object_IsObjectType(object_id, g_NullObjects)
&& !Object_IsObjectType(object_id, g_AnimObjects)
&& !Object_IsObjectType(object_id, g_InvObjects);
}
static inline bool M_IsFloatRound(const float num)
{
return (fabsf(num) - roundf(num)) < 0.0001f;
}
static COMMAND_RESULT M_TeleportToXYZ(float x, const float y, float z)
{
if (M_IsFloatRound(x)) {
x += 0.5f;
}
if (M_IsFloatRound(z)) {
z += 0.5f;
}
if (!Lara_Cheat_Teleport(x * WALL_L, y * WALL_L, z * WALL_L)) {
Console_Log(GS(OSD_POS_SET_POS_FAIL), x, y, z);
return CR_FAILURE;
}
Console_Log(GS(OSD_POS_SET_POS), x, y, z);
return CR_SUCCESS;
}
static COMMAND_RESULT M_TeleportToRoom(const int16_t room_num)
{
if (room_num < 0 || room_num >= Room_GetTotalCount()) {
Console_Log(GS(OSD_INVALID_ROOM), room_num, Room_GetTotalCount() - 1);
return CR_FAILURE;
}
const ROOM *const room = Room_Get(room_num);
const int32_t x1 = room->pos.x + WALL_L;
const int32_t x2 = room->pos.x + (room->size.x << WALL_SHIFT) - WALL_L;
const int32_t y1 = room->min_floor;
const int32_t y2 = room->max_ceiling;
const int32_t z1 = room->pos.z + WALL_L;
const int32_t z2 = room->pos.z + (room->size.z << WALL_SHIFT) - WALL_L;
bool success = false;
for (int32_t i = 0; i < 100; i++) {
int32_t x = x1 + Random_GetControl() * (x2 - x1) / 0x7FFF;
int32_t y = y1;
int32_t z = z1 + Random_GetControl() * (z2 - z1) / 0x7FFF;
if (Lara_Cheat_Teleport(x, y, z)) {
success = true;
break;
}
}
if (!success) {
Console_Log(GS(OSD_POS_SET_ROOM_FAIL), room_num);
return CR_FAILURE;
}
Console_Log(GS(OSD_POS_SET_ROOM), room_num);
return CR_SUCCESS;
}
static COMMAND_RESULT M_TeleportToObject(const char *const user_input)
{
// Nearest item of this name
if (String_Equivalent(user_input, "")) {
return CR_BAD_INVOCATION;
}
int32_t match_count = 0;
GAME_OBJECT_ID *matching_objs =
Object_IdsFromName(user_input, &match_count, M_CanTargetObject);
const ITEM *const lara_item = Lara_GetItem();
const ITEM *best_item = NULL;
int32_t best_distance = INT32_MAX;
for (int16_t item_num = 0; item_num < Item_GetTotalCount(); item_num++) {
const ITEM *const item = Item_Get(item_num);
if (Object_IsObjectType(item->object_id, g_PickupObjects)
&& (item->status == IS_INVISIBLE || item->status == IS_DEACTIVATED
|| item->room_num == NO_ROOM)) {
continue;
}
if (item->flags & IF_KILLED) {
continue;
}
bool is_matched = false;
for (int32_t i = 0; i < match_count; i++) {
if (matching_objs[i] == item->object_id) {
is_matched = true;
break;
}
}
if (!is_matched) {
continue;
}
const int32_t distance = Item_GetDistance(item, &lara_item->pos);
if (distance < best_distance) {
best_distance = distance;
best_item = item;
}
}
if (best_item == NULL) {
Console_Log(GS(OSD_POS_SET_ITEM_FAIL), user_input);
return CR_FAILURE;
}
const char *obj_name = Object_GetName(best_item->object_id);
if (obj_name == NULL) {
obj_name = user_input;
}
if (Lara_Cheat_Teleport(
best_item->pos.x, best_item->pos.y - STEP_L, best_item->pos.z)) {
Console_Log(GS(OSD_POS_SET_ITEM), obj_name);
} else {
Console_Log(GS(OSD_POS_SET_ITEM_FAIL), obj_name);
}
return CR_SUCCESS;
}
static COMMAND_RESULT M_Entrypoint(const COMMAND_CONTEXT *const ctx)
{
if (!Game_IsPlayable()) {
return CR_UNAVAILABLE;
}
const ITEM *const lara_item = Lara_GetItem();
if (!lara_item->hit_points) {
return CR_UNAVAILABLE;
}
float x, y, z;
if (sscanf(ctx->args, "%f %f %f", &x, &y, &z) == 3) {
return M_TeleportToXYZ(x, y, z);
}
int16_t room_num = -1;
if (sscanf(ctx->args, "%hd", &room_num) == 1) {
return M_TeleportToRoom(room_num);
}
return M_TeleportToObject(ctx->args);
}
CONSOLE_COMMAND g_Console_Cmd_Teleport = {
.prefix = "tp",
.proc = M_Entrypoint,
};

View file

@ -0,0 +1,189 @@
#include "game/console/common.h"
#include "game/console/extern.h"
#include "game/game_string.h"
#include "game/ui/widgets/console.h"
#include "log.h"
#include "memory.h"
#include "strings.h"
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
static bool m_IsOpened = false;
static UI_WIDGET *m_Console;
static void M_LogMultiline(const char *text);
static void M_Log(const char *text);
static void M_LogMultiline(const char *const text)
{
assert(text != NULL);
char *wrapped_text = String_WordWrap(text, Console_GetMaxLineLength());
const char *start = wrapped_text;
while (true) {
const char *newline = strchr(start, '\n');
if (newline == NULL) {
break;
}
char temp[newline - start + 1];
strncpy(temp, start, newline - start);
temp[newline - start] = '\0';
M_Log(temp);
start = newline + 1;
}
if (*start != '\0') {
M_Log(start);
}
Memory_FreePointer(&wrapped_text);
}
static void M_Log(const char *text)
{
assert(text != NULL);
UI_Console_HandleLog(m_Console, text);
}
void Console_Init(void)
{
m_Console = UI_Console_Create();
}
void Console_Shutdown(void)
{
if (m_Console != NULL) {
m_Console->free(m_Console);
m_Console = NULL;
}
m_IsOpened = false;
}
void Console_Open(void)
{
if (m_IsOpened) {
UI_Console_HandleClose(m_Console);
}
m_IsOpened = true;
UI_Console_HandleOpen(m_Console);
}
void Console_Close(void)
{
UI_Console_HandleClose(m_Console);
m_IsOpened = false;
}
bool Console_IsOpened(void)
{
return m_IsOpened;
}
void Console_ScrollLogs(void)
{
UI_Console_ScrollLogs(m_Console);
}
int32_t Console_GetVisibleLogCount(void)
{
return UI_Console_GetVisibleLogCount(m_Console);
}
int32_t Console_GetMaxLogCount(void)
{
return UI_Console_GetMaxLogCount(m_Console);
}
void Console_Log(const char *fmt, ...)
{
assert(fmt != NULL);
va_list va;
va_start(va, fmt);
const size_t text_length = vsnprintf(NULL, 0, fmt, va);
char *text = Memory_Alloc(text_length + 1);
va_end(va);
va_start(va, fmt);
vsnprintf(text, text_length + 1, fmt, va);
va_end(va);
LOG_INFO("%s", text);
M_LogMultiline(text);
Memory_FreePointer(&text);
}
COMMAND_RESULT Console_Eval(const char *const cmdline)
{
LOG_INFO("executing command: %s", cmdline);
const CONSOLE_COMMAND *matching_cmd = NULL;
CONSOLE_COMMAND **cmd = Console_GetCommands();
while (*cmd != NULL) {
char regex[strlen((*cmd)->prefix) + 13];
sprintf(regex, "^(%s)(\\s+.*)?$", (*cmd)->prefix);
if (String_Match(cmdline, regex)) {
matching_cmd = *cmd;
break;
}
*cmd++;
}
if (matching_cmd == NULL) {
Console_Log(GS(OSD_UNKNOWN_COMMAND), cmdline);
return CR_BAD_INVOCATION;
}
char *prefix = Memory_DupStr(cmdline);
char *args = "";
char *space = strchr(prefix, ' ');
if (space != NULL) {
*space = '\0';
args = space + 1;
}
const COMMAND_CONTEXT ctx = {
.cmd = matching_cmd,
.prefix = prefix,
.args = args,
};
assert(matching_cmd->proc != NULL);
const COMMAND_RESULT result = matching_cmd->proc(&ctx);
Memory_FreePointer(&prefix);
switch (result) {
case CR_BAD_INVOCATION:
Console_Log(GS(OSD_COMMAND_BAD_INVOCATION), cmdline);
break;
case CR_UNAVAILABLE:
Console_Log(GS(OSD_COMMAND_UNAVAILABLE));
break;
case CR_SUCCESS:
case CR_FAILURE:
// The commands themselves are responsible for handling logging in
// these scenarios.
break;
}
return result;
}
void Console_Draw(void)
{
if (m_Console == NULL) {
return;
}
Console_ScrollLogs();
if (Console_IsOpened() || Console_GetVisibleLogCount() > 0) {
Console_DrawBackdrop();
}
m_Console->draw(m_Console);
}

View file

@ -0,0 +1,57 @@
#include "game/game_string.h"
#include "memory.h"
#include <uthash.h>
typedef struct {
char *key;
char *value;
UT_hash_handle hh;
} M_STRING_ENTRY;
static M_STRING_ENTRY *m_StringTable = NULL;
void GameString_Define(const char *key, const char *value)
{
M_STRING_ENTRY *entry;
HASH_FIND_STR(m_StringTable, key, entry);
if (entry == NULL) {
entry = (M_STRING_ENTRY *)Memory_Alloc(sizeof(M_STRING_ENTRY));
entry->key = Memory_DupStr(key);
entry->value = Memory_DupStr(value);
HASH_ADD_KEYPTR(
hh, m_StringTable, entry->key, strlen(entry->key), entry);
} else {
Memory_Free(entry->value);
entry->value = Memory_DupStr(value);
}
}
bool GameString_IsKnown(const char *key)
{
M_STRING_ENTRY *entry;
HASH_FIND_STR(m_StringTable, key, entry);
return entry != NULL;
}
const char *GameString_Get(const char *key)
{
M_STRING_ENTRY *entry;
HASH_FIND_STR(m_StringTable, key, entry);
return entry ? entry->value : NULL;
}
void GameString_Clear(void)
{
M_STRING_ENTRY *entry, *tmp;
HASH_ITER(hh, m_StringTable, entry, tmp)
{
HASH_DEL(m_StringTable, entry);
Memory_Free(entry->key);
Memory_Free(entry->value);
Memory_Free(entry);
}
}

21
src/libtrx/game/items.c Normal file
View file

@ -0,0 +1,21 @@
#include "game/items.h"
#include "game/const.h"
#include "utils.h"
void Item_TakeDamage(
ITEM *const item, const int16_t damage, const bool hit_status)
{
#if TR_VERSION == 1
if (item->hit_points == DONT_TARGET) {
return;
}
#endif
item->hit_points -= damage;
CLAMPL(item->hit_points, 0);
if (hit_status) {
item->hit_status = 1;
}
}

View file

@ -0,0 +1,82 @@
#include "game/objects/names.h"
#include "game/game_string.h"
#include "memory.h"
#include "strings/fuzzy_match.h"
#include <assert.h>
typedef struct {
char *name;
} M_NAME_ENTRY;
static M_NAME_ENTRY m_NamesTable[O_NUMBER_OF] = { 0 };
static void M_ClearNames(void);
static void M_ClearNames(void)
{
for (GAME_OBJECT_ID object_id = 0; object_id < O_NUMBER_OF; object_id++) {
M_NAME_ENTRY *const entry = &m_NamesTable[object_id];
Memory_FreePointer(&entry->name);
}
}
void Object_SetName(const GAME_OBJECT_ID object_id, const char *const name)
{
M_NAME_ENTRY *const entry = &m_NamesTable[object_id];
Memory_FreePointer(&entry->name);
assert(name != NULL);
entry->name = Memory_DupStr(name);
}
const char *Object_GetName(const GAME_OBJECT_ID object_id)
{
M_NAME_ENTRY *const entry = &m_NamesTable[object_id];
return entry != NULL ? entry->name : NULL;
}
void Object_ResetNames(void)
{
M_ClearNames();
#define OBJ_NAME_DEFINE(object_id, name) Object_SetName(object_id, name);
#include "game/objects/names.def"
}
GAME_OBJECT_ID *Object_IdsFromName(
const char *user_input, int32_t *out_match_count,
bool (*filter)(GAME_OBJECT_ID))
{
VECTOR *source = Vector_Create(sizeof(STRING_FUZZY_SOURCE));
for (GAME_OBJECT_ID object_id = 0; object_id < O_NUMBER_OF; object_id++) {
if (filter != NULL && !filter(object_id)) {
continue;
}
STRING_FUZZY_SOURCE source_item = {
.key = Object_GetName(object_id),
.value = (void *)(intptr_t)object_id,
.weight = 1,
};
Vector_Add(source, &source_item);
}
VECTOR *matches = String_FuzzyMatch(user_input, source);
GAME_OBJECT_ID *results =
Memory_Alloc(sizeof(GAME_OBJECT_ID) * (matches->count + 1));
for (int32_t i = 0; i < matches->count; i++) {
const STRING_FUZZY_MATCH *const match = Vector_Get(matches, i);
results[i] = (GAME_OBJECT_ID)(intptr_t)match->value;
}
results[matches->count] = NO_OBJECT;
if (out_match_count != NULL) {
*out_match_count = matches->count;
}
Vector_Free(matches);
Vector_Free(source);
matches = NULL;
return results;
}

View file

@ -0,0 +1,43 @@
#include "game/ui/common.h"
#include "vector.h"
void UI_Init(void)
{
UI_Events_Init();
}
void UI_Shutdown(void)
{
UI_Events_Shutdown();
}
void UI_HandleKeyDown(const uint32_t key)
{
const EVENT event = {
.name = "key_down",
.sender = NULL,
.data = (void *)UI_TranslateInput(key),
};
UI_Events_Fire(&event);
}
void UI_HandleKeyUp(const uint32_t key)
{
const EVENT event = {
.name = "key_up",
.sender = NULL,
.data = (void *)UI_TranslateInput(key),
};
UI_Events_Fire(&event);
}
void UI_HandleTextEdit(const char *const text)
{
const EVENT event = {
.name = "text_edit",
.sender = NULL,
.data = (void *)text,
};
UI_Events_Fire(&event);
}

View file

@ -0,0 +1,49 @@
#include "game/ui/events.h"
#include "config/common.h"
#include <stddef.h>
static EVENT_MANAGER *m_EventManager = NULL;
static void M_HandleConfigChange(const EVENT *event, void *data);
static void M_HandleConfigChange(const EVENT *const event, void *const data)
{
const EVENT new_event = {
.name = "canvas_resize",
.sender = NULL,
.data = NULL,
};
EventManager_Fire(m_EventManager, &new_event);
}
void UI_Events_Init(void)
{
m_EventManager = EventManager_Create();
Config_SubscribeChanges(M_HandleConfigChange, NULL);
}
void UI_Events_Shutdown(void)
{
EventManager_Free(m_EventManager);
m_EventManager = NULL;
}
int32_t UI_Events_Subscribe(
const char *const event_name, const UI_WIDGET *const sender,
const EVENT_LISTENER listener, void *const user_data)
{
return EventManager_Subscribe(
m_EventManager, event_name, sender, listener, user_data);
}
void UI_Events_Unsubscribe(const int32_t listener_id)
{
EventManager_Unsubscribe(m_EventManager, listener_id);
}
void UI_Events_Fire(const EVENT *const event)
{
EventManager_Fire(m_EventManager, event);
}

View file

@ -0,0 +1,235 @@
#include "game/ui/widgets/console.h"
#include "game/clock.h"
#include "game/console/common.h"
#include "game/ui/common.h"
#include "game/ui/events.h"
#include "game/ui/widgets/label.h"
#include "game/ui/widgets/prompt.h"
#include "game/ui/widgets/spacer.h"
#include "game/ui/widgets/stack.h"
#include "memory.h"
#include "utils.h"
#include <string.h>
#define WINDOW_MARGIN 5
#define LOG_HEIGHT 16
#define LOG_MARGIN 10
#define MAX_LOG_LINES 20
#define LOG_SCALE 0.8
#define DELAY_PER_CHAR 0.2
typedef struct {
UI_WIDGET_VTABLE vtable;
UI_WIDGET *container;
UI_WIDGET *prompt;
UI_WIDGET *spacer;
char *log_lines;
int32_t logs_on_screen;
int32_t listener1;
int32_t listener2;
int32_t listener3;
struct {
double expire_at;
UI_WIDGET *label;
} logs[MAX_LOG_LINES];
} UI_CONSOLE;
static void M_HandlePromptCancel(const EVENT *event, void *data);
static void M_HandlePromptConfirm(const EVENT *event, void *data);
static void M_HandleCanvasResize(const EVENT *event, void *data);
static void M_UpdateLogCount(UI_CONSOLE *self);
static int32_t M_GetWidth(const UI_CONSOLE *self);
static int32_t M_GetHeight(const UI_CONSOLE *self);
static void M_SetPosition(UI_CONSOLE *self, int32_t x, int32_t y);
static void M_Control(UI_CONSOLE *self);
static void M_Draw(UI_CONSOLE *self);
static void M_Free(UI_CONSOLE *self);
static void M_HandlePromptCancel(const EVENT *const event, void *const data)
{
Console_Close();
}
static void M_HandlePromptConfirm(const EVENT *const event, void *const data)
{
const char *text = event->data;
Console_Eval(text);
Console_Close();
}
static void M_HandleCanvasResize(const EVENT *event, void *data)
{
UI_CONSOLE *const self = (UI_CONSOLE *)data;
UI_Stack_SetSize(self->container, M_GetWidth(self), M_GetHeight(self));
}
static void M_UpdateLogCount(UI_CONSOLE *const self)
{
self->logs_on_screen = 0;
for (int32_t i = MAX_LOG_LINES - 1; i >= 0; i--) {
if (self->logs[i].expire_at) {
self->logs_on_screen = i + 1;
break;
}
}
}
static int32_t M_GetWidth(const UI_CONSOLE *const self)
{
return UI_GetCanvasWidth() - 2 * WINDOW_MARGIN;
}
static int32_t M_GetHeight(const UI_CONSOLE *const self)
{
return UI_GetCanvasHeight() - 2 * WINDOW_MARGIN;
}
static void M_SetPosition(UI_CONSOLE *const self, int32_t x, int32_t y)
{
return self->container->set_position(self->container, x, y);
}
static void M_Control(UI_CONSOLE *const self)
{
if (self->container->control != NULL) {
self->container->control(self->container);
}
}
static void M_Draw(UI_CONSOLE *const self)
{
if (self->container->draw != NULL) {
self->container->draw(self->container);
}
}
static void M_Free(UI_CONSOLE *const self)
{
self->spacer->free(self->spacer);
self->prompt->free(self->prompt);
self->container->free(self->container);
UI_Events_Unsubscribe(self->listener1);
UI_Events_Unsubscribe(self->listener2);
UI_Events_Unsubscribe(self->listener3);
Memory_Free(self);
}
UI_WIDGET *UI_Console_Create(void)
{
UI_CONSOLE *const self = Memory_Alloc(sizeof(UI_CONSOLE));
self->vtable = (UI_WIDGET_VTABLE) {
.control = (UI_WIDGET_CONTROL)M_Control,
.draw = (UI_WIDGET_DRAW)M_Draw,
.get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth,
.get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight,
.set_position = (UI_WIDGET_SET_POSITION)M_SetPosition,
.free = (UI_WIDGET_FREE)M_Free,
};
self->container = UI_Stack_Create(
UI_STACK_LAYOUT_VERTICAL, M_GetWidth(self), M_GetHeight(self));
UI_Stack_SetVAlign(self->container, UI_STACK_V_ALIGN_BOTTOM);
for (int32_t i = MAX_LOG_LINES - 1; i >= 0; i--) {
self->logs[i].label =
UI_Label_Create("", UI_LABEL_AUTO_SIZE, LOG_HEIGHT * LOG_SCALE);
UI_Label_SetScale(self->logs[i].label, LOG_SCALE);
UI_Stack_AddChild(self->container, self->logs[i].label);
}
self->spacer = UI_Spacer_Create(LOG_MARGIN, LOG_MARGIN);
UI_Stack_AddChild(self->container, self->spacer);
self->prompt = UI_Prompt_Create(UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE);
UI_Stack_AddChild(self->container, self->prompt);
M_SetPosition(self, WINDOW_MARGIN, WINDOW_MARGIN);
self->listener1 = UI_Events_Subscribe(
"confirm", self->prompt, M_HandlePromptConfirm, NULL);
self->listener2 =
UI_Events_Subscribe("cancel", self->prompt, M_HandlePromptCancel, NULL);
self->listener3 =
UI_Events_Subscribe("canvas_resize", NULL, M_HandleCanvasResize, self);
return (UI_WIDGET *)self;
}
void UI_Console_HandleOpen(UI_WIDGET *const widget)
{
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
UI_Prompt_SetFocus(self->prompt, true);
}
void UI_Console_HandleClose(UI_WIDGET *const widget)
{
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
UI_Prompt_SetFocus(self->prompt, false);
UI_Prompt_Clear(self->prompt);
}
void UI_Console_HandleLog(UI_WIDGET *const widget, const char *const text)
{
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
int32_t dst_idx = -1;
for (int32_t i = MAX_LOG_LINES - 1; i > 0; i--) {
if (self->logs[i].label == NULL) {
continue;
}
UI_Label_ChangeText(
self->logs[i].label, UI_Label_GetText(self->logs[i - 1].label));
self->logs[i].expire_at = self->logs[i - 1].expire_at;
}
if (self->logs[0].label == NULL) {
return;
}
self->logs[0].expire_at =
Clock_GetHighPrecisionCounter() + 1000 * strlen(text) * DELAY_PER_CHAR;
UI_Label_ChangeText(self->logs[0].label, text);
UI_Stack_DoLayout(self->container);
M_UpdateLogCount(self);
}
void UI_Console_ScrollLogs(UI_WIDGET *const widget)
{
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
int32_t i = MAX_LOG_LINES - 1;
while (i >= 0 && !self->logs[i].expire_at) {
i--;
}
bool need_layout = false;
while (i >= 0 && self->logs[i].expire_at
&& Clock_GetHighPrecisionCounter() >= self->logs[i].expire_at) {
self->logs[i].expire_at = 0;
UI_Label_ChangeText(self->logs[i].label, "");
need_layout = true;
i--;
}
if (need_layout) {
M_UpdateLogCount(self);
UI_Stack_DoLayout(self->container);
}
}
int32_t UI_Console_GetVisibleLogCount(UI_WIDGET *const widget)
{
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
return self->logs_on_screen;
}
int32_t UI_Console_GetMaxLogCount(UI_WIDGET *const widget)
{
return MAX_LOG_LINES;
}

View file

@ -0,0 +1,307 @@
#include "game/ui/widgets/prompt.h"
#include "game/input.h"
#include "game/ui/common.h"
#include "game/ui/events.h"
#include "game/ui/widgets/label.h"
#include "memory.h"
#include "strings.h"
#include <string.h>
static const char m_ValidPromptChars[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ";
typedef struct {
UI_WIDGET_VTABLE vtable;
UI_WIDGET *label;
UI_WIDGET *caret;
int32_t listener1;
int32_t listener2;
struct {
int32_t x;
int32_t y;
} pos;
bool is_focused;
int32_t current_text_capacity;
char *current_text;
int32_t caret_pos;
} UI_PROMPT;
static void M_UpdatePromptLabel(UI_PROMPT *self);
static void M_UpdateCaretLabel(UI_PROMPT *self);
static void M_MoveCaretLeft(UI_PROMPT *self);
static void M_MoveCaretRight(UI_PROMPT *self);
static void M_MoveCaretStart(UI_PROMPT *self);
static void M_MoveCaretEnd(UI_PROMPT *self);
static void M_DeleteCharBack(UI_PROMPT *self);
static void M_Confirm(UI_PROMPT *self);
static void M_Cancel(UI_PROMPT *self);
static void M_Clear(UI_PROMPT *self);
static int32_t M_GetWidth(const UI_PROMPT *self);
static int32_t M_GetHeight(const UI_PROMPT *self);
static void M_SetPosition(UI_PROMPT *self, int32_t x, int32_t y);
static void M_Control(UI_PROMPT *self);
static void M_Draw(UI_PROMPT *self);
static void M_Free(UI_PROMPT *self);
static void M_HandleKeyDown(const EVENT *event, void *user_data);
static void M_HandleTextEdit(const EVENT *event, void *user_data);
static void M_UpdatePromptLabel(UI_PROMPT *const self)
{
UI_Label_ChangeText(self->label, self->current_text);
}
static void M_UpdateCaretLabel(UI_PROMPT *const self)
{
const char old = self->current_text[self->caret_pos];
self->current_text[self->caret_pos] = '\0';
UI_Label_ChangeText(self->label, self->current_text);
const int32_t width = UI_Label_MeasureTextWidth(self->label);
self->current_text[self->caret_pos] = old;
UI_Label_ChangeText(self->label, self->current_text);
self->caret->set_position(self->caret, self->pos.x + width, self->pos.y);
}
static int32_t M_GetWidth(const UI_PROMPT *const self)
{
return self->label->get_width(self->label);
}
static int32_t M_GetHeight(const UI_PROMPT *const self)
{
return self->label->get_height(self->label);
}
static void M_SetPosition(
UI_PROMPT *const self, const int32_t x, const int32_t y)
{
self->pos.x = x;
self->pos.y = y;
self->label->set_position(self->label, x, y);
M_UpdateCaretLabel(self);
}
static void M_Control(UI_PROMPT *const self)
{
if (self->label->control != NULL) {
self->label->control(self->label);
}
if (self->caret->control != NULL) {
self->caret->control(self->caret);
}
}
static void M_Draw(UI_PROMPT *const self)
{
if (self->label->draw != NULL) {
self->label->draw(self->label);
}
if (self->caret->draw != NULL) {
self->caret->draw(self->caret);
}
}
static void M_Free(UI_PROMPT *const self)
{
self->label->free(self->label);
self->caret->free(self->caret);
UI_Events_Unsubscribe(self->listener1);
UI_Events_Unsubscribe(self->listener2);
Memory_FreePointer(&self->current_text);
Memory_Free(self);
}
static void M_MoveCaretLeft(UI_PROMPT *const self)
{
if (self->caret_pos > 0) {
self->caret_pos--;
M_UpdateCaretLabel(self);
}
}
static void M_MoveCaretRight(UI_PROMPT *const self)
{
if (self->caret_pos < (int32_t)strlen(self->current_text)) {
self->caret_pos++;
M_UpdateCaretLabel(self);
}
}
static void M_MoveCaretStart(UI_PROMPT *const self)
{
self->caret_pos = 0;
M_UpdateCaretLabel(self);
}
static void M_MoveCaretEnd(UI_PROMPT *const self)
{
self->caret_pos = strlen(self->current_text);
M_UpdateCaretLabel(self);
}
static void M_DeleteCharBack(UI_PROMPT *const self)
{
if (self->caret_pos <= 0) {
return;
}
memmove(
self->current_text + self->caret_pos - 1,
self->current_text + self->caret_pos,
strlen(self->current_text) + 1 - self->caret_pos);
self->caret_pos--;
M_UpdatePromptLabel(self);
M_UpdateCaretLabel(self);
}
static void M_Confirm(UI_PROMPT *const self)
{
if (String_IsEmpty(self->current_text)) {
M_Cancel(self);
return;
}
const EVENT event = {
.name = "confirm",
.sender = self,
.data = self->current_text,
};
UI_Events_Fire(&event);
M_Clear(self);
M_UpdateCaretLabel(self);
}
static void M_Cancel(UI_PROMPT *const self)
{
const EVENT event = {
.name = "cancel",
.sender = self,
.data = self->current_text,
};
UI_Events_Fire(&event);
M_Clear(self);
}
static void M_Clear(UI_PROMPT *const self)
{
strcpy(self->current_text, "");
self->caret_pos = 0;
M_UpdatePromptLabel(self);
M_UpdateCaretLabel(self);
}
static void M_HandleKeyDown(const EVENT *const event, void *const user_data)
{
const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data;
UI_PROMPT *const self = user_data;
if (!self->is_focused) {
return;
}
// clang-format off
switch (key) {
case UI_KEY_LEFT: M_MoveCaretLeft(self); break;
case UI_KEY_RIGHT: M_MoveCaretRight(self); break;
case UI_KEY_HOME: M_MoveCaretStart(self); break;
case UI_KEY_END: M_MoveCaretEnd(self); break;
case UI_KEY_BACK: M_DeleteCharBack(self); break;
case UI_KEY_RETURN: M_Confirm(self); break;
case UI_KEY_ESCAPE: M_Cancel(self); break;
}
// clang-format on
}
static void M_HandleTextEdit(const EVENT *const event, void *const user_data)
{
const char *insert_string = event->data;
const size_t insert_length = strlen(insert_string);
UI_PROMPT *const self = user_data;
if (!self->is_focused) {
return;
}
if (strlen(insert_string) != 1
|| !strstr(m_ValidPromptChars, insert_string)) {
return;
}
const size_t available_space =
self->current_text_capacity - strlen(self->current_text);
if (insert_length >= available_space) {
self->current_text_capacity *= 2;
self->current_text =
Memory_Realloc(self->current_text, self->current_text_capacity);
}
memmove(
self->current_text + self->caret_pos + insert_length,
self->current_text + self->caret_pos,
strlen(self->current_text) + 1 - self->caret_pos);
memcpy(self->current_text + self->caret_pos, insert_string, insert_length);
self->caret_pos += insert_length;
M_UpdatePromptLabel(self);
M_UpdateCaretLabel(self);
}
UI_WIDGET *UI_Prompt_Create(const int32_t width, const int32_t height)
{
UI_PROMPT *const self = Memory_Alloc(sizeof(UI_PROMPT));
self->vtable = (UI_WIDGET_VTABLE) {
.control = (UI_WIDGET_CONTROL)M_Control,
.draw = (UI_WIDGET_DRAW)M_Draw,
.get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth,
.get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight,
.set_position = (UI_WIDGET_SET_POSITION)M_SetPosition,
.free = (UI_WIDGET_FREE)M_Free,
};
self->current_text_capacity = 1;
self->current_text = Memory_Alloc(self->current_text_capacity);
self->label = UI_Label_Create(self->current_text, width, height);
self->caret = UI_Label_Create("", width, height);
UI_Label_SetZIndex(self->label, 16);
UI_Label_SetZIndex(self->caret, 8);
self->is_focused = false;
self->listener1 =
UI_Events_Subscribe("key_down", NULL, M_HandleKeyDown, self);
self->listener2 =
UI_Events_Subscribe("text_edit", NULL, M_HandleTextEdit, self);
return (UI_WIDGET *)self;
}
void UI_Prompt_SetSize(
UI_WIDGET *const widget, const int32_t width, const int32_t height)
{
UI_PROMPT *const self = (UI_PROMPT *)widget;
UI_Label_SetSize(self->label, width, height);
}
void UI_Prompt_SetFocus(UI_WIDGET *const widget, const bool is_focused)
{
UI_PROMPT *const self = (UI_PROMPT *)widget;
self->is_focused = is_focused;
if (is_focused) {
Input_EnterListenMode();
UI_Label_ChangeText(self->caret, UI_Prompt_GetPromptChar());
UI_Label_Flash(self->caret, 1, UI_Prompt_GetCaretFlashRate());
} else {
Input_ExitListenMode();
UI_Label_ChangeText(self->caret, "");
}
}
void UI_Prompt_Clear(UI_WIDGET *const widget)
{
UI_PROMPT *const self = (UI_PROMPT *)widget;
M_Clear(self);
}

View file

@ -0,0 +1,60 @@
#include "game/ui/widgets/spacer.h"
#include "memory.h"
typedef struct {
UI_WIDGET_VTABLE vtable;
int32_t width;
int32_t height;
} UI_SPACER;
static int32_t M_GetWidth(const UI_SPACER *self);
static int32_t M_GetHeight(const UI_SPACER *self);
static void M_SetPosition(UI_SPACER *self, int32_t x, int32_t y);
static void M_Control(UI_SPACER *self);
static void M_Draw(UI_SPACER *self);
static void M_Free(UI_SPACER *self);
static int32_t M_GetWidth(const UI_SPACER *const self)
{
return self->width;
}
static int32_t M_GetHeight(const UI_SPACER *const self)
{
return self->height;
}
static void M_SetPosition(
UI_SPACER *const self, const int32_t x, const int32_t y)
{
}
static void M_Free(UI_SPACER *const self)
{
Memory_Free(self);
}
UI_WIDGET *UI_Spacer_Create(const int32_t width, const int32_t height)
{
UI_SPACER *const self = Memory_Alloc(sizeof(UI_SPACER));
self->vtable = (UI_WIDGET_VTABLE) {
.control = NULL,
.draw = NULL,
.get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth,
.get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight,
.set_position = (UI_WIDGET_SET_POSITION)M_SetPosition,
.free = (UI_WIDGET_FREE)M_Free,
};
self->width = width;
self->height = height;
return (UI_WIDGET *)self;
}
void UI_Spacer_SetSize(
UI_WIDGET *const widget, const int32_t width, const int32_t height)
{
UI_SPACER *const self = (UI_SPACER *)widget;
self->width = width;
self->height = height;
}

View file

@ -0,0 +1,255 @@
#include "game/ui/widgets/stack.h"
#include "memory.h"
#include "utils.h"
#include "vector.h"
typedef struct {
UI_WIDGET_VTABLE vtable;
struct {
UI_STACK_H_ALIGN h;
UI_STACK_V_ALIGN v;
} align;
int32_t width;
int32_t height;
int32_t x;
int32_t y;
UI_STACK_LAYOUT layout;
VECTOR *children;
} UI_STACK;
static int32_t M_GetChildrenWidth(const UI_STACK *self);
static int32_t M_GetChildrenHeight(const UI_STACK *self);
static int32_t M_GetHeight(const UI_STACK *self);
static int32_t M_GetWidth(const UI_STACK *self);
static void M_SetPosition(UI_STACK *self, int32_t x, int32_t y);
static void M_Control(UI_STACK *self);
static void M_Draw(UI_STACK *self);
static void M_Free(UI_STACK *self);
static int32_t M_GetChildrenWidth(const UI_STACK *const self)
{
int32_t result = 0;
for (int32_t i = 0; i < self->children->count; i++) {
const UI_WIDGET *const child =
*(UI_WIDGET **)Vector_Get(self->children, i);
switch (self->layout) {
case UI_STACK_LAYOUT_HORIZONTAL:
result += child->get_width(child);
break;
case UI_STACK_LAYOUT_VERTICAL:
result = MAX(result, child->get_width(child));
break;
}
}
return result;
}
static int32_t M_GetChildrenHeight(const UI_STACK *const self)
{
int32_t result = 0;
for (int32_t i = 0; i < self->children->count; i++) {
const UI_WIDGET *const child =
*(UI_WIDGET **)Vector_Get(self->children, i);
switch (self->layout) {
case UI_STACK_LAYOUT_HORIZONTAL:
result = MAX(result, child->get_height(child));
break;
case UI_STACK_LAYOUT_VERTICAL:
result += child->get_height(child);
break;
}
}
return result;
}
static int32_t M_GetWidth(const UI_STACK *const self)
{
if (self->width != UI_STACK_AUTO_SIZE) {
return self->width;
}
return M_GetChildrenWidth(self);
}
static int32_t M_GetHeight(const UI_STACK *const self)
{
if (self->height != UI_STACK_AUTO_SIZE) {
return self->height;
}
return M_GetChildrenHeight(self);
}
static void M_SetPosition(
UI_STACK *const self, const int32_t x, const int32_t y)
{
self->x = x;
self->y = y;
UI_Stack_DoLayout((UI_WIDGET *)self);
}
static void M_Control(UI_STACK *const self)
{
for (int32_t i = 0; i < self->children->count; i++) {
UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i);
if (child->control != NULL) {
child->control(child);
}
}
}
static void M_Draw(UI_STACK *const self)
{
for (int32_t i = 0; i < self->children->count; i++) {
UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i);
if (child->draw != NULL) {
child->draw(child);
}
}
}
static void M_Free(UI_STACK *const self)
{
Vector_Free(self->children);
Memory_Free(self);
}
void UI_Stack_AddChild(UI_WIDGET *const widget, UI_WIDGET *const child)
{
UI_STACK *const self = (UI_STACK *)widget;
Vector_Add(self->children, (void *)&child);
}
UI_WIDGET *UI_Stack_Create(
const UI_STACK_LAYOUT layout, const int32_t width, const int32_t height)
{
UI_STACK *const self = Memory_Alloc(sizeof(UI_STACK));
self->vtable = (UI_WIDGET_VTABLE) {
.control = (UI_WIDGET_CONTROL)M_Control,
.draw = (UI_WIDGET_DRAW)M_Draw,
.get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth,
.get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight,
.set_position = (UI_WIDGET_SET_POSITION)M_SetPosition,
.free = (UI_WIDGET_FREE)M_Free,
};
self->align.h = UI_STACK_H_ALIGN_LEFT;
self->align.v = UI_STACK_V_ALIGN_TOP;
self->width = width;
self->height = height;
self->layout = layout;
self->children = Vector_Create(sizeof(UI_WIDGET *));
return (UI_WIDGET *)self;
}
void UI_Stack_SetHAlign(UI_WIDGET *const widget, const UI_STACK_H_ALIGN align)
{
UI_STACK *const self = (UI_STACK *)widget;
self->align.h = align;
}
void UI_Stack_SetVAlign(UI_WIDGET *const widget, const UI_STACK_V_ALIGN align)
{
UI_STACK *const self = (UI_STACK *)widget;
self->align.v = align;
}
void UI_Stack_SetSize(
UI_WIDGET *const widget, const int32_t width, const int32_t height)
{
UI_STACK *const self = (UI_STACK *)widget;
self->width = width;
self->height = height;
UI_Stack_DoLayout(widget);
}
void UI_Stack_DoLayout(UI_WIDGET *const widget)
{
UI_STACK *const self = (UI_STACK *)widget;
const int32_t self_width = M_GetWidth(self);
const int32_t self_height = M_GetHeight(self);
const int32_t children_width = M_GetChildrenWidth(self);
const int32_t children_height = M_GetChildrenHeight(self);
// calculate main axis placement
int32_t x = -999;
int32_t y = -999;
switch (self->layout) {
case UI_STACK_LAYOUT_HORIZONTAL:
switch (self->align.h) {
case UI_STACK_H_ALIGN_LEFT:
x = self->x;
break;
case UI_STACK_H_ALIGN_CENTER:
x = self->x + (self_width - children_width) / 2;
break;
case UI_STACK_H_ALIGN_RIGHT:
x = self->x + self_width - children_width;
break;
}
break;
case UI_STACK_LAYOUT_VERTICAL:
switch (self->align.v) {
case UI_STACK_V_ALIGN_TOP:
y = self->y;
break;
case UI_STACK_V_ALIGN_CENTER:
y = self->y + (self_height - children_height) / 2;
break;
case UI_STACK_V_ALIGN_BOTTOM:
y = self->y + self_height - children_height;
break;
}
break;
}
for (int32_t i = 0; i < self->children->count; i++) {
UI_WIDGET *const child = *(UI_WIDGET **)Vector_Get(self->children, i);
const int32_t child_width = child->get_width(child);
const int32_t child_height = child->get_height(child);
// calculate other axis placement
switch (self->layout) {
case UI_STACK_LAYOUT_HORIZONTAL:
switch (self->align.v) {
case UI_STACK_V_ALIGN_TOP:
y = self->y;
break;
case UI_STACK_V_ALIGN_CENTER:
y = self->y + (self_height - child_height) / 2;
break;
case UI_STACK_V_ALIGN_BOTTOM:
y = self->y + self_height - child_height;
break;
}
break;
case UI_STACK_LAYOUT_VERTICAL:
switch (self->align.h) {
case UI_STACK_H_ALIGN_LEFT:
x = self->x;
break;
case UI_STACK_H_ALIGN_CENTER:
x = self->x + (self_width - child_width) / 2;
break;
case UI_STACK_H_ALIGN_RIGHT:
x = self->x + self_width - child_width;
break;
}
break;
}
child->set_position(child, x, y);
// calculate main axis offset
switch (self->layout) {
case UI_STACK_LAYOUT_HORIZONTAL:
x += child_width;
break;
case UI_STACK_LAYOUT_VERTICAL:
y += child_height;
break;
}
}
}

View file

@ -0,0 +1,118 @@
#include "gfx/2d/2d_renderer.h"
#include "gfx/gl/gl_core_3_3.h"
#include "gfx/gl/utils.h"
#include "log.h"
#include <assert.h>
void GFX_2D_Renderer_Init(GFX_2D_RENDERER *renderer)
{
LOG_INFO("");
assert(renderer);
GFX_GL_Buffer_Init(&renderer->surface_buffer, GL_ARRAY_BUFFER);
GFX_GL_Buffer_Bind(&renderer->surface_buffer);
GLfloat verts[] = { 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 1.0, 0.0, 1.0, 1.0 };
GFX_GL_Buffer_Data(
&renderer->surface_buffer, sizeof(verts), verts, GL_STATIC_DRAW);
GFX_GL_VertexArray_Init(&renderer->surface_format);
GFX_GL_VertexArray_Bind(&renderer->surface_format);
GFX_GL_VertexArray_Attribute(
&renderer->surface_format, 0, 2, GL_FLOAT, GL_FALSE, 0, 0);
GFX_GL_Texture_Init(&renderer->surface_texture, GL_TEXTURE_2D);
GFX_GL_Sampler_Init(&renderer->sampler);
GFX_GL_Sampler_Bind(&renderer->sampler, 0);
GFX_GL_Sampler_Parameteri(
&renderer->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GFX_GL_Sampler_Parameteri(
&renderer->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GFX_GL_Sampler_Parameteri(
&renderer->sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
GFX_GL_Sampler_Parameteri(
&renderer->sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
GFX_GL_Program_Init(&renderer->program);
GFX_GL_Program_AttachShader(
&renderer->program, GL_VERTEX_SHADER, "shaders/2d.glsl");
GFX_GL_Program_AttachShader(
&renderer->program, GL_FRAGMENT_SHADER, "shaders/2d.glsl");
GFX_GL_Program_Link(&renderer->program);
GFX_GL_Program_FragmentData(&renderer->program, "fragColor");
GFX_GL_CheckError();
}
void GFX_2D_Renderer_Close(GFX_2D_RENDERER *renderer)
{
LOG_INFO("");
assert(renderer);
GFX_GL_VertexArray_Close(&renderer->surface_format);
GFX_GL_Buffer_Close(&renderer->surface_buffer);
GFX_GL_Texture_Close(&renderer->surface_texture);
GFX_GL_Sampler_Close(&renderer->sampler);
GFX_GL_Program_Close(&renderer->program);
}
void GFX_2D_Renderer_Upload(
GFX_2D_RENDERER *renderer, GFX_2D_SURFACE_DESC *desc, const uint8_t *data)
{
const uint32_t width = desc->width;
const uint32_t height = desc->height;
GFX_GL_Texture_Bind(&renderer->surface_texture);
// TODO: implement texture packs
// update buffer if the size is unchanged, otherwise create a new one
if (width != renderer->width || height != renderer->height) {
renderer->width = width;
renderer->height = height;
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA, renderer->width, renderer->height, 0,
desc->tex_format, desc->tex_type, data);
GFX_GL_CheckError();
} else {
glTexSubImage2D(
GL_TEXTURE_2D, 0, 0, 0, renderer->width, renderer->height,
desc->tex_format, desc->tex_type, data);
GFX_GL_CheckError();
}
}
void GFX_2D_Renderer_Render(GFX_2D_RENDERER *renderer)
{
GFX_GL_Program_Bind(&renderer->program);
GFX_GL_Buffer_Bind(&renderer->surface_buffer);
GFX_GL_VertexArray_Bind(&renderer->surface_format);
GFX_GL_Texture_Bind(&renderer->surface_texture);
GFX_GL_Sampler_Bind(&renderer->sampler, 0);
GLboolean blend = glIsEnabled(GL_BLEND);
if (blend) {
glDisable(GL_BLEND);
}
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
GLboolean depth_test = glIsEnabled(GL_DEPTH_TEST);
if (depth_test) {
glDisable(GL_DEPTH_TEST);
}
glDrawArrays(GL_TRIANGLES, 0, 6);
GFX_GL_CheckError();
if (blend) {
glEnable(GL_BLEND);
}
if (depth_test) {
glEnable(GL_DEPTH_TEST);
}
}

View file

@ -0,0 +1,129 @@
#include "gfx/2d/2d_surface.h"
#include "gfx/context.h"
#include "log.h"
#include "memory.h"
#include <assert.h>
#include <string.h>
GFX_2D_SURFACE *GFX_2D_Surface_Create(const GFX_2D_SURFACE_DESC *desc)
{
GFX_2D_SURFACE *surface = Memory_Alloc(sizeof(GFX_2D_SURFACE));
GFX_2D_Surface_Init(surface, desc);
return surface;
}
GFX_2D_SURFACE *GFX_2D_Surface_CreateFromImage(const IMAGE *image)
{
GFX_2D_SURFACE *surface = Memory_Alloc(sizeof(GFX_2D_SURFACE));
surface->is_locked = false;
surface->is_dirty = true;
surface->desc.width = image->width;
surface->desc.height = image->height;
surface->desc.bit_count = 24;
surface->desc.tex_format = GL_RGB;
surface->desc.tex_type = GL_UNSIGNED_BYTE;
surface->desc.pitch = surface->desc.width * (surface->desc.bit_count / 8);
surface->desc.pixels = NULL;
surface->buffer = Memory_Alloc(surface->desc.pitch * surface->desc.height);
memcpy(
surface->buffer, image->data,
surface->desc.pitch * surface->desc.height);
return surface;
}
void GFX_2D_Surface_Free(GFX_2D_SURFACE *surface)
{
if (surface) {
GFX_2D_Surface_Close(surface);
Memory_FreePointer(&surface);
}
}
void GFX_2D_Surface_Init(
GFX_2D_SURFACE *surface, const GFX_2D_SURFACE_DESC *desc)
{
surface->is_locked = false;
surface->is_dirty = false;
surface->desc = *desc;
GFX_2D_SURFACE_DESC display_desc = {
.bit_count = 32,
.width = GFX_Context_GetDisplayWidth(),
.height = GFX_Context_GetDisplayHeight(),
};
if (!surface->desc.width || !surface->desc.height) {
surface->desc.width = display_desc.width;
surface->desc.height = display_desc.height;
}
if (!surface->desc.bit_count) {
surface->desc.bit_count = display_desc.bit_count;
}
if (!surface->desc.tex_format) {
surface->desc.tex_format = GL_BGRA;
}
if (!surface->desc.tex_type) {
surface->desc.tex_type = GL_UNSIGNED_INT_8_8_8_8_REV;
}
surface->desc.pitch = surface->desc.width * (surface->desc.bit_count / 8);
surface->buffer = Memory_Alloc(surface->desc.pitch * surface->desc.height);
surface->desc.pixels = NULL;
}
void GFX_2D_Surface_Close(GFX_2D_SURFACE *surface)
{
Memory_FreePointer(&surface->buffer);
}
bool GFX_2D_Surface_Clear(GFX_2D_SURFACE *surface)
{
if (surface->is_locked) {
LOG_ERROR("Surface is locked");
return false;
}
surface->is_dirty = true;
memset(surface->buffer, 0, surface->desc.pitch * surface->desc.height);
return true;
}
bool GFX_2D_Surface_Lock(GFX_2D_SURFACE *surface, GFX_2D_SURFACE_DESC *out_desc)
{
assert(surface != NULL);
if (surface->is_locked) {
LOG_ERROR("Surface is busy");
return false;
}
// assign pixels
surface->desc.pixels = surface->buffer;
surface->is_locked = true;
surface->is_dirty = true;
*out_desc = surface->desc;
return true;
}
bool GFX_2D_Surface_Unlock(GFX_2D_SURFACE *surface)
{
// ensure that the surface is actually locked
if (!surface->is_locked) {
LOG_ERROR("Surface is not locked");
return false;
}
// unassign pixels
surface->desc.pixels = NULL;
surface->is_locked = false;
return true;
}

View file

@ -0,0 +1,359 @@
#include "gfx/3d/3d_renderer.h"
#include "gfx/context.h"
#include "gfx/gl/utils.h"
#include "log.h"
#include <assert.h>
#include <stddef.h>
static void M_SelectTextureImpl(GFX_3D_RENDERER *renderer, int texture_num);
static void M_SelectTextureImpl(GFX_3D_RENDERER *renderer, int texture_num)
{
assert(renderer);
GFX_GL_TEXTURE *texture = NULL;
if (texture_num == GFX_ENV_MAP_TEXTURE) {
texture = renderer->env_map_texture;
} else if (texture_num != GFX_NO_TEXTURE) {
assert(texture_num >= 0);
assert(texture_num < GFX_MAX_TEXTURES);
texture = renderer->textures[texture_num];
}
if (texture == NULL) {
glBindTexture(GL_TEXTURE_2D, 0);
GFX_GL_CheckError();
return;
}
GFX_GL_Texture_Bind(texture);
}
void GFX_3D_Renderer_Init(
GFX_3D_RENDERER *renderer, const GFX_CONFIG *const config)
{
LOG_INFO("");
assert(renderer);
renderer->config = config;
renderer->selected_texture_num = GFX_NO_TEXTURE;
for (int i = 0; i < GFX_MAX_TEXTURES; i++) {
renderer->textures[i] = NULL;
}
GFX_GL_Sampler_Init(&renderer->sampler);
GFX_GL_Sampler_Bind(&renderer->sampler, 0);
GFX_GL_Sampler_Parameterf(
&renderer->sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, 0);
GFX_GL_Sampler_Parameteri(
&renderer->sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
GFX_GL_Sampler_Parameteri(
&renderer->sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
GFX_GL_Program_Init(&renderer->program);
GFX_GL_Program_AttachShader(
&renderer->program, GL_VERTEX_SHADER, "shaders/3d.glsl");
GFX_GL_Program_AttachShader(
&renderer->program, GL_FRAGMENT_SHADER, "shaders/3d.glsl");
GFX_GL_Program_Link(&renderer->program);
renderer->loc_mat_projection =
GFX_GL_Program_UniformLocation(&renderer->program, "matProjection");
renderer->loc_mat_model_view =
GFX_GL_Program_UniformLocation(&renderer->program, "matModelView");
renderer->loc_texturing_enabled =
GFX_GL_Program_UniformLocation(&renderer->program, "texturingEnabled");
renderer->loc_smoothing_enabled =
GFX_GL_Program_UniformLocation(&renderer->program, "smoothingEnabled");
GFX_GL_Program_FragmentData(&renderer->program, "fragColor");
GFX_GL_Program_Bind(&renderer->program);
// negate Z axis so the model is rendered behind the viewport, which is
// better than having a negative z_near in the ortho matrix, which seems
// to mess up depth testing
GLfloat model_view[4][4] = {
{ +1.0f, +0.0f, +0.0f, +0.0f },
{ +0.0f, +1.0f, +0.0f, +0.0f },
{ +0.0f, +0.0f, -1.0f, +0.0f },
{ +0.0f, +0.0f, +0.0f, +1.0f },
};
GFX_GL_Program_UniformMatrix4fv(
&renderer->program, renderer->loc_mat_model_view, 1, GL_FALSE,
&model_view[0][0]);
GFX_3D_VertexStream_Init(&renderer->vertex_stream);
GFX_GL_CheckError();
}
void GFX_3D_Renderer_Close(GFX_3D_RENDERER *renderer)
{
LOG_INFO("");
assert(renderer);
GFX_3D_VertexStream_Close(&renderer->vertex_stream);
GFX_GL_Program_Close(&renderer->program);
GFX_GL_Sampler_Close(&renderer->sampler);
}
void GFX_3D_Renderer_RenderBegin(GFX_3D_RENDERER *renderer)
{
assert(renderer);
glEnable(GL_BLEND);
glLineWidth(renderer->config->line_width);
glPolygonMode(
GL_FRONT_AND_BACK,
renderer->config->enable_wireframe ? GL_LINE : GL_FILL);
GFX_GL_CheckError();
GFX_GL_Program_Bind(&renderer->program);
GFX_3D_VertexStream_Bind(&renderer->vertex_stream);
GFX_GL_Sampler_Bind(&renderer->sampler, 0);
GFX_3D_Renderer_RestoreTexture(renderer);
const float left = 0.0f;
const float top = 0.0f;
const float right = GFX_Context_GetDisplayWidth();
const float bottom = GFX_Context_GetDisplayHeight();
const float z_near = -1e6;
const float z_far = 1e6;
GLfloat projection[4][4] = {
{ 2.0f / (right - left), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f / (top - bottom), 0.0f, 0.0f },
{ 0.0f, 0.0f, -2.0f / (z_far - z_near), 0.0f },
{ -(right + left) / (right - left), -(top + bottom) / (top - bottom),
-(z_far + z_near) / (z_far - z_near), 1.0f }
};
GFX_GL_Program_UniformMatrix4fv(
&renderer->program, renderer->loc_mat_projection, 1, GL_FALSE,
&projection[0][0]);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
GFX_GL_CheckError();
}
void GFX_3D_Renderer_RenderEnd(GFX_3D_RENDERER *renderer)
{
assert(renderer);
GFX_3D_VertexStream_RenderPending(&renderer->vertex_stream);
GFX_GL_CheckError();
}
void GFX_3D_Renderer_ClearDepth(GFX_3D_RENDERER *renderer)
{
GFX_3D_VertexStream_RenderPending(&renderer->vertex_stream);
glClear(GL_DEPTH_BUFFER_BIT);
GFX_GL_CheckError();
}
int GFX_3D_Renderer_RegisterEnvironmentMap(GFX_3D_RENDERER *const renderer)
{
assert(renderer != NULL);
assert(renderer->env_map_texture == NULL);
GFX_GL_TEXTURE *texture = GFX_GL_Texture_Create(GL_TEXTURE_2D);
renderer->env_map_texture = texture;
GFX_3D_Renderer_RestoreTexture(renderer);
GFX_GL_CheckError();
return GFX_ENV_MAP_TEXTURE;
}
bool GFX_3D_Renderer_UnregisterEnvironmentMap(
GFX_3D_RENDERER *const renderer, const int texture_num)
{
assert(renderer != NULL);
GFX_GL_TEXTURE *const texture = renderer->env_map_texture;
if (texture == NULL) {
LOG_ERROR("No environment map registered");
return false;
}
if (texture_num != GFX_ENV_MAP_TEXTURE) {
LOG_ERROR("Invalid environment map texture ID");
return false;
}
// unbind texture if currently bound
if (renderer->selected_texture_num == texture_num) {
M_SelectTextureImpl(renderer, GFX_NO_TEXTURE);
renderer->selected_texture_num = GFX_NO_TEXTURE;
}
GFX_GL_Texture_Free(texture);
renderer->env_map_texture = NULL;
return true;
}
void GFX_3D_Renderer_FillEnvironmentMap(GFX_3D_RENDERER *const renderer)
{
assert(renderer != NULL);
GFX_GL_TEXTURE *const env_map = renderer->env_map_texture;
if (env_map != NULL) {
GFX_3D_VertexStream_RenderPending(&renderer->vertex_stream);
GFX_GL_Texture_LoadFromBackBuffer(env_map);
GFX_3D_Renderer_RestoreTexture(renderer);
}
}
int GFX_3D_Renderer_RegisterTexturePage(
GFX_3D_RENDERER *renderer, const void *data, int width, int height)
{
assert(renderer);
assert(data);
GFX_GL_TEXTURE *texture = GFX_GL_Texture_Create(GL_TEXTURE_2D);
GFX_GL_Texture_Load(texture, data, width, height, GL_RGBA, GL_RGBA);
int texture_num = GFX_NO_TEXTURE;
for (int i = 0; i < GFX_MAX_TEXTURES; i++) {
if (!renderer->textures[i]) {
renderer->textures[i] = texture;
texture_num = i;
break;
}
}
GFX_3D_Renderer_RestoreTexture(renderer);
GFX_GL_CheckError();
return texture_num;
}
bool GFX_3D_Renderer_UnregisterTexturePage(
GFX_3D_RENDERER *renderer, int texture_num)
{
assert(renderer);
assert(texture_num >= 0);
assert(texture_num < GFX_MAX_TEXTURES);
GFX_GL_TEXTURE *texture = renderer->textures[texture_num];
if (!texture) {
LOG_ERROR("Invalid texture handle");
return false;
}
// unbind texture if currently bound
if (texture_num == renderer->selected_texture_num) {
M_SelectTextureImpl(renderer, GFX_NO_TEXTURE);
renderer->selected_texture_num = GFX_NO_TEXTURE;
}
GFX_GL_Texture_Free(texture);
renderer->textures[texture_num] = NULL;
return true;
}
void GFX_3D_Renderer_RenderPrimStrip(
GFX_3D_RENDERER *renderer, GFX_3D_VERTEX *vertices, int count)
{
assert(renderer);
assert(vertices);
GFX_3D_VertexStream_PushPrimStrip(
&renderer->vertex_stream, vertices, count);
}
void GFX_3D_Renderer_RenderPrimFan(
GFX_3D_RENDERER *renderer, GFX_3D_VERTEX *vertices, int count)
{
assert(renderer);
assert(vertices);
GFX_3D_VertexStream_PushPrimFan(&renderer->vertex_stream, vertices, count);
}
void GFX_3D_Renderer_RenderPrimList(
GFX_3D_RENDERER *renderer, GFX_3D_VERTEX *vertices, int count)
{
assert(renderer);
assert(vertices);
GFX_3D_VertexStream_PushPrimList(&renderer->vertex_stream, vertices, count);
}
void GFX_3D_Renderer_SelectTexture(GFX_3D_RENDERER *renderer, int texture_num)
{
assert(renderer);
GFX_3D_VertexStream_RenderPending(&renderer->vertex_stream);
renderer->selected_texture_num = texture_num;
M_SelectTextureImpl(renderer, texture_num);
}
void GFX_3D_Renderer_RestoreTexture(GFX_3D_RENDERER *renderer)
{
assert(renderer);
M_SelectTextureImpl(renderer, renderer->selected_texture_num);
}
void GFX_3D_Renderer_SetPrimType(
GFX_3D_RENDERER *renderer, GFX_3D_PRIM_TYPE value)
{
assert(renderer);
GFX_3D_VertexStream_RenderPending(&renderer->vertex_stream);
GFX_3D_VertexStream_SetPrimType(&renderer->vertex_stream, value);
}
void GFX_3D_Renderer_SetTextureFilter(
GFX_3D_RENDERER *renderer, GFX_TEXTURE_FILTER filter)
{
assert(renderer);
GFX_3D_VertexStream_RenderPending(&renderer->vertex_stream);
GFX_GL_Sampler_Parameteri(
&renderer->sampler, GL_TEXTURE_MAG_FILTER,
filter == GFX_TF_BILINEAR ? GL_LINEAR : GL_NEAREST);
GFX_GL_Sampler_Parameteri(
&renderer->sampler, GL_TEXTURE_MIN_FILTER,
filter == GFX_TF_BILINEAR ? GL_LINEAR : GL_NEAREST);
GFX_GL_Program_Uniform1i(
&renderer->program, renderer->loc_smoothing_enabled,
filter == GFX_TF_BILINEAR);
}
void GFX_3D_Renderer_SetDepthTestEnabled(
GFX_3D_RENDERER *renderer, bool is_enabled)
{
assert(renderer);
GFX_3D_VertexStream_RenderPending(&renderer->vertex_stream);
if (is_enabled) {
glEnable(GL_DEPTH_TEST);
} else {
glDisable(GL_DEPTH_TEST);
}
}
void GFX_3D_Renderer_SetBlendingMode(
GFX_3D_RENDERER *const renderer, const GFX_BLEND_MODE blend_mode)
{
assert(renderer != NULL);
GFX_3D_VertexStream_RenderPending(&renderer->vertex_stream);
switch (blend_mode) {
case GFX_BLEND_MODE_OFF:
glBlendFunc(GL_ONE, GL_ZERO);
break;
case GFX_BLEND_MODE_NORMAL:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
break;
case GFX_BLEND_MODE_MULTIPLY:
glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
break;
}
}
void GFX_3D_Renderer_SetTexturingEnabled(
GFX_3D_RENDERER *renderer, bool is_enabled)
{
assert(renderer);
GFX_3D_VertexStream_RenderPending(&renderer->vertex_stream);
GFX_GL_Program_Uniform1i(
&renderer->program, renderer->loc_texturing_enabled, is_enabled);
}

View file

@ -0,0 +1,160 @@
#include "gfx/3d/vertex_stream.h"
#include "gfx/gl/gl_core_3_3.h"
#include "gfx/gl/utils.h"
#include "log.h"
#include "memory.h"
static const GLenum GL_PRIM_MODES[] = {
GL_LINES, // GFX_3D_PRIM_LINE
GL_TRIANGLES, // GFX_3D_PRIM_TRI
};
static void M_PushVertex(
GFX_3D_VERTEX_STREAM *vertex_stream, GFX_3D_VERTEX *vertex);
static void M_PushVertex(
GFX_3D_VERTEX_STREAM *vertex_stream, GFX_3D_VERTEX *vertex)
{
if (vertex_stream->pending_vertices.count + 1
>= vertex_stream->pending_vertices.capacity) {
vertex_stream->pending_vertices.capacity += 1000;
vertex_stream->pending_vertices.data = Memory_Realloc(
vertex_stream->pending_vertices.data,
vertex_stream->pending_vertices.capacity * sizeof(GFX_3D_VERTEX));
}
vertex_stream->pending_vertices
.data[vertex_stream->pending_vertices.count++] = *vertex;
}
void GFX_3D_VertexStream_Init(GFX_3D_VERTEX_STREAM *vertex_stream)
{
vertex_stream->prim_type = GFX_3D_PRIM_TRI;
vertex_stream->buffer_size = 0;
vertex_stream->pending_vertices.data = NULL;
vertex_stream->pending_vertices.count = 0;
vertex_stream->pending_vertices.capacity = 0;
GFX_GL_Buffer_Init(&vertex_stream->buffer, GL_ARRAY_BUFFER);
GFX_GL_Buffer_Bind(&vertex_stream->buffer);
GFX_GL_VertexArray_Init(&vertex_stream->vtc_format);
GFX_GL_VertexArray_Bind(&vertex_stream->vtc_format);
GFX_GL_VertexArray_Attribute(
&vertex_stream->vtc_format, 0, 3, GL_FLOAT, GL_FALSE, 40, 0);
GFX_GL_VertexArray_Attribute(
&vertex_stream->vtc_format, 1, 3, GL_FLOAT, GL_FALSE, 40, 12);
GFX_GL_VertexArray_Attribute(
&vertex_stream->vtc_format, 2, 4, GL_FLOAT, GL_FALSE, 40, 24);
GFX_GL_CheckError();
}
void GFX_3D_VertexStream_Close(GFX_3D_VERTEX_STREAM *vertex_stream)
{
GFX_GL_VertexArray_Close(&vertex_stream->vtc_format);
GFX_GL_Buffer_Close(&vertex_stream->buffer);
Memory_FreePointer(&vertex_stream->pending_vertices.data);
}
void GFX_3D_VertexStream_Bind(GFX_3D_VERTEX_STREAM *vertex_stream)
{
GFX_GL_Buffer_Bind(&vertex_stream->buffer);
}
void GFX_3D_VertexStream_SetPrimType(
GFX_3D_VERTEX_STREAM *vertex_stream, GFX_3D_PRIM_TYPE prim_type)
{
vertex_stream->prim_type = prim_type;
}
bool GFX_3D_VertexStream_PushPrimStrip(
GFX_3D_VERTEX_STREAM *vertex_stream, GFX_3D_VERTEX *vertices, int count)
{
if (vertex_stream->prim_type != GFX_3D_PRIM_TRI) {
LOG_ERROR("Unsupported prim type: %d", vertex_stream->prim_type);
return false;
}
if (count <= 2) {
for (int i = 0; i < count; i++) {
M_PushVertex(vertex_stream, &vertices[i]);
}
} else {
// convert strip to raw triangles
for (int i = 2; i < count; i++) {
M_PushVertex(vertex_stream, &vertices[i - 2]);
M_PushVertex(vertex_stream, &vertices[i - 1]);
M_PushVertex(vertex_stream, &vertices[i]);
}
}
return true;
}
bool GFX_3D_VertexStream_PushPrimFan(
GFX_3D_VERTEX_STREAM *vertex_stream, GFX_3D_VERTEX *vertices, int count)
{
if (vertex_stream->prim_type != GFX_3D_PRIM_TRI) {
LOG_ERROR("Unsupported prim type: %d", vertex_stream->prim_type);
return false;
}
if (count <= 2) {
for (int i = 0; i < count; i++) {
M_PushVertex(vertex_stream, &vertices[i]);
}
} else {
// convert fan to raw triangles
for (int i = 2; i < count; i++) {
M_PushVertex(vertex_stream, &vertices[0]);
M_PushVertex(vertex_stream, &vertices[i - 1]);
M_PushVertex(vertex_stream, &vertices[i]);
}
}
return true;
}
bool GFX_3D_VertexStream_PushPrimList(
GFX_3D_VERTEX_STREAM *vertex_stream, GFX_3D_VERTEX *vertices, int count)
{
for (int i = 0; i < count; i++) {
M_PushVertex(vertex_stream, &vertices[i]);
}
return true;
}
void GFX_3D_VertexStream_RenderPending(GFX_3D_VERTEX_STREAM *vertex_stream)
{
if (!vertex_stream->pending_vertices.count) {
return;
}
GFX_GL_VertexArray_Bind(&vertex_stream->vtc_format);
// resize GPU buffer if required
size_t buffer_size =
sizeof(GFX_3D_VERTEX) * vertex_stream->pending_vertices.count;
if (buffer_size > vertex_stream->buffer_size) {
LOG_INFO(
"Vertex buffer resize: %d -> %d", vertex_stream->buffer_size,
buffer_size);
GFX_GL_Buffer_Data(
&vertex_stream->buffer, buffer_size, NULL, GL_STREAM_DRAW);
vertex_stream->buffer_size = buffer_size;
}
GFX_GL_Buffer_SubData(
&vertex_stream->buffer, 0, buffer_size,
vertex_stream->pending_vertices.data);
glDrawArrays(
GL_PRIM_MODES[vertex_stream->prim_type], 0,
vertex_stream->pending_vertices.count);
GFX_GL_CheckError();
vertex_stream->pending_vertices.count = 0;
}

329
src/libtrx/gfx/context.c Normal file
View file

@ -0,0 +1,329 @@
#include "gfx/context.h"
#include "game/shell.h"
#include "gfx/gl/gl_core_3_3.h"
#include "gfx/gl/utils.h"
#include "gfx/renderers/fbo_renderer.h"
#include "gfx/renderers/legacy_renderer.h"
#include "gfx/screenshot.h"
#include "log.h"
#include "memory.h"
#include <SDL2/SDL_video.h>
#include <string.h>
typedef struct {
SDL_GLContext context;
SDL_Window *window_handle;
GFX_CONFIG config;
GFX_RENDER_MODE render_mode;
int32_t display_width;
int32_t display_height;
int32_t window_width;
int32_t window_height;
char *scheduled_screenshot_path;
GFX_RENDERER *renderer;
GFX_2D_RENDERER renderer_2d;
GFX_3D_RENDERER renderer_3d;
} GFX_CONTEXT;
static GFX_CONTEXT m_Context = { 0 };
static bool M_IsExtensionSupported(const char *name);
static void M_CheckExtensionSupport(const char *name);
static bool M_IsExtensionSupported(const char *name)
{
int number_of_extensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &number_of_extensions);
GFX_GL_CheckError();
for (int i = 0; i < number_of_extensions; i++) {
const char *gl_ext = (const char *)glGetStringi(GL_EXTENSIONS, i);
GFX_GL_CheckError();
if (gl_ext && !strcmp(gl_ext, name)) {
return true;
}
}
return false;
}
static void M_CheckExtensionSupport(const char *name)
{
LOG_INFO(
"%s supported: %s", name, M_IsExtensionSupported(name) ? "yes" : "no");
}
void GFX_Context_SwitchToWindowViewport(void)
{
glViewport(0, 0, m_Context.window_width, m_Context.window_height);
GFX_GL_CheckError();
}
void GFX_Context_SwitchToWindowViewportAR(void)
{
// switch to window viewport at the aspect ratio of the display viewport
int vp_width = m_Context.window_width;
int vp_height = m_Context.window_height;
// default to bottom left corner of the window
int vp_x = 0;
int vp_y = 0;
int hw = m_Context.display_height * vp_width;
int wh = m_Context.display_width * vp_height;
// create viewport offset if the window has a different
// aspect ratio than the current display mode
if (hw > wh) {
int max_w = wh / m_Context.display_height;
vp_x = (vp_width - max_w) / 2;
vp_width = max_w;
} else if (hw < wh) {
int max_h = hw / m_Context.display_width;
vp_y = (vp_height - max_h) / 2;
vp_height = max_h;
}
glViewport(vp_x, vp_y, vp_width, vp_height);
GFX_GL_CheckError();
}
void GFX_Context_SwitchToDisplayViewport(void)
{
glViewport(0, 0, m_Context.display_width, m_Context.display_height);
GFX_GL_CheckError();
}
void GFX_Context_Attach(void *window_handle)
{
const char *shading_ver;
if (m_Context.window_handle) {
return;
}
LOG_INFO("Attaching to window %p", window_handle);
m_Context.config.line_width = 1;
m_Context.config.enable_wireframe = false;
m_Context.render_mode = -1;
SDL_GetWindowSize(
window_handle, &m_Context.window_width, &m_Context.window_height);
m_Context.display_width = m_Context.window_width;
m_Context.display_height = m_Context.window_height;
m_Context.window_handle = window_handle;
if (GFX_GL_DEFAULT_BACKEND == GFX_GL_33C) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(
SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
}
m_Context.context = SDL_GL_CreateContext(m_Context.window_handle);
if (!m_Context.context) {
Shell_ExitSystem("Can't create OpenGL context");
}
if (SDL_GL_MakeCurrent(m_Context.window_handle, m_Context.context)) {
Shell_ExitSystem("Can't activate OpenGL context");
}
LOG_INFO("OpenGL vendor string: %s", glGetString(GL_VENDOR));
LOG_INFO("OpenGL renderer string: %s", glGetString(GL_RENDERER));
LOG_INFO("OpenGL version string: %s", glGetString(GL_VERSION));
shading_ver = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
if (shading_ver != NULL) {
LOG_INFO("Shading version string: %s", shading_ver);
} else {
GFX_GL_CheckError();
}
// Check the availability of non-Core Profile extensions for OpenGL 2.1
if (GFX_GL_DEFAULT_BACKEND == GFX_GL_21) {
M_CheckExtensionSupport("GL_ARB_explicit_attrib_location");
M_CheckExtensionSupport("GL_EXT_gpu_shader4");
}
glClearColor(0, 0, 0, 0);
glClearDepth(1);
GFX_GL_CheckError();
// VSync defaults to on unless user disabled it in runtime json
SDL_GL_SetSwapInterval(1);
GFX_2D_Renderer_Init(&m_Context.renderer_2d);
GFX_3D_Renderer_Init(&m_Context.renderer_3d, &m_Context.config);
}
void GFX_Context_Detach(void)
{
if (!m_Context.window_handle) {
return;
}
if (m_Context.renderer != NULL && m_Context.renderer->shutdown != NULL) {
m_Context.renderer->shutdown(m_Context.renderer);
}
GFX_2D_Renderer_Close(&m_Context.renderer_2d);
GFX_3D_Renderer_Close(&m_Context.renderer_3d);
SDL_GL_MakeCurrent(NULL, NULL);
if (m_Context.context != NULL) {
SDL_GL_DeleteContext(m_Context.context);
m_Context.context = NULL;
}
m_Context.window_handle = NULL;
}
void GFX_Context_SetDisplayFilter(const GFX_TEXTURE_FILTER filter)
{
m_Context.config.display_filter = filter;
}
void GFX_Context_SetWireframeMode(const bool enable)
{
m_Context.config.enable_wireframe = enable;
}
void GFX_Context_SetLineWidth(const int32_t line_width)
{
m_Context.config.line_width = line_width;
}
void GFX_Context_SetAnisotropyFilter(float value)
{
GFX_GL_Sampler_Bind(&m_Context.renderer_3d.sampler, 0);
GFX_GL_Sampler_Parameterf(
&m_Context.renderer_3d.sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, value);
}
void GFX_Context_SetVSync(bool vsync)
{
SDL_GL_SetSwapInterval(vsync);
}
void GFX_Context_SetWindowSize(int32_t width, int32_t height)
{
LOG_INFO("Window size: %dx%d", width, height);
m_Context.window_width = width;
m_Context.window_height = height;
}
void GFX_Context_SetDisplaySize(int32_t width, int32_t height)
{
if (width == m_Context.display_width
&& height == m_Context.display_height) {
return;
}
LOG_INFO("Display size: %dx%d", width, height);
if (width <= 0 || height <= 0) {
LOG_INFO("invalid size, ignoring");
return;
}
m_Context.display_width = width;
m_Context.display_height = height;
if (m_Context.renderer != NULL && m_Context.renderer->reset != NULL) {
m_Context.renderer->reset(m_Context.renderer);
}
}
void GFX_Context_SetRenderingMode(GFX_RENDER_MODE target_mode)
{
GFX_RENDER_MODE current_mode = m_Context.render_mode;
if (current_mode == target_mode) {
return;
}
LOG_INFO("Render mode: %d", target_mode);
if (m_Context.renderer != NULL && m_Context.renderer->shutdown != NULL) {
m_Context.renderer->shutdown(m_Context.renderer);
}
switch (target_mode) {
case GFX_RM_FRAMEBUFFER:
m_Context.renderer = &g_GFX_Renderer_FBO;
break;
case GFX_RM_LEGACY:
m_Context.renderer = &g_GFX_Renderer_Legacy;
break;
}
if (m_Context.renderer != NULL && m_Context.renderer->init != NULL) {
m_Context.renderer->init(m_Context.renderer, &m_Context.config);
}
m_Context.render_mode = target_mode;
}
void *GFX_Context_GetWindowHandle(void)
{
return m_Context.window_handle;
}
int32_t GFX_Context_GetDisplayWidth(void)
{
return m_Context.display_width;
}
int32_t GFX_Context_GetDisplayHeight(void)
{
return m_Context.display_height;
}
void GFX_Context_Clear(void)
{
if (m_Context.config.enable_wireframe) {
glClearColor(1.0, 1.0, 1.0, 0.0);
} else {
glClearColor(0.0, 0.0, 0.0, 0.0);
}
glClear(GL_COLOR_BUFFER_BIT);
}
void GFX_Context_SwapBuffers(void)
{
glFinish();
GFX_GL_CheckError();
if (m_Context.renderer != NULL
&& m_Context.renderer->swap_buffers != NULL) {
m_Context.renderer->swap_buffers(m_Context.renderer);
}
}
void GFX_Context_ScheduleScreenshot(const char *path)
{
Memory_FreePointer(&m_Context.scheduled_screenshot_path);
m_Context.scheduled_screenshot_path = Memory_DupStr(path);
}
const char *GFX_Context_GetScheduledScreenshotPath(void)
{
return m_Context.scheduled_screenshot_path;
}
void GFX_Context_ClearScheduledScreenshotPath(void)
{
Memory_FreePointer(&m_Context.scheduled_screenshot_path);
}
GFX_2D_RENDERER *GFX_Context_GetRenderer2D(void)
{
return &m_Context.renderer_2d;
}
GFX_3D_RENDERER *GFX_Context_GetRenderer3D(void)
{
return &m_Context.renderer_3d;
}

View file

@ -0,0 +1,67 @@
#include "gfx/gl/buffer.h"
#include "gfx/gl/utils.h"
#include <assert.h>
void GFX_GL_Buffer_Init(GFX_GL_BUFFER *buf, GLenum target)
{
assert(buf);
buf->target = target;
glGenBuffers(1, &buf->id);
GFX_GL_CheckError();
}
void GFX_GL_Buffer_Close(GFX_GL_BUFFER *buf)
{
assert(buf);
glDeleteBuffers(1, &buf->id);
GFX_GL_CheckError();
}
void GFX_GL_Buffer_Bind(GFX_GL_BUFFER *buf)
{
assert(buf);
glBindBuffer(buf->target, buf->id);
GFX_GL_CheckError();
}
void GFX_GL_Buffer_Data(
GFX_GL_BUFFER *buf, GLsizei size, const void *data, GLenum usage)
{
assert(buf);
glBufferData(buf->target, size, data, usage);
GFX_GL_CheckError();
}
void GFX_GL_Buffer_SubData(
GFX_GL_BUFFER *buf, GLsizei offset, GLsizei size, const void *data)
{
assert(buf);
glBufferSubData(buf->target, offset, size, data);
GFX_GL_CheckError();
}
void *GFX_GL_Buffer_Map(GFX_GL_BUFFER *buf, GLenum access)
{
assert(buf);
void *ret = glMapBuffer(buf->target, access);
GFX_GL_CheckError();
return ret;
}
void GFX_GL_Buffer_Unmap(GFX_GL_BUFFER *buf)
{
assert(buf);
glUnmapBuffer(buf->target);
GFX_GL_CheckError();
}
GLint GFX_GL_Buffer_Parameter(GFX_GL_BUFFER *buf, GLenum pname)
{
assert(buf);
GLint params = 0;
glGetBufferParameteriv(buf->target, pname, &params);
GFX_GL_CheckError();
return params;
}

File diff suppressed because it is too large Load diff

204
src/libtrx/gfx/gl/program.c Normal file
View file

@ -0,0 +1,204 @@
#include "gfx/gl/program.h"
#include "filesystem.h"
#include "game/shell.h"
#include "gfx/gl/utils.h"
#include "log.h"
#include "memory.h"
#include <assert.h>
#include <stddef.h>
#include <string.h>
bool GFX_GL_Program_Init(GFX_GL_PROGRAM *program)
{
assert(program);
program->id = glCreateProgram();
GFX_GL_CheckError();
if (!program->id) {
LOG_ERROR("Can't create shader program");
return false;
}
return true;
}
void GFX_GL_Program_Close(GFX_GL_PROGRAM *program)
{
if (program->id) {
glDeleteProgram(program->id);
GFX_GL_CheckError();
program->id = 0;
}
}
void GFX_GL_Program_Bind(GFX_GL_PROGRAM *program)
{
glUseProgram(program->id);
GFX_GL_CheckError();
}
char *GFX_GL_Program_PreprocessShader(
const char *content, GLenum type, GFX_GL_BACKEND backend)
{
const char *version_ogl21 =
"#version 120\n"
"#extension GL_ARB_explicit_attrib_location: enable\n"
"#extension GL_EXT_gpu_shader4: enable\n";
const char *version_ogl33c = "#version 330 core\n";
const char *define_vertex = "#define VERTEX\n";
const char *define_ogl33c = "#define OGL33C\n";
size_t bufsize = strlen(content) + 1;
if (backend == GFX_GL_33C) {
bufsize += strlen(version_ogl33c);
bufsize += strlen(define_ogl33c);
} else {
bufsize += strlen(version_ogl21);
}
if (type == GL_VERTEX_SHADER) {
bufsize += strlen(define_vertex);
}
char *processed_content = Memory_Alloc(bufsize);
if (!processed_content) {
return NULL;
}
processed_content[0] = '\0';
if (backend == GFX_GL_33C) {
strcpy(processed_content, version_ogl33c);
strcat(processed_content, define_ogl33c);
} else {
strcpy(processed_content, version_ogl21);
}
if (type == GL_VERTEX_SHADER) {
strcat(processed_content, define_vertex);
}
strcat(processed_content, content);
return processed_content;
}
void GFX_GL_Program_AttachShader(
GFX_GL_PROGRAM *program, GLenum type, const char *path)
{
GLuint shader_id = glCreateShader(type);
GFX_GL_CheckError();
if (!shader_id) {
Shell_ExitSystem("Failed to create shader");
}
char *content = NULL;
if (!File_Load(path, &content, NULL)) {
Shell_ExitSystemFmt("Unable to find shader file: %s", path);
}
char *processed_content =
GFX_GL_Program_PreprocessShader(content, type, GFX_GL_DEFAULT_BACKEND);
Memory_FreePointer(&content);
if (!processed_content) {
Shell_ExitSystemFmt("Failed to pre-process shader source: %s", path);
}
glShaderSource(shader_id, 1, (const char *const *)&processed_content, NULL);
GFX_GL_CheckError();
glCompileShader(shader_id);
GFX_GL_CheckError();
int compile_status;
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &compile_status);
GFX_GL_CheckError();
if (compile_status != GL_TRUE) {
GLsizei info_log_size = 4096;
char info_log[info_log_size];
glGetShaderInfoLog(shader_id, info_log_size, &info_log_size, info_log);
GFX_GL_CheckError();
if (info_log[0]) {
Shell_ExitSystemFmt("Shader compilation failed:\n%s", info_log);
} else {
Shell_ExitSystemFmt("Shader compilation failed.");
}
}
Memory_FreePointer(&processed_content);
glAttachShader(program->id, shader_id);
GFX_GL_CheckError();
glDeleteShader(shader_id);
GFX_GL_CheckError();
}
void GFX_GL_Program_Link(GFX_GL_PROGRAM *program)
{
glLinkProgram(program->id);
GFX_GL_CheckError();
GLint linkStatus;
glGetProgramiv(program->id, GL_LINK_STATUS, &linkStatus);
GFX_GL_CheckError();
if (!linkStatus) {
GLsizei info_log_size = 4096;
char info_log[info_log_size];
glGetProgramInfoLog(
program->id, info_log_size, &info_log_size, info_log);
GFX_GL_CheckError();
if (info_log[0]) {
Shell_ExitSystemFmt("Shader linking failed:\n%s", info_log);
} else {
Shell_ExitSystemFmt("Shader linking failed.");
}
}
}
void GFX_GL_Program_FragmentData(GFX_GL_PROGRAM *program, const char *name)
{
glBindFragDataLocation(program->id, 0, name);
GFX_GL_CheckError();
}
GLint GFX_GL_Program_UniformLocation(GFX_GL_PROGRAM *program, const char *name)
{
GLint location = glGetUniformLocation(program->id, name);
GFX_GL_CheckError();
if (location == -1) {
LOG_INFO("Shader uniform not found: %s", name);
}
return location;
}
void GFX_GL_Program_Uniform3f(
GFX_GL_PROGRAM *program, GLint loc, GLfloat v0, GLfloat v1, GLfloat v2)
{
glUniform3f(loc, v0, v1, v2);
GFX_GL_CheckError();
}
void GFX_GL_Program_Uniform4f(
GFX_GL_PROGRAM *program, GLint loc, GLfloat v0, GLfloat v1, GLfloat v2,
GLfloat v3)
{
glUniform4f(loc, v0, v1, v2, v3);
GFX_GL_CheckError();
}
void GFX_GL_Program_Uniform1i(GFX_GL_PROGRAM *program, GLint loc, GLint v0)
{
glUniform1i(loc, v0);
GFX_GL_CheckError();
}
void GFX_GL_Program_UniformMatrix4fv(
GFX_GL_PROGRAM *program, GLint loc, GLsizei count, GLboolean transpose,
const GLfloat *value)
{
glUniformMatrix4fv(loc, count, transpose, value);
GFX_GL_CheckError();
}

View file

@ -0,0 +1,35 @@
#include "gfx/gl/sampler.h"
#include "gfx/gl/utils.h"
void GFX_GL_Sampler_Init(GFX_GL_SAMPLER *sampler)
{
glGenSamplers(1, &sampler->id);
GFX_GL_CheckError();
}
void GFX_GL_Sampler_Close(GFX_GL_SAMPLER *sampler)
{
glDeleteSamplers(1, &sampler->id);
GFX_GL_CheckError();
}
void GFX_GL_Sampler_Bind(GFX_GL_SAMPLER *sampler, GLuint unit)
{
glBindSampler(unit, sampler->id);
GFX_GL_CheckError();
}
void GFX_GL_Sampler_Parameteri(
GFX_GL_SAMPLER *sampler, GLenum pname, GLint param)
{
glSamplerParameteri(sampler->id, pname, param);
GFX_GL_CheckError();
}
void GFX_GL_Sampler_Parameterf(
GFX_GL_SAMPLER *sampler, GLenum pname, GLfloat param)
{
glSamplerParameterf(sampler->id, pname, param);
GFX_GL_CheckError();
}

View file

@ -0,0 +1,88 @@
#include "gfx/gl/texture.h"
#include "gfx/gl/utils.h"
#include "memory.h"
#include "utils.h"
#include <assert.h>
GFX_GL_TEXTURE *GFX_GL_Texture_Create(GLenum target)
{
GFX_GL_TEXTURE *texture = Memory_Alloc(sizeof(GFX_GL_TEXTURE));
GFX_GL_Texture_Init(texture, target);
return texture;
}
void GFX_GL_Texture_Free(GFX_GL_TEXTURE *texture)
{
if (texture != NULL) {
GFX_GL_Texture_Close(texture);
Memory_FreePointer(&texture);
}
}
void GFX_GL_Texture_Init(GFX_GL_TEXTURE *texture, GLenum target)
{
assert(texture != NULL);
texture->target = target;
glGenTextures(1, &texture->id);
GFX_GL_CheckError();
}
void GFX_GL_Texture_Close(GFX_GL_TEXTURE *texture)
{
assert(texture != NULL);
glDeleteTextures(1, &texture->id);
GFX_GL_CheckError();
}
void GFX_GL_Texture_Bind(GFX_GL_TEXTURE *texture)
{
assert(texture != NULL);
glBindTexture(texture->target, texture->id);
GFX_GL_CheckError();
}
void GFX_GL_Texture_Load(
GFX_GL_TEXTURE *texture, const void *data, int width, int height,
GLint internal_format, GLint format)
{
assert(texture != NULL);
GFX_GL_Texture_Bind(texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(
GL_TEXTURE_2D, 0, internal_format, width, height, 0, format,
GL_UNSIGNED_BYTE, data);
GFX_GL_CheckError();
glGenerateMipmap(GL_TEXTURE_2D);
GFX_GL_CheckError();
}
void GFX_GL_Texture_LoadFromBackBuffer(GFX_GL_TEXTURE *const texture)
{
assert(texture != NULL);
GFX_GL_Texture_Bind(texture);
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
GFX_GL_CheckError();
const GLint vp_x = viewport[0];
const GLint vp_y = viewport[1];
const GLint vp_w = viewport[2];
const GLint vp_h = viewport[3];
const int32_t side = MIN(vp_w, vp_h);
const int32_t x = vp_x + (vp_w - side) / 2;
const int32_t y = vp_y + (vp_h - side) / 2;
const int32_t w = side;
const int32_t h = side;
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, x, y, w, h, 0);
GFX_GL_CheckError();
}

27
src/libtrx/gfx/gl/utils.c Normal file
View file

@ -0,0 +1,27 @@
#include "gfx/gl/utils.h"
#include "gfx/gl/gl_core_3_3.h"
const char *GFX_GL_GetErrorString(GLenum err)
{
switch (err) {
case GL_NO_ERROR:
return "GL_NO_ERROR";
case GL_INVALID_ENUM:
return "GL_INVALID_ENUM";
case GL_INVALID_VALUE:
return "GL_INVALID_VALUE";
case GL_INVALID_OPERATION:
return "GL_INVALID_OPERATION";
case GL_INVALID_FRAMEBUFFER_OPERATION:
return "GL_INVALID_FRAMEBUFFER_OPERATION";
case GL_OUT_OF_MEMORY:
return "GL_OUT_OF_MEMORY";
case GL_STACK_UNDERFLOW:
return "GL_STACK_UNDERFLOW";
case GL_STACK_OVERFLOW:
return "GL_STACK_OVERFLOW";
default:
return "UNKNOWN";
}
}

View file

@ -0,0 +1,40 @@
#include "gfx/gl/vertex_array.h"
#include "gfx/gl/utils.h"
#include <assert.h>
#include <stdint.h>
void GFX_GL_VertexArray_Init(GFX_GL_VERTEX_ARRAY *array)
{
assert(array);
glGenVertexArrays(1, &array->id);
GFX_GL_CheckError();
}
void GFX_GL_VertexArray_Close(GFX_GL_VERTEX_ARRAY *array)
{
assert(array);
glDeleteVertexArrays(1, &array->id);
GFX_GL_CheckError();
}
void GFX_GL_VertexArray_Bind(GFX_GL_VERTEX_ARRAY *array)
{
assert(array);
glBindVertexArray(array->id);
GFX_GL_CheckError();
}
void GFX_GL_VertexArray_Attribute(
GFX_GL_VERTEX_ARRAY *array, GLuint index, GLint size, GLenum type,
GLboolean normalized, GLsizei stride, GLsizei offset)
{
assert(array);
glEnableVertexAttribArray(index);
GFX_GL_CheckError();
glVertexAttribPointer(
index, size, type, normalized, stride, (void *)(intptr_t)offset);
GFX_GL_CheckError();
}

View file

@ -0,0 +1,245 @@
#include "gfx/renderers/fbo_renderer.h"
#include "gfx/common.h"
#include "gfx/context.h"
#include "gfx/gl/buffer.h"
#include "gfx/gl/gl_core_3_3.h"
#include "gfx/gl/program.h"
#include "gfx/gl/sampler.h"
#include "gfx/gl/texture.h"
#include "gfx/gl/utils.h"
#include "gfx/gl/vertex_array.h"
#include "gfx/screenshot.h"
#include "log.h"
#include "memory.h"
#include <SDL2/SDL_video.h>
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct {
const GFX_CONFIG *config;
GLuint fbo;
GLuint rbo;
GFX_GL_VERTEX_ARRAY vertex_array;
GFX_GL_BUFFER buffer;
GFX_GL_TEXTURE texture;
GFX_GL_SAMPLER sampler;
GFX_GL_PROGRAM program;
} M_CONTEXT;
static void M_SwapBuffers(GFX_RENDERER *renderer);
static void M_Init(GFX_RENDERER *renderer, const GFX_CONFIG *config);
static void M_Shutdown(GFX_RENDERER *renderer);
static void M_Reset(GFX_RENDERER *renderer);
static void M_Render(GFX_RENDERER *renderer);
static void M_Bind(const GFX_RENDERER *renderer);
static void M_Unbind(const GFX_RENDERER *renderer);
static void M_SwapBuffers(GFX_RENDERER *renderer)
{
if (GFX_Context_GetScheduledScreenshotPath()) {
GFX_Screenshot_CaptureToFile(GFX_Context_GetScheduledScreenshotPath());
GFX_Context_ClearScheduledScreenshotPath();
}
GFX_Context_SwitchToWindowViewportAR();
M_Render(renderer);
SDL_GL_SwapWindow(GFX_Context_GetWindowHandle());
GFX_Context_SwitchToWindowViewport();
M_Unbind(renderer);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GFX_GL_CheckError();
M_Bind(renderer);
GFX_Context_SwitchToDisplayViewport();
}
static void M_Init(GFX_RENDERER *const renderer, const GFX_CONFIG *const config)
{
LOG_INFO("");
assert(renderer != NULL);
renderer->priv = (M_CONTEXT *)Memory_Alloc(sizeof(M_CONTEXT));
M_CONTEXT *priv = renderer->priv;
assert(priv != NULL);
priv->config = config;
int32_t fbo_width = GFX_Context_GetDisplayWidth();
int32_t fbo_height = GFX_Context_GetDisplayHeight();
GFX_GL_Buffer_Init(&priv->buffer, GL_ARRAY_BUFFER);
GFX_GL_Buffer_Bind(&priv->buffer);
GLfloat verts[] = { 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 1.0, 0.0, 1.0, 1.0 };
GFX_GL_Buffer_Data(&priv->buffer, sizeof(verts), verts, GL_STATIC_DRAW);
GFX_GL_VertexArray_Init(&priv->vertex_array);
GFX_GL_VertexArray_Bind(&priv->vertex_array);
GFX_GL_VertexArray_Attribute(
&priv->vertex_array, 0, 2, GL_FLOAT, GL_FALSE, 0, 0);
GFX_GL_Texture_Init(&priv->texture, GL_TEXTURE_2D);
GFX_GL_Sampler_Init(&priv->sampler);
GFX_GL_Sampler_Bind(&priv->sampler, 0);
GFX_GL_Sampler_Parameteri(&priv->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GFX_GL_Sampler_Parameteri(&priv->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GFX_GL_Sampler_Parameteri(
&priv->sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
GFX_GL_Sampler_Parameteri(
&priv->sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
GFX_GL_Program_Init(&priv->program);
GFX_GL_Program_AttachShader(
&priv->program, GL_VERTEX_SHADER, "shaders/fbo.glsl");
GFX_GL_Program_AttachShader(
&priv->program, GL_FRAGMENT_SHADER, "shaders/fbo.glsl");
GFX_GL_Program_Link(&priv->program);
GFX_GL_Program_FragmentData(&priv->program, "fragColor");
glGenFramebuffers(1, &priv->fbo);
GFX_GL_CheckError();
glBindFramebuffer(GL_FRAMEBUFFER, priv->fbo);
GFX_GL_CheckError();
GFX_GL_Texture_Load(
&priv->texture, NULL, fbo_width, fbo_height, GL_RGB, GL_RGB);
glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, priv->texture.id,
0);
GFX_GL_CheckError();
glGenRenderbuffers(1, &priv->rbo);
GFX_GL_CheckError();
glBindRenderbuffer(GL_RENDERBUFFER, priv->rbo);
GFX_GL_CheckError();
glRenderbufferStorage(
GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, fbo_width, fbo_height);
GFX_GL_CheckError();
glBindRenderbuffer(GL_RENDERBUFFER, 0);
GFX_GL_CheckError();
glFramebufferRenderbuffer(
GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
priv->rbo);
GFX_GL_CheckError();
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
LOG_ERROR("framebuffer is not complete!");
}
}
static void M_Shutdown(GFX_RENDERER *renderer)
{
LOG_INFO("");
assert(renderer != NULL);
M_CONTEXT *priv = renderer->priv;
assert(priv != NULL);
if (!priv->fbo) {
return;
}
glDeleteFramebuffers(1, &priv->fbo);
priv->fbo = 0;
GFX_GL_VertexArray_Close(&priv->vertex_array);
GFX_GL_Buffer_Close(&priv->buffer);
GFX_GL_Texture_Close(&priv->texture);
GFX_GL_Sampler_Close(&priv->sampler);
GFX_GL_Program_Close(&priv->program);
Memory_FreePointer(&renderer->priv);
}
static void M_Reset(GFX_RENDERER *renderer)
{
M_CONTEXT *const priv = renderer->priv;
const GFX_CONFIG *const config = priv->config;
renderer->shutdown(renderer);
renderer->init(renderer, config);
}
static void M_Render(GFX_RENDERER *renderer)
{
assert(renderer != NULL);
M_CONTEXT *priv = renderer->priv;
assert(priv != NULL);
const GLuint filter = priv->config->display_filter == GFX_TF_BILINEAR
? GL_LINEAR
: GL_NEAREST;
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
GFX_GL_CheckError();
GFX_GL_Program_Bind(&priv->program);
GFX_GL_Buffer_Bind(&priv->buffer);
GFX_GL_VertexArray_Bind(&priv->vertex_array);
GFX_GL_Texture_Bind(&priv->texture);
GFX_GL_Sampler_Bind(&priv->sampler, 0);
GFX_GL_Sampler_Parameteri(&priv->sampler, GL_TEXTURE_MAG_FILTER, filter);
GFX_GL_Sampler_Parameteri(&priv->sampler, GL_TEXTURE_MIN_FILTER, filter);
GLboolean blend = glIsEnabled(GL_BLEND);
if (blend) {
glDisable(GL_BLEND);
}
GLboolean depth_test = glIsEnabled(GL_DEPTH_TEST);
if (depth_test) {
glDisable(GL_DEPTH_TEST);
}
glDrawArrays(GL_TRIANGLES, 0, 6);
GFX_GL_CheckError();
if (blend) {
glEnable(GL_BLEND);
}
if (depth_test) {
glEnable(GL_DEPTH_TEST);
}
GFX_GL_CheckError();
glBindFramebuffer(GL_FRAMEBUFFER, priv->fbo);
GFX_GL_CheckError();
}
static void M_Bind(const GFX_RENDERER *renderer)
{
assert(renderer != NULL);
M_CONTEXT *priv = renderer->priv;
assert(priv != NULL);
glBindFramebuffer(GL_FRAMEBUFFER, priv->fbo);
}
static void M_Unbind(const GFX_RENDERER *renderer)
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
GFX_RENDERER g_GFX_Renderer_FBO = {
.swap_buffers = &M_SwapBuffers,
.init = &M_Init,
.shutdown = &M_Shutdown,
.reset = &M_Reset,
};

View file

@ -0,0 +1,35 @@
#include "gfx/renderers/legacy_renderer.h"
#include "gfx/context.h"
#include "gfx/gl/utils.h"
#include "gfx/screenshot.h"
#include <SDL2/SDL_video.h>
#include <assert.h>
static void M_SwapBuffers(GFX_RENDERER *renderer);
static void M_SwapBuffers(GFX_RENDERER *renderer)
{
assert(renderer != NULL);
GFX_Context_SwitchToWindowViewportAR();
if (GFX_Context_GetScheduledScreenshotPath()) {
GFX_Screenshot_CaptureToFile(GFX_Context_GetScheduledScreenshotPath());
GFX_Context_ClearScheduledScreenshotPath();
}
SDL_GL_SwapWindow(GFX_Context_GetWindowHandle());
glDrawBuffer(GL_BACK);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GFX_GL_CheckError();
}
GFX_RENDERER g_GFX_Renderer_Legacy = {
.priv = NULL,
.swap_buffers = &M_SwapBuffers,
.init = NULL,
.reset = NULL,
.shutdown = NULL,
};

View file

@ -0,0 +1,76 @@
#include "gfx/screenshot.h"
#include "engine/image.h"
#include "gfx/gl/utils.h"
#include "memory.h"
#include <assert.h>
#include <string.h>
bool GFX_Screenshot_CaptureToFile(const char *path)
{
bool ret = false;
GLint width;
GLint height;
GFX_Screenshot_CaptureToBuffer(
NULL, &width, &height, 3, GL_RGB, GL_UNSIGNED_BYTE, true);
IMAGE *image = Image_Create(width, height);
assert(image);
GFX_Screenshot_CaptureToBuffer(
(uint8_t *)image->data, &width, &height, 3, GL_RGB, GL_UNSIGNED_BYTE,
true);
ret = Image_SaveToFile(image, path);
if (image) {
Image_Free(image);
}
return ret;
}
void GFX_Screenshot_CaptureToBuffer(
uint8_t *out_buffer, GLint *out_width, GLint *out_height, GLint depth,
GLenum format, GLenum type, bool vflip)
{
assert(out_width);
assert(out_height);
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
GFX_GL_CheckError();
GLint x = viewport[0];
GLint y = viewport[1];
*out_width = viewport[2];
*out_height = viewport[3];
if (!out_buffer) {
return;
}
GLint pitch = *out_width * depth;
glPixelStorei(GL_PACK_ALIGNMENT, 1);
GFX_GL_CheckError();
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GFX_GL_CheckError();
glReadBuffer(GL_BACK);
GFX_GL_CheckError();
glReadPixels(x, y, *out_width, *out_height, format, type, out_buffer);
GFX_GL_CheckError();
if (vflip) {
uint8_t *scanline = Memory_Alloc(pitch);
for (int y1 = 0, middle = *out_height / 2; y1 < middle; y1++) {
int y2 = *out_height - 1 - y1;
memcpy(scanline, &out_buffer[y1 * pitch], pitch);
memcpy(&out_buffer[y1 * pitch], &out_buffer[y2 * pitch], pitch);
memcpy(&out_buffer[y2 * pitch], scanline, pitch);
}
Memory_FreePointer(&scanline);
}
}

View file

@ -0,0 +1,24 @@
#pragma once
#include <SDL2/SDL_stdinc.h>
typedef struct {
Uint64 start;
Uint64 last;
} BENCHMARK;
BENCHMARK *Benchmark_Start(void);
#define Benchmark_End(b, ...) \
Benchmark_End_Impl(b, __FILE__, __LINE__, __func__, __VA_ARGS__)
#define Benchmark_Tick(b, ...) \
Benchmark_Tick_Impl(b, __FILE__, __LINE__, __func__, __VA_ARGS__)
void Benchmark_End_Impl(
BENCHMARK *b, const char *file, int32_t line, const char *func,
const char *message);
void Benchmark_Tick_Impl(
BENCHMARK *b, const char *file, int32_t line, const char *func,
const char *message);

View file

@ -0,0 +1,29 @@
#pragma once
#include "json.h"
typedef enum {
BSON_PARSE_ERROR_NONE = 0,
BSON_PARSE_ERROR_INVALID_VALUE,
BSON_PARSE_ERROR_PREMATURE_END_OF_BUFFER,
BSON_PARSE_ERROR_UNEXPECTED_TRAILING_BYTES,
BSON_PARSE_ERROR_UNKNOWN,
} BSON_PARSE_ERROR;
typedef struct {
BSON_PARSE_ERROR error;
size_t error_offset;
} BSON_PARSE_RESULT;
// Parse a BSON file, returning a pointer to the root of the JSON structure.
// Returns NULL if an error occurred (malformed BSON input, or malloc failed).
JSON_VALUE *BSON_Parse(const char *src, size_t src_size);
JSON_VALUE *BSON_ParseEx(
const char *src, size_t src_size, BSON_PARSE_RESULT *result);
const char *BSON_GetErrorDescription(BSON_PARSE_ERROR error);
/* Write out a BSON binary string. Return 0 if an error occurred (malformed
* JSON input, or malloc failed). The out_size parameter is optional. */
void *BSON_Write(const JSON_VALUE *value, size_t *out_size);

View file

@ -0,0 +1,6 @@
#pragma once
#include "config/common.h"
#include "config/file.h"
#include "config/map.h"
#include "config/option.h"

View file

@ -0,0 +1,27 @@
#pragma once
#include "../event_manager.h"
#include "../json.h"
#include "./option.h"
#include <stdbool.h>
#include <stdint.h>
void Config_Init(void);
void Config_Shutdown(void);
bool Config_Read(void);
bool Config_Write(void);
int32_t Config_SubscribeChanges(EVENT_LISTENER listener, void *user_data);
void Config_UnsubscribeChanges(int32_t listener_id);
extern const char *Config_GetPath(void);
extern void Config_Sanitize(void);
extern void Config_ApplyChanges(void);
extern const CONFIG_OPTION *Config_GetOptionMap(void);
extern void Config_LoadFromJSON(JSON_OBJECT *root_obj);
extern void Config_DumpToJSON(JSON_OBJECT *root_obj);

View file

@ -0,0 +1,22 @@
#pragma once
#include "../enum_map.h"
#include "../json.h"
#include "./option.h"
#include <stdbool.h>
#include <stdint.h>
bool ConfigFile_Read(const char *path, void (*load)(JSON_OBJECT *root_obj));
bool ConfigFile_Write(const char *path, void (*dump)(JSON_OBJECT *root_obj));
void ConfigFile_LoadOptions(
JSON_OBJECT *root_obj, const CONFIG_OPTION *options);
void ConfigFile_DumpOptions(
JSON_OBJECT *root_obj, const CONFIG_OPTION *options);
int ConfigFile_ReadEnum(
JSON_OBJECT *obj, const char *name, int default_value,
const char *enum_name);
void ConfigFile_WriteEnum(
JSON_OBJECT *obj, const char *name, int value, const char *enum_name);

View file

@ -0,0 +1,42 @@
#include "../enum_map.h"
#include "../utils.h"
#include "./option.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define CFG_BOOL(parent, target_, default_value_) \
{ .name = QUOTE(target_), \
.type = COT_BOOL, \
.target = &parent.target_, \
.default_value = &(bool) { default_value_ }, \
.param = NULL },
#define CFG_INT32(parent, target_, default_value_) \
{ .name = QUOTE(target_), \
.type = COT_INT32, \
.target = &parent.target_, \
.default_value = &(int32_t) { default_value_ }, \
.param = NULL },
#define CFG_FLOAT(parent, target_, default_value_) \
{ .name = QUOTE(target_), \
.type = COT_FLOAT, \
.target = &parent.target_, \
.default_value = &(float) { default_value_ }, \
.param = NULL },
#define CFG_DOUBLE(parent, target_, default_value_) \
{ .name = QUOTE(target_), \
.type = COT_DOUBLE, \
.target = &parent.target_, \
.default_value = &(double) { default_value_ }, \
.param = NULL },
#define CFG_ENUM(parent, target_, default_value_, enum_map) \
{ .name = QUOTE(target_), \
.type = COT_ENUM, \
.target = &parent.target_, \
.default_value = &(int32_t) { default_value_ }, \
.param = ENUM_MAP_NAME(enum_map) },

View file

@ -0,0 +1,17 @@
#pragma once
typedef enum {
COT_BOOL = 0,
COT_INT32 = 1,
COT_FLOAT = 2,
COT_DOUBLE = 3,
COT_ENUM = 4,
} CONFIG_OPTION_TYPE;
typedef struct {
const char *name;
CONFIG_OPTION_TYPE type;
const void *target;
const void *default_value;
const void *param;
} CONFIG_OPTION;

View file

@ -0,0 +1,51 @@
#pragma once
#include <SDL2/SDL_audio.h>
#include <libavutil/samplefmt.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define AUDIO_MAX_SAMPLES 1000
#define AUDIO_MAX_ACTIVE_SAMPLES 50
#define AUDIO_MAX_ACTIVE_STREAMS 10
#define AUDIO_NO_SOUND (-1)
bool Audio_Init(void);
bool Audio_Shutdown(void);
bool Audio_Stream_Pause(int32_t sound_id);
bool Audio_Stream_Unpause(int32_t sound_id);
int32_t Audio_Stream_CreateFromFile(const char *path);
bool Audio_Stream_Close(int32_t sound_id);
bool Audio_Stream_IsLooped(int32_t sound_id);
bool Audio_Stream_SetVolume(int32_t sound_id, float volume);
bool Audio_Stream_SetIsLooped(int32_t sound_id, bool is_looped);
bool Audio_Stream_SetFinishCallback(
int32_t sound_id, void (*callback)(int32_t sound_id, void *user_data),
void *user_data);
double Audio_Stream_GetTimestamp(int32_t sound_id);
double Audio_Stream_GetDuration(int32_t sound_id);
bool Audio_Stream_SeekTimestamp(int32_t sound_id, double timestamp);
bool Audio_Stream_SetStartTimestamp(int32_t sound_id, double timestamp);
bool Audio_Stream_SetStopTimestamp(int32_t sound_id, double timestamp);
bool Audio_Sample_LoadMany(size_t count, const char **contents, size_t *sizes);
bool Audio_Sample_LoadSingle(
int32_t sample_num, const char *content, size_t size);
bool Audio_Sample_Unload(int32_t sample_id);
bool Audio_Sample_UnloadAll(void);
int32_t Audio_Sample_Play(
int32_t sample_id, int32_t volume, float pitch, int32_t pan,
bool is_looped);
bool Audio_Sample_IsPlaying(int32_t sound_id);
bool Audio_Sample_Pause(int32_t sound_id);
bool Audio_Sample_PauseAll(void);
bool Audio_Sample_Unpause(int32_t sound_id);
bool Audio_Sample_UnpauseAll(void);
bool Audio_Sample_Close(int32_t sound_id);
bool Audio_Sample_CloseAll(void);
bool Audio_Sample_SetPan(int32_t sound_id, int32_t pan);
bool Audio_Sample_SetVolume(int32_t sound_id, int32_t volume);
bool Audio_Sample_SetPitch(int32_t sound_id, float pan);

View file

@ -0,0 +1,42 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} IMAGE_PIXEL;
typedef struct {
int32_t width;
int32_t height;
IMAGE_PIXEL *data;
} IMAGE;
typedef enum {
IMAGE_FIT_STRETCH,
IMAGE_FIT_CROP,
IMAGE_FIT_LETTERBOX,
IMAGE_FIT_SMART,
} IMAGE_FIT_MODE;
IMAGE *Image_Create(int width, int height);
IMAGE *Image_CreateFromFile(const char *path);
IMAGE *Image_CreateFromFileInto(
const char *path, int32_t target_width, int32_t target_height,
IMAGE_FIT_MODE fit_mode);
void Image_Free(IMAGE *image);
bool Image_GetFileInfo(const char *path, int32_t *width, int32_t *height);
bool Image_SaveToFile(const IMAGE *image, const char *path);
IMAGE *Image_Scale(
const IMAGE *source_image, size_t target_width, size_t target_height,
IMAGE_FIT_MODE fit_mode);

View file

@ -0,0 +1,25 @@
#include <stdint.h>
#define ENUM_MAP_DEFINE(enum_name, enum_value, str_value) \
EnumMap_Define(ENUM_MAP_NAME(enum_name), enum_value, str_value);
#define ENUM_MAP_DEFINE_SELF(enum_name, enum_value) \
EnumMap_Define(ENUM_MAP_NAME(enum_name), enum_value, #enum_value);
#define ENUM_MAP_GET(enum_name, str_value, default_value) \
EnumMap_Get(ENUM_MAP_NAME(enum_name), str_value, default_value)
#define ENUM_MAP_TO_STRING(enum_name, enum_value) \
EnumMap_ToString(ENUM_MAP_NAME(enum_name), enum_value)
#define ENUM_MAP_NAME(enum_name) #enum_name
// The function to put the EnumMap_Define calls in
extern void EnumMap_Init(void);
void EnumMap_Shutdown(void);
void EnumMap_Define(
const char *enum_name, int32_t enum_value, const char *str_value);
int32_t EnumMap_Get(
const char *enum_name, const char *str_value, int32_t default_value);
const char *EnumMap_ToString(const char *enum_name, int32_t enum_value);

View file

@ -0,0 +1,24 @@
#pragma once
#include <stdint.h>
typedef struct {
const char *name;
const void *sender;
void *data;
} EVENT;
typedef void (*EVENT_LISTENER)(const EVENT *, void *user_data);
typedef struct EVENT_MANAGER EVENT_MANAGER;
EVENT_MANAGER *EventManager_Create(void);
void EventManager_Free(EVENT_MANAGER *manager);
int32_t EventManager_Subscribe(
EVENT_MANAGER *manager, const char *event_name, const void *sender,
EVENT_LISTENER listener, void *user_data);
void EventManager_Unsubscribe(EVENT_MANAGER *manager, int32_t listener_id);
void EventManager_Fire(EVENT_MANAGER *manager, const EVENT *event);

View file

@ -0,0 +1,76 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef enum {
FILE_SEEK_SET,
FILE_SEEK_CUR,
FILE_SEEK_END,
} FILE_SEEK_MODE;
typedef enum {
FILE_OPEN_READ,
FILE_OPEN_READ_WRITE,
FILE_OPEN_WRITE,
} FILE_OPEN_MODE;
typedef struct MYFILE MYFILE;
bool File_DirExists(const char *path);
bool File_IsAbsolute(const char *path);
bool File_IsRelative(const char *path);
bool File_Exists(const char *path);
const char *File_GetGameDirectory(void);
// Get the absolute path to the given file, if possible.
// Internaly all operations on files within filesystem.c
// perform this normalization, so calling this function should
// only be necessary when interacting with external libraries.
char *File_GetFullPath(const char *path);
char *File_GetParentDirectory(const char *path);
char *File_GuessExtension(const char *path, const char **extensions);
MYFILE *File_Open(const char *path, FILE_OPEN_MODE mode);
void File_ReadData(MYFILE *file, void *data, size_t size);
void File_ReadItems(MYFILE *file, void *data, size_t count, size_t item_size);
int8_t File_ReadS8(MYFILE *file);
int16_t File_ReadS16(MYFILE *file);
int32_t File_ReadS32(MYFILE *file);
uint8_t File_ReadU8(MYFILE *file);
uint16_t File_ReadU16(MYFILE *file);
uint32_t File_ReadU32(MYFILE *file);
void File_WriteData(MYFILE *file, const void *data, size_t size);
void File_WriteItems(
MYFILE *file, const void *data, size_t count, size_t item_size);
void File_WriteS8(MYFILE *file, int8_t value);
void File_WriteS16(MYFILE *file, int16_t value);
void File_WriteS32(MYFILE *file, int32_t value);
void File_WriteU8(MYFILE *file, uint8_t value);
void File_WriteU16(MYFILE *file, uint16_t value);
void File_WriteU32(MYFILE *file, uint32_t value);
size_t File_Pos(MYFILE *file);
size_t File_Size(MYFILE *file);
const char *File_GetPath(MYFILE *file);
void File_Skip(MYFILE *file, size_t bytes);
void File_Seek(MYFILE *file, size_t pos, FILE_SEEK_MODE mode);
void File_Close(MYFILE *file);
bool File_Load(const char *path, char **output_data, size_t *output_size);
void File_CreateDirectory(const char *path);

View file

@ -0,0 +1,46 @@
#pragma once
#include "math.h"
typedef struct __PACKING {
int16_t goal_anim_state;
int16_t num_ranges;
int16_t range_idx;
} ANIM_CHANGE;
typedef struct __PACKING {
int16_t start_frame;
int16_t end_frame;
int16_t link_anim_num;
int16_t link_frame_num;
} ANIM_RANGE;
#if TR_VERSION == 1
typedef struct __PACKING {
BOUNDS_16 bounds;
XYZ_16 offset;
int16_t nmeshes;
int32_t *mesh_rots;
} FRAME_INFO;
#endif
typedef struct __PACKING {
#if TR_VERSION == 1
FRAME_INFO *frame_ptr;
uint32_t frame_ofs;
#elif TR_VERSION == 2
int16_t *frame_ptr;
#endif
int16_t interpolation;
int16_t current_anim_state;
int32_t velocity;
int32_t acceleration;
int16_t frame_base;
int16_t frame_end;
int16_t jump_anim_num;
int16_t jump_frame_num;
int16_t num_changes;
int16_t change_idx;
int16_t num_commands;
int16_t command_idx;
} ANIM;

View file

@ -0,0 +1,9 @@
#pragma once
#include "objects/ids.h"
#include <stdbool.h>
#include <stdint.h>
bool Backpack_AddItem(GAME_OBJECT_ID object_id);
bool Backpack_AddItemNTimes(GAME_OBJECT_ID object_id, int32_t n);

View file

@ -0,0 +1,3 @@
#pragma once
extern double Clock_GetHighPrecisionCounter(void);

View file

@ -0,0 +1,78 @@
#pragma once
#include "math.h"
#if TR_VERSION == 1
typedef struct __PACKING {
int32_t mid_floor;
int32_t mid_ceiling;
int32_t mid_type;
int32_t front_floor;
int32_t front_ceiling;
int32_t front_type;
int32_t left_floor;
int32_t left_ceiling;
int32_t left_type;
int32_t right_floor;
int32_t right_ceiling;
int32_t right_type;
int32_t radius;
int32_t bad_pos;
int32_t bad_neg;
int32_t bad_ceiling;
XYZ_32 shift;
XYZ_32 old;
int16_t facing;
DIRECTION quadrant;
int16_t coll_type;
int8_t tilt_x;
int8_t tilt_z;
int8_t hit_by_baddie;
int8_t hit_static;
uint16_t slopes_are_walls : 1;
uint16_t slopes_are_pits : 1;
uint16_t lava_is_pit : 1;
uint16_t enable_baddie_push : 1;
uint16_t enable_spaz : 1;
} COLL_INFO;
#elif TR_VERSION == 2
typedef struct __PACKING {
int32_t floor;
int32_t ceiling;
int32_t type;
} COLL_SIDE;
typedef struct __PACKING {
COLL_SIDE side_mid;
COLL_SIDE side_front;
COLL_SIDE side_left;
COLL_SIDE side_right;
int32_t radius;
int32_t bad_pos;
int32_t bad_neg;
int32_t bad_ceiling;
XYZ_32 shift;
XYZ_32 old;
int16_t old_anim_state;
int16_t old_anim_num;
int16_t old_frame_num;
int16_t facing;
int16_t quadrant;
int16_t coll_type;
int16_t *trigger;
int8_t x_tilt;
int8_t z_tilt;
int8_t hit_by_baddie;
int8_t hit_static;
// clang-format off
uint16_t slopes_are_walls: 1; // 0x01 1
uint16_t slopes_are_pits: 1; // 0x02 2
uint16_t lava_is_pit: 1; // 0x04 4
uint16_t enable_baddie_push: 1; // 0x08 8
uint16_t enable_spaz: 1; // 0x10 16
uint16_t hit_ceiling: 1; // 0x20 32
uint16_t pad: 10;
// clang-format on
} COLL_INFO;
#endif

View file

@ -0,0 +1,11 @@
#pragma once
#include "../../../config/option.h"
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_Config;
const CONFIG_OPTION *Console_Cmd_Config_GetOptionFromKey(const char *key);
const CONFIG_OPTION *Console_Cmd_Config_GetOptionFromTarget(const void *target);
COMMAND_RESULT Console_Cmd_Config_Helper(
const CONFIG_OPTION *option, const char *new_value);

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_Die;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_EndLevel;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_ExitGame;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_ExitToTitle;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_FlipMap;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_Fly;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_GiveItem;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_Heal;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_Kill;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_LoadGame;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_PlayDemo;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_PlayLevel;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_Pos;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_SaveGame;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_SetHealth;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_SFX;

View file

@ -0,0 +1,5 @@
#pragma once
#include "../common.h"
extern CONSOLE_COMMAND g_Console_Cmd_Teleport;

View file

@ -0,0 +1,41 @@
#pragma once
#include "../types.h"
#include <stdbool.h>
#include <stdint.h>
typedef enum {
CR_SUCCESS,
CR_FAILURE,
CR_UNAVAILABLE,
CR_BAD_INVOCATION,
} COMMAND_RESULT;
typedef struct __PACKING {
const struct __PACKING CONSOLE_COMMAND *cmd;
const char *prefix;
const char *args;
} COMMAND_CONTEXT;
typedef struct __PACKING CONSOLE_COMMAND {
const char *prefix;
COMMAND_RESULT (*proc)(const COMMAND_CONTEXT *ctx);
} CONSOLE_COMMAND;
void Console_Init(void);
void Console_Shutdown(void);
void Console_Open(void);
void Console_Close(void);
bool Console_IsOpened(void);
void Console_ScrollLogs(void);
int32_t Console_GetVisibleLogCount(void);
int32_t Console_GetMaxLogCount(void);
void Console_Log(const char *fmt, ...);
COMMAND_RESULT Console_Eval(const char *cmdline);
void Console_Draw(void);
extern void Console_DrawBackdrop(void);

View file

@ -0,0 +1,8 @@
#pragma once
#include "game/console/common.h"
#include <stdint.h>
extern int32_t Console_GetMaxLineLength(void);
extern CONSOLE_COMMAND **Console_GetCommands(void);

View file

@ -0,0 +1,8 @@
#pragma once
#define DONT_TARGET (-16384)
#define PHD_ONE 0x10000
#define STEP_L 256
#define WALL_L 1024 // 1 << WALL_SHIFT
#define WALL_SHIFT 10

View file

@ -0,0 +1,30 @@
#pragma once
#include "items.h"
#include "lot.h"
#include "math.h"
#include <stdint.h>
typedef enum {
MOOD_BORED = 0,
MOOD_ATTACK = 1,
MOOD_ESCAPE = 2,
MOOD_STALK = 3,
} MOOD_TYPE;
typedef struct __PACKING {
int16_t head_rotation;
int16_t neck_rotation;
int16_t maximum_turn;
uint16_t flags;
int16_t item_num;
MOOD_TYPE mood;
LOT_INFO lot;
XYZ_32 target;
#if TR_VERSION == 2
ITEM *enemy;
#endif
} CREATURE;
bool Creature_IsEnemy(const ITEM *item);

View file

@ -0,0 +1,4 @@
#pragma once
#include "effects/exploding_death.h"
#include "effects/types.h"

View file

@ -0,0 +1,6 @@
#pragma once
#include <stdint.h>
int32_t Effect_ExplodingDeath(
int16_t item_num, int32_t mesh_bits, int16_t damage);

View file

@ -0,0 +1,35 @@
#pragma once
#include "../math.h"
#include "../objects/ids.h"
#include "../types.h"
typedef struct __PACKING {
XYZ_32 pos;
XYZ_16 rot;
int16_t room_num;
#if TR_VERSION == 1
GAME_OBJECT_ID object_id;
#else
int16_t object_id;
#endif
#if TR_VERSION == 1
int16_t next_draw;
#endif
int16_t next_free;
int16_t next_active;
int16_t speed;
int16_t fall_speed;
int16_t frame_num;
int16_t counter;
int16_t shade;
#if TR_VERSION == 1
struct __PACKING {
struct __PACKING {
XYZ_32 pos;
XYZ_16 rot;
} result, prev;
} interp;
#endif
} FX;

View file

@ -0,0 +1,8 @@
#pragma once
#include "./gameflow/types.h"
#include <stdbool.h>
extern bool Game_IsPlayable(void);
extern GAMEFLOW_LEVEL_TYPE Game_GetCurrentLevelType(void);

View file

@ -0,0 +1,45 @@
GS_DEFINE(OSD_POS_GET, "Room: %d\nPosition: %.3f, %.3f, %.3f\nRotation: %.3f,%.3f,%.3f")
GS_DEFINE(OSD_CURRENT_HEALTH_GET, "Current Lara's health: %d")
GS_DEFINE(OSD_CURRENT_HEALTH_SET, "Lara's health set to %d")
GS_DEFINE(OSD_CONFIG_OPTION_GET, "%s is currently set to %s")
GS_DEFINE(OSD_CONFIG_OPTION_SET, "%s changed to %s")
GS_DEFINE(OSD_CONFIG_OPTION_UNKNOWN_OPTION, "Unknown option: %s")
GS_DEFINE(MISC_ON, "On")
GS_DEFINE(MISC_OFF, "Off")
GS_DEFINE(OSD_HEAL_ALREADY_FULL_HP, "Lara's already at full health")
GS_DEFINE(OSD_HEAL_SUCCESS, "Healed Lara back to full health")
GS_DEFINE(OSD_GIVE_ITEM, "Added %s to Lara's inventory")
GS_DEFINE(OSD_INVALID_ITEM, "Unknown item: %s")
GS_DEFINE(OSD_KILL_ALL, "Poof! %d enemies gone!")
GS_DEFINE(OSD_KILL_ALL_FAIL, "Uh-oh, there are no enemies left to kill...")
GS_DEFINE(OSD_KILL, "Bye-bye!")
GS_DEFINE(OSD_KILL_FAIL, "No enemy nearby...")
GS_DEFINE(OSD_INVALID_OBJECT, "Invalid object")
GS_DEFINE(OSD_INVALID_SAMPLE, "Invalid sound: %d")
GS_DEFINE(OSD_OBJECT_NOT_FOUND, "Object not found")
GS_DEFINE(OSD_SOUND_AVAILABLE_SAMPLES, "Available sounds: %s")
GS_DEFINE(OSD_SOUND_PLAYING_SAMPLE, "Playing sound %d")
GS_DEFINE(OSD_UNKNOWN_COMMAND, "Unknown command: %s")
GS_DEFINE(OSD_COMMAND_BAD_INVOCATION, "Invalid invocation: %s")
GS_DEFINE(OSD_COMMAND_UNAVAILABLE, "This command is not currently available")
GS_DEFINE(OSD_INVALID_ROOM, "Invalid room: %d. Valid rooms are 0-%d")
GS_DEFINE(OSD_POS_SET_POS, "Teleported to position: %.3f %.3f %.3f")
GS_DEFINE(OSD_POS_SET_POS_FAIL, "Failed to teleport to position: %.3f %.3f %.3f")
GS_DEFINE(OSD_POS_SET_ROOM, "Teleported to room: %d")
GS_DEFINE(OSD_POS_SET_ROOM_FAIL, "Failed to teleport to room: %d")
GS_DEFINE(OSD_POS_SET_ITEM, "Teleported to object: %s")
GS_DEFINE(OSD_POS_SET_ITEM_FAIL, "Failed to teleport to object: %s")
GS_DEFINE(OSD_INVALID_LEVEL, "Invalid level")
GS_DEFINE(OSD_PLAY_LEVEL, "Loading %s")
GS_DEFINE(OSD_LOAD_GAME, "Loaded game from save slot %d")
GS_DEFINE(OSD_LOAD_GAME_FAIL_UNAVAILABLE_SLOT, "Save slot %d is not available")
GS_DEFINE(OSD_LOAD_GAME_FAIL_INVALID_SLOT, "Invalid save slot %d")
GS_DEFINE(OSD_SAVE_GAME, "Saved game to save slot %d")
GS_DEFINE(OSD_SAVE_GAME_FAIL, "Cannot save the game in the current state")
GS_DEFINE(OSD_SAVE_GAME_FAIL_INVALID_SLOT, "Invalid save slot %d")
GS_DEFINE(OSD_FLIPMAP_ON, "Flipmap set to ON")
GS_DEFINE(OSD_FLIPMAP_OFF, "Flipmap set to OFF")
GS_DEFINE(OSD_FLIPMAP_FAIL_ALREADY_ON, "Flipmap is already ON")
GS_DEFINE(OSD_FLIPMAP_FAIL_ALREADY_OFF, "Flipmap is already OFF")
GS_DEFINE(OSD_AMBIGUOUS_INPUT_2, "Ambiguous input: %s and %s")
GS_DEFINE(OSD_AMBIGUOUS_INPUT_3, "Ambiguous input: %s, %s, ...")

View file

@ -0,0 +1,15 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#define GS_DEFINE(id, value) GameString_Define(#id, value);
#define GS(id) GameString_Get(#id)
#define GS_ID(id) (#id)
typedef const char *GAME_STRING_ID;
void GameString_Define(const char *key, const char *value);
bool GameString_IsKnown(const char *key);
const char *GameString_Get(const char *key);
void GameString_Clear(void);

View file

@ -0,0 +1,10 @@
#pragma once
#include "./types.h"
extern int32_t Gameflow_GetLevelCount(void);
extern const char *Gameflow_GetLevelFileName(int32_t level_num);
extern const char *Gameflow_GetLevelTitle(int32_t level_num);
extern int32_t Gameflow_GetGymLevelNumber(void);
extern void Gameflow_OverrideCommand(GAMEFLOW_COMMAND action);

Some files were not shown because too many files have changed in this diff Show more