mirror of
https://github.com/LostArtefacts/TRX.git
synced 2025-04-28 20:58:07 +03:00
misc: import libtrx codebase
This commit is contained in:
parent
d46069af22
commit
61ae36ee44
193 changed files with 29958 additions and 0 deletions
62
src/libtrx/benchmark.c
Normal file
62
src/libtrx/benchmark.c
Normal 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);
|
||||||
|
}
|
60
src/libtrx/config/common.c
Normal file
60
src/libtrx/config/common.c
Normal 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
197
src/libtrx/config/file.c
Normal 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
128
src/libtrx/engine/audio.c
Normal 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
|
||||||
|
}
|
25
src/libtrx/engine/audio_internal.h
Normal file
25
src/libtrx/engine/audio_internal.h
Normal 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);
|
776
src/libtrx/engine/audio_sample.c
Normal file
776
src/libtrx/engine/audio_sample.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
719
src/libtrx/engine/audio_stream.c
Normal file
719
src/libtrx/engine/audio_stream.c
Normal 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
560
src/libtrx/engine/image.c
Normal 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
99
src/libtrx/enum_map.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
src/libtrx/event_manager.c
Normal file
73
src/libtrx/event_manager.c
Normal 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
436
src/libtrx/filesystem.c
Normal 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(¤t_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(¤t_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);
|
||||||
|
}
|
10
src/libtrx/game/backpack.c
Normal file
10
src/libtrx/game/backpack.c
Normal 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;
|
||||||
|
}
|
278
src/libtrx/game/console/cmd/config.c
Normal file
278
src/libtrx/game/console/cmd/config.c
Normal 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,
|
||||||
|
};
|
41
src/libtrx/game/console/cmd/die.c
Normal file
41
src/libtrx/game/console/cmd/die.c
Normal 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,
|
||||||
|
};
|
26
src/libtrx/game/console/cmd/end_level.c
Normal file
26
src/libtrx/game/console/cmd/end_level.c
Normal 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,
|
||||||
|
};
|
21
src/libtrx/game/console/cmd/exit_game.c
Normal file
21
src/libtrx/game/console/cmd/exit_game.c
Normal 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,
|
||||||
|
};
|
21
src/libtrx/game/console/cmd/exit_to_title.c
Normal file
21
src/libtrx/game/console/cmd/exit_to_title.c
Normal 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,
|
||||||
|
};
|
38
src/libtrx/game/console/cmd/flipmap.c
Normal file
38
src/libtrx/game/console/cmd/flipmap.c
Normal 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,
|
||||||
|
};
|
43
src/libtrx/game/console/cmd/fly.c
Normal file
43
src/libtrx/game/console/cmd/fly.c
Normal 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,
|
||||||
|
};
|
86
src/libtrx/game/console/cmd/give_item.c
Normal file
86
src/libtrx/game/console/cmd/give_item.c
Normal 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,
|
||||||
|
};
|
37
src/libtrx/game/console/cmd/heal.c
Normal file
37
src/libtrx/game/console/cmd/heal.c
Normal 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,
|
||||||
|
};
|
135
src/libtrx/game/console/cmd/kill.c
Normal file
135
src/libtrx/game/console/cmd/kill.c
Normal 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,
|
||||||
|
};
|
41
src/libtrx/game/console/cmd/load_game.c
Normal file
41
src/libtrx/game/console/cmd/load_game.c
Normal 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,
|
||||||
|
};
|
21
src/libtrx/game/console/cmd/play_demo.c
Normal file
21
src/libtrx/game/console/cmd/play_demo.c
Normal 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,
|
||||||
|
};
|
86
src/libtrx/game/console/cmd/play_level.c
Normal file
86
src/libtrx/game/console/cmd/play_level.c
Normal 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,
|
||||||
|
};
|
43
src/libtrx/game/console/cmd/pos.c
Normal file
43
src/libtrx/game/console/cmd/pos.c
Normal 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,
|
||||||
|
};
|
37
src/libtrx/game/console/cmd/save_game.c
Normal file
37
src/libtrx/game/console/cmd/save_game.c
Normal 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,
|
||||||
|
};
|
40
src/libtrx/game/console/cmd/set_health.c
Normal file
40
src/libtrx/game/console/cmd/set_health.c
Normal 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,
|
||||||
|
};
|
85
src/libtrx/game/console/cmd/sfx.c
Normal file
85
src/libtrx/game/console/cmd/sfx.c
Normal 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,
|
||||||
|
};
|
184
src/libtrx/game/console/cmd/teleport.c
Normal file
184
src/libtrx/game/console/cmd/teleport.c
Normal 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,
|
||||||
|
};
|
189
src/libtrx/game/console/common.c
Normal file
189
src/libtrx/game/console/common.c
Normal 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);
|
||||||
|
}
|
57
src/libtrx/game/game_string.c
Normal file
57
src/libtrx/game/game_string.c
Normal 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
21
src/libtrx/game/items.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
82
src/libtrx/game/objects/names.c
Normal file
82
src/libtrx/game/objects/names.c
Normal 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;
|
||||||
|
}
|
43
src/libtrx/game/ui/common.c
Normal file
43
src/libtrx/game/ui/common.c
Normal 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);
|
||||||
|
}
|
49
src/libtrx/game/ui/events.c
Normal file
49
src/libtrx/game/ui/events.c
Normal 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);
|
||||||
|
}
|
235
src/libtrx/game/ui/widgets/console.c
Normal file
235
src/libtrx/game/ui/widgets/console.c
Normal 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;
|
||||||
|
}
|
307
src/libtrx/game/ui/widgets/prompt.c
Normal file
307
src/libtrx/game/ui/widgets/prompt.c
Normal 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);
|
||||||
|
}
|
60
src/libtrx/game/ui/widgets/spacer.c
Normal file
60
src/libtrx/game/ui/widgets/spacer.c
Normal 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;
|
||||||
|
}
|
255
src/libtrx/game/ui/widgets/stack.c
Normal file
255
src/libtrx/game/ui/widgets/stack.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
src/libtrx/gfx/2d/2d_renderer.c
Normal file
118
src/libtrx/gfx/2d/2d_renderer.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
129
src/libtrx/gfx/2d/2d_surface.c
Normal file
129
src/libtrx/gfx/2d/2d_surface.c
Normal 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;
|
||||||
|
}
|
359
src/libtrx/gfx/3d/3d_renderer.c
Normal file
359
src/libtrx/gfx/3d/3d_renderer.c
Normal 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);
|
||||||
|
}
|
160
src/libtrx/gfx/3d/vertex_stream.c
Normal file
160
src/libtrx/gfx/3d/vertex_stream.c
Normal 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
329
src/libtrx/gfx/context.c
Normal 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;
|
||||||
|
}
|
67
src/libtrx/gfx/gl/buffer.c
Normal file
67
src/libtrx/gfx/gl/buffer.c
Normal 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, ¶ms);
|
||||||
|
GFX_GL_CheckError();
|
||||||
|
return params;
|
||||||
|
}
|
8745
src/libtrx/gfx/gl/gl_core_3_3.c
Normal file
8745
src/libtrx/gfx/gl/gl_core_3_3.c
Normal file
File diff suppressed because it is too large
Load diff
204
src/libtrx/gfx/gl/program.c
Normal file
204
src/libtrx/gfx/gl/program.c
Normal 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();
|
||||||
|
}
|
35
src/libtrx/gfx/gl/sampler.c
Normal file
35
src/libtrx/gfx/gl/sampler.c
Normal 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();
|
||||||
|
}
|
88
src/libtrx/gfx/gl/texture.c
Normal file
88
src/libtrx/gfx/gl/texture.c
Normal 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
27
src/libtrx/gfx/gl/utils.c
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
40
src/libtrx/gfx/gl/vertex_array.c
Normal file
40
src/libtrx/gfx/gl/vertex_array.c
Normal 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();
|
||||||
|
}
|
245
src/libtrx/gfx/renderers/fbo_renderer.c
Normal file
245
src/libtrx/gfx/renderers/fbo_renderer.c
Normal 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,
|
||||||
|
};
|
35
src/libtrx/gfx/renderers/legacy_renderer.c
Normal file
35
src/libtrx/gfx/renderers/legacy_renderer.c
Normal 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,
|
||||||
|
};
|
76
src/libtrx/gfx/screenshot.c
Normal file
76
src/libtrx/gfx/screenshot.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
24
src/libtrx/include/libtrx/benchmark.h
Normal file
24
src/libtrx/include/libtrx/benchmark.h
Normal 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);
|
29
src/libtrx/include/libtrx/bson.h
Normal file
29
src/libtrx/include/libtrx/bson.h
Normal 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);
|
6
src/libtrx/include/libtrx/config.h
Normal file
6
src/libtrx/include/libtrx/config.h
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "config/common.h"
|
||||||
|
#include "config/file.h"
|
||||||
|
#include "config/map.h"
|
||||||
|
#include "config/option.h"
|
27
src/libtrx/include/libtrx/config/common.h
Normal file
27
src/libtrx/include/libtrx/config/common.h
Normal 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);
|
22
src/libtrx/include/libtrx/config/file.h
Normal file
22
src/libtrx/include/libtrx/config/file.h
Normal 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);
|
42
src/libtrx/include/libtrx/config/map.h
Normal file
42
src/libtrx/include/libtrx/config/map.h
Normal 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) },
|
17
src/libtrx/include/libtrx/config/option.h
Normal file
17
src/libtrx/include/libtrx/config/option.h
Normal 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;
|
51
src/libtrx/include/libtrx/engine/audio.h
Normal file
51
src/libtrx/include/libtrx/engine/audio.h
Normal 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);
|
42
src/libtrx/include/libtrx/engine/image.h
Normal file
42
src/libtrx/include/libtrx/engine/image.h
Normal 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);
|
25
src/libtrx/include/libtrx/enum_map.h
Normal file
25
src/libtrx/include/libtrx/enum_map.h
Normal 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);
|
24
src/libtrx/include/libtrx/event_manager.h
Normal file
24
src/libtrx/include/libtrx/event_manager.h
Normal 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);
|
76
src/libtrx/include/libtrx/filesystem.h
Normal file
76
src/libtrx/include/libtrx/filesystem.h
Normal 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);
|
46
src/libtrx/include/libtrx/game/anims.h
Normal file
46
src/libtrx/include/libtrx/game/anims.h
Normal 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;
|
9
src/libtrx/include/libtrx/game/backpack.h
Normal file
9
src/libtrx/include/libtrx/game/backpack.h
Normal 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);
|
3
src/libtrx/include/libtrx/game/clock.h
Normal file
3
src/libtrx/include/libtrx/game/clock.h
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
extern double Clock_GetHighPrecisionCounter(void);
|
78
src/libtrx/include/libtrx/game/collision.h
Normal file
78
src/libtrx/include/libtrx/game/collision.h
Normal 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
|
11
src/libtrx/include/libtrx/game/console/cmd/config.h
Normal file
11
src/libtrx/include/libtrx/game/console/cmd/config.h
Normal 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);
|
5
src/libtrx/include/libtrx/game/console/cmd/die.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/die.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_Die;
|
5
src/libtrx/include/libtrx/game/console/cmd/end_level.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/end_level.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_EndLevel;
|
5
src/libtrx/include/libtrx/game/console/cmd/exit_game.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/exit_game.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_ExitGame;
|
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_ExitToTitle;
|
5
src/libtrx/include/libtrx/game/console/cmd/flipmap.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/flipmap.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_FlipMap;
|
5
src/libtrx/include/libtrx/game/console/cmd/fly.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/fly.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_Fly;
|
5
src/libtrx/include/libtrx/game/console/cmd/give_item.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/give_item.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_GiveItem;
|
5
src/libtrx/include/libtrx/game/console/cmd/heal.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/heal.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_Heal;
|
5
src/libtrx/include/libtrx/game/console/cmd/kill.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/kill.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_Kill;
|
5
src/libtrx/include/libtrx/game/console/cmd/load_game.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/load_game.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_LoadGame;
|
5
src/libtrx/include/libtrx/game/console/cmd/play_demo.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/play_demo.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_PlayDemo;
|
5
src/libtrx/include/libtrx/game/console/cmd/play_level.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/play_level.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_PlayLevel;
|
5
src/libtrx/include/libtrx/game/console/cmd/pos.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/pos.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_Pos;
|
5
src/libtrx/include/libtrx/game/console/cmd/save_game.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/save_game.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_SaveGame;
|
5
src/libtrx/include/libtrx/game/console/cmd/set_health.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/set_health.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_SetHealth;
|
5
src/libtrx/include/libtrx/game/console/cmd/sfx.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/sfx.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_SFX;
|
5
src/libtrx/include/libtrx/game/console/cmd/teleport.h
Normal file
5
src/libtrx/include/libtrx/game/console/cmd/teleport.h
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../common.h"
|
||||||
|
|
||||||
|
extern CONSOLE_COMMAND g_Console_Cmd_Teleport;
|
41
src/libtrx/include/libtrx/game/console/common.h
Normal file
41
src/libtrx/include/libtrx/game/console/common.h
Normal 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);
|
8
src/libtrx/include/libtrx/game/console/extern.h
Normal file
8
src/libtrx/include/libtrx/game/console/extern.h
Normal 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);
|
8
src/libtrx/include/libtrx/game/const.h
Normal file
8
src/libtrx/include/libtrx/game/const.h
Normal 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
|
30
src/libtrx/include/libtrx/game/creature.h
Normal file
30
src/libtrx/include/libtrx/game/creature.h
Normal 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);
|
4
src/libtrx/include/libtrx/game/effects.h
Normal file
4
src/libtrx/include/libtrx/game/effects.h
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "effects/exploding_death.h"
|
||||||
|
#include "effects/types.h"
|
6
src/libtrx/include/libtrx/game/effects/exploding_death.h
Normal file
6
src/libtrx/include/libtrx/game/effects/exploding_death.h
Normal 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);
|
35
src/libtrx/include/libtrx/game/effects/types.h
Normal file
35
src/libtrx/include/libtrx/game/effects/types.h
Normal 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;
|
8
src/libtrx/include/libtrx/game/game.h
Normal file
8
src/libtrx/include/libtrx/game/game.h
Normal 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);
|
45
src/libtrx/include/libtrx/game/game_string.def
Normal file
45
src/libtrx/include/libtrx/game/game_string.def
Normal 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, ...")
|
15
src/libtrx/include/libtrx/game/game_string.h
Normal file
15
src/libtrx/include/libtrx/game/game_string.h
Normal 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);
|
10
src/libtrx/include/libtrx/game/gameflow/common.h
Normal file
10
src/libtrx/include/libtrx/game/gameflow/common.h
Normal 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
Loading…
Add table
Add a link
Reference in a new issue