big refactor
This commit is contained in:
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
#ifndef ANIMATION_H
|
||||
#define ANIMATION_H
|
||||
|
||||
typedef struct {
|
||||
int state;
|
||||
int currentFrame;
|
||||
int maxFrame;
|
||||
int framesCounter;
|
||||
int framesSpeed;
|
||||
float animTimer;
|
||||
} Animation;
|
||||
|
||||
void UpdateAnimation(Animation *animation, float deltaTime) {
|
||||
animation->animTimer += deltaTime;
|
||||
float frameTime = 1.0f / animation->framesSpeed;
|
||||
|
||||
if (animation->animTimer < frameTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
animation->animTimer -= frameTime;
|
||||
animation->currentFrame++;
|
||||
|
||||
if (animation->currentFrame >= animation->maxFrame) {
|
||||
animation->currentFrame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ANIMATION_H
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
#ifndef ASSET_H
|
||||
#define ASSET_H
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
float frameWidth;
|
||||
float frameHeight;
|
||||
float rotation;
|
||||
} TextureAsset;
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
} ShaderAsset;
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
} SoundtrackAsset;
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
} SoundAsset;
|
||||
|
||||
typedef struct {
|
||||
const TextureAsset *textureAssets;
|
||||
const ShaderAsset *shaderAssets;
|
||||
const SoundtrackAsset *soundtrackAssets;
|
||||
const SoundAsset *soundAssets;
|
||||
} Assets;
|
||||
|
||||
#endif // ASSET_H
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
#ifndef CAMERA_H
|
||||
#define CAMERA_H
|
||||
|
||||
#include "input.h"
|
||||
#include <raymath.h>
|
||||
|
||||
Camera2D CreateCamera(int screenWidth, int screenHeight, Vector2 playerPosition, float initZoom){
|
||||
return (Camera2D){
|
||||
.offset = (Vector2){screenWidth / 2.0f, screenHeight / 2.0f},
|
||||
.target = playerPosition,
|
||||
.rotation = 0.0f,
|
||||
.zoom = initZoom
|
||||
};
|
||||
}
|
||||
|
||||
void UpdateCameraTarget(Camera2D *camera, Vector2 *position, const float smoothness){
|
||||
camera->target = Vector2Lerp(camera->target, *position, smoothness * GetFrameTime());
|
||||
}
|
||||
|
||||
void UpdateCameraZoom(Camera2D *camera, const float *scaleFactor, const float *minZoom, const float *maxZoom){
|
||||
float targetZoom = Clamp(camera->zoom * *scaleFactor, *minZoom, *maxZoom);
|
||||
camera->zoom = Lerp(camera->zoom, targetZoom, 0.1f);
|
||||
}
|
||||
|
||||
void UpdateCamera2D(Camera2D* camera, Vector2* position, Mouse* mouse){
|
||||
UpdateCameraTarget(camera, position, 5.0f);
|
||||
UpdateCameraZoom(camera, &mouse->scaleFactor, &mouse->minZoom, &mouse->maxZoom);
|
||||
}
|
||||
|
||||
|
||||
#endif // CAMERA_H
|
||||
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#define RINI_IMPLEMENTATION
|
||||
#define RINI_VALUE_DELIMITER '='
|
||||
|
||||
#include <rini.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
char *path;
|
||||
rini_config rini;
|
||||
} Config;
|
||||
|
||||
void InitConfig(Config *config, const char* path) {
|
||||
config->rini = rini_load_config(path);
|
||||
if (config->rini.count == 0) {
|
||||
printf("Warning: Loaded config has no entries. Path: %s\n", config->path);
|
||||
}
|
||||
}
|
||||
|
||||
int GetConfigInt(Config *config, const char *key) {
|
||||
return rini_get_config_value_fallback(config->rini, key, 0);
|
||||
}
|
||||
|
||||
int GetConfigIntOrDefault(Config *config, const char *key, int default_value) {
|
||||
return rini_get_config_value_fallback(config->rini, key, default_value);
|
||||
}
|
||||
|
||||
const char* GetConfigString(Config *config, const char *key) {
|
||||
return rini_get_config_value_text(config->rini, key);
|
||||
}
|
||||
|
||||
const char* GetConfigStringOrDefault(Config *config, const char *key, const char *default_value) {
|
||||
return rini_get_config_value_text_fallback(config->rini, key, default_value);
|
||||
}
|
||||
|
||||
const char* GetConfigDescription(Config *config, const char *key) {
|
||||
return rini_get_config_value_description(config->rini, key);
|
||||
}
|
||||
|
||||
int SetConfigInt(Config *config, const char *key, int value, const char *desc) {
|
||||
return rini_set_config_value(&config->rini, key, value, desc);
|
||||
}
|
||||
|
||||
int SetConfigString(Config *config, const char *key, const char *text, const char *desc) {
|
||||
return rini_set_config_value_text(&config->rini, key, text, desc);
|
||||
}
|
||||
|
||||
int SetConfigDescription(Config *config, const char *key, const char *desc) {
|
||||
return rini_set_config_value_description(&config->rini, key, desc);
|
||||
}
|
||||
|
||||
int AddCommentLine(Config *config, const char *comment) {
|
||||
return rini_set_config_comment_line(&config->rini, comment);
|
||||
}
|
||||
|
||||
void SaveConfig(Config *config) {
|
||||
rini_save_config(config->rini, config->path);
|
||||
}
|
||||
|
||||
void CleanUpConfig(Config *config) {
|
||||
rini_unload_config(&config->rini);
|
||||
}
|
||||
|
||||
#endif // CONFIG_H
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
#ifndef DISPLAY_H
|
||||
#define DISPLAY_H
|
||||
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
const char* title;
|
||||
} Display;
|
||||
|
||||
#endif // DISPLAY_H
|
||||
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
#ifndef GAMEGUI_H
|
||||
#define GAMEGUI_H
|
||||
|
||||
#include "sprite.h"
|
||||
#include <raylib.h>
|
||||
|
||||
typedef enum {
|
||||
NOT_CLICKED,
|
||||
HOVER,
|
||||
CLICKED
|
||||
} ButtonState;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Sprite sprite;
|
||||
Vector2 notClicked;
|
||||
Vector2 hover;
|
||||
Vector2 clicked;
|
||||
bool isHovered;
|
||||
bool isClicked;
|
||||
} ImageButton;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Sprite sprite;
|
||||
} ImageElement;
|
||||
|
||||
ImageButton CreateImageButton(ButtonState state, Texture2D *texture, const TextureAsset *asset, Vector2 *source, Vector2 *clicked, Vector2 *hover, Rectangle *destRec){
|
||||
return (ImageButton){
|
||||
.sprite = {
|
||||
.texture = *texture,
|
||||
.sourceRec = (Rectangle){ source->x, source->y, asset->frameWidth, asset->frameHeight},
|
||||
.destRec = *destRec,
|
||||
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
|
||||
.origin = (Vector2){0.0f, 0.0f},
|
||||
.rotation = 0.0f,
|
||||
.color = WHITE
|
||||
},
|
||||
.notClicked = (Vector2){source->x, source->y},
|
||||
.hover = *hover,
|
||||
.clicked = *clicked
|
||||
};
|
||||
}
|
||||
|
||||
ImageElement CreateImageElement(Texture2D *texture, const TextureAsset *asset, Vector2 *source, Rectangle *destRec){
|
||||
return (ImageElement){
|
||||
.sprite = {
|
||||
.texture = *texture,
|
||||
.sourceRec = (Rectangle){ source->x, source->y, asset->frameWidth, asset->frameHeight},
|
||||
.destRec = *destRec,
|
||||
.frameSize = (Vector2){asset->frameWidth, asset->frameHeight},
|
||||
.origin = (Vector2){0.0f, 0.0f},
|
||||
.rotation = 0.0f,
|
||||
.color = WHITE
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
int isImageButtonClicked(ImageButton *b, Sound soundHover, Sound soundClick){
|
||||
if (CheckCollisionPointRec(GetMousePosition(), b->sprite.destRec)) {
|
||||
|
||||
if (!b->isHovered) {
|
||||
PlaySound(soundHover);
|
||||
b->isHovered = true;
|
||||
UpdateSpriteSourceRec(&b->sprite, &b->hover);
|
||||
}
|
||||
|
||||
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)){
|
||||
if (!b->isClicked) {
|
||||
PlaySound(soundClick);
|
||||
b->isClicked = true;
|
||||
UpdateSpriteSourceRec(&b->sprite, &b->clicked);
|
||||
}
|
||||
}
|
||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
|
||||
{
|
||||
b->isClicked = false;
|
||||
b->isHovered = false;
|
||||
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
UpdateSpriteSourceRec(&b->sprite, &b->notClicked);
|
||||
b->isClicked = false;
|
||||
b->isHovered = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void renderImageButton(ImageButton *b) {
|
||||
RenderSprite(&b->sprite);
|
||||
}
|
||||
|
||||
void renderImageElement(ImageElement *e){
|
||||
RenderSprite(&e->sprite);
|
||||
}
|
||||
|
||||
|
||||
#endif // GAMEGUI_H
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
#ifndef GUI_H
|
||||
#define GUI_H
|
||||
|
||||
#define RAYGUI_IMPLEMENTATION
|
||||
#include <raygui.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Rectangle rec;
|
||||
char *text;
|
||||
} Button;
|
||||
|
||||
void initButton(Button *b, char *text, int buttonWidth, int buttonHeight, int buttonPosX, int buttonPosY)
|
||||
{
|
||||
b->rec = (Rectangle){buttonPosX - buttonWidth / 2, buttonPosY - buttonHeight / 2, buttonWidth, buttonHeight};
|
||||
b->text = text;
|
||||
}
|
||||
|
||||
int isButtonClicked(Button *b)
|
||||
{
|
||||
return (CheckCollisionPointRec(GetMousePosition(), b->rec) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON));
|
||||
}
|
||||
|
||||
void renderButton(Button *b)
|
||||
{
|
||||
GuiButton(b->rec, b->text);
|
||||
}
|
||||
|
||||
#endif // GUI_H
|
||||
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
#ifndef INPUT_H
|
||||
#define INPUT_H
|
||||
|
||||
#include <raymath.h>
|
||||
|
||||
typedef struct {
|
||||
Vector2 screenPosition;
|
||||
Vector2 worldPosition;
|
||||
float zoomSpeed;
|
||||
float minZoom;
|
||||
float maxZoom;
|
||||
float scaleFactor;
|
||||
float wheelMove;
|
||||
bool leftButton;
|
||||
bool rightButton;
|
||||
Texture2D cursorTexture;
|
||||
} Mouse;
|
||||
|
||||
Mouse CreateMouse(float zoomSpeed, float minZoom, float maxZoom, Texture2D *texture){
|
||||
HideCursor();
|
||||
return (Mouse){
|
||||
.zoomSpeed = zoomSpeed,
|
||||
.minZoom = minZoom,
|
||||
.maxZoom = maxZoom,
|
||||
.cursorTexture = *texture
|
||||
};
|
||||
}
|
||||
|
||||
void UpdateMouseScreen(Mouse *mouse){
|
||||
mouse->screenPosition = GetMousePosition();
|
||||
|
||||
}
|
||||
|
||||
void UpdateMouse(Mouse *mouse, Camera2D *camera) {
|
||||
|
||||
Vector2 pos = GetMousePosition();
|
||||
mouse->screenPosition = pos;
|
||||
mouse->worldPosition = GetScreenToWorld2D(pos, *camera);
|
||||
mouse->leftButton = IsMouseButtonDown(MOUSE_LEFT_BUTTON);
|
||||
mouse->rightButton = IsMouseButtonDown(MOUSE_RIGHT_BUTTON);
|
||||
|
||||
mouse->wheelMove = GetMouseWheelMove();
|
||||
if (mouse->wheelMove != 0) {
|
||||
float zoomFactor = 1.0f + (mouse->zoomSpeed * fabsf(mouse->wheelMove));
|
||||
mouse->scaleFactor = (mouse->wheelMove < 0) ? (1.0f / zoomFactor) : zoomFactor;
|
||||
} else {
|
||||
mouse->scaleFactor = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 GetDirectionVector(void){
|
||||
Vector2 vector = {.x = 0.0f, .y = 0.0f};
|
||||
if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)){vector.x += 1.0f; }
|
||||
if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)){ vector.x -= 1.0f; }
|
||||
if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)){ vector.y -= 1.0f; }
|
||||
if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)){ vector.y += 1.0f; }
|
||||
if (Vector2Length(vector) > 0.0f) {vector = Vector2Normalize(vector); }
|
||||
return vector;
|
||||
}
|
||||
|
||||
#endif //INPUT_H
|
||||
Vendored
+129
@@ -0,0 +1,129 @@
|
||||
#ifndef OBJECT_H
|
||||
#define OBJECT_H
|
||||
#include "sprite.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
Sprite sprite;
|
||||
Vector2 position;
|
||||
} Object;
|
||||
|
||||
typedef struct {
|
||||
Object *data;
|
||||
const Object *prototype;
|
||||
size_t count;
|
||||
} ObjectArray;
|
||||
|
||||
ObjectArray CreateObjectArray(const Object *prototype, const size_t count) {
|
||||
ObjectArray objects = {0};
|
||||
|
||||
if (count == 0) {
|
||||
printf("Objects size must be greater than 0.\n");
|
||||
return objects;
|
||||
}
|
||||
|
||||
objects.count = count;
|
||||
objects.data = (Object* )malloc(count * sizeof(Object));
|
||||
|
||||
if (!objects.data) {
|
||||
printf("Memory allocation of objects failed.\n");
|
||||
return objects;
|
||||
}
|
||||
|
||||
objects.prototype = prototype;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
objects.data[i].position = prototype->position;
|
||||
objects.data[i].sprite = prototype->sprite;
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
Object* GetRandomObject(ObjectArray *objects){
|
||||
if (objects->count == 0 || objects->data == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
return &objects->data[GetRandomValue(0, (int)objects->count-1)];
|
||||
}
|
||||
|
||||
void AddObjectToArray(ObjectArray *objects, const Object *prototype, Vector2 position) {
|
||||
Object *newData = (Object* )realloc(objects->data, (objects->count + 1) * sizeof(Object));
|
||||
if (newData) {
|
||||
objects->data = newData;
|
||||
objects->data[objects->count] = *prototype;
|
||||
objects->data[objects->count].position = position;
|
||||
objects->data[objects->count].sprite.destRec.x = position.x;
|
||||
objects->data[objects->count].sprite.destRec.y = position.y;
|
||||
objects->count++;
|
||||
} else {
|
||||
printf("Memory allocation failed while adding an object.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void SetPositionForObjectArray(ObjectArray *objects, const Vector2Array *positions) {
|
||||
if (positions->count != objects->count) {
|
||||
printf("Warning: Position array size (%zu) does not match ObjectArray size (%zu)\n", positions->count, objects->count);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < objects->count; i++) {
|
||||
objects->data[i].position = positions->data[i];
|
||||
objects->data[i].sprite.destRec.x = positions->data[i].x;
|
||||
objects->data[i].sprite.destRec.y = positions->data[i].y;
|
||||
}
|
||||
}
|
||||
|
||||
void SetRandomPositionForObjectArray(ObjectArray *objects, const Rectangle bounds){
|
||||
for (int i = 0; i < objects->count; i++) {
|
||||
Vector2 position = GetRandomVector(bounds);
|
||||
objects->data[i].position = position;
|
||||
objects->data[i].sprite.destRec.x = position.x;
|
||||
objects->data[i].sprite.destRec.y = position.y;
|
||||
}
|
||||
}
|
||||
|
||||
void SwapAndPopObjectsArrayOnRecCollision(ObjectArray *objects, const Rectangle rec){
|
||||
for (int i = 0; i < objects->count; i++){
|
||||
|
||||
if (CheckCollisionRecs(rec, objects->data[i].sprite.destRec)){
|
||||
if (objects->count == 1) {
|
||||
objects->count = 0;
|
||||
break;
|
||||
}
|
||||
objects->data[i] = objects->data[objects->count - 1];
|
||||
objects->count--;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
objects->data = (Object*)realloc(objects->data, objects->count * sizeof(Object));
|
||||
|
||||
}
|
||||
|
||||
void SwapAndPopObjectsArrayOnPointCollision(ObjectArray *objects, const Vector2 point){
|
||||
for (int i = 0; i < objects->count; i++){
|
||||
|
||||
if (CheckCollisionPointRec(point, objects->data[i].sprite.destRec)){
|
||||
if (objects->count == 1) {
|
||||
objects->count = 0;
|
||||
break;
|
||||
}
|
||||
objects->data[i] = objects->data[objects->count - 1];
|
||||
objects->count--;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
objects->data = (Object*)realloc(objects->data, objects->count * sizeof(Object));
|
||||
}
|
||||
|
||||
Vector2 GetDirectionToObject(Object *object, Vector2 position){
|
||||
return Vector2Normalize(Vector2Subtract(object->position, position));
|
||||
}
|
||||
|
||||
void RenderObjectArray(const ObjectArray *objects){
|
||||
for (int i = 0; i < objects->count; i++){
|
||||
RenderSprite(&objects->data[i].sprite);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OBJECT_H
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
#ifndef PHYSICS_H
|
||||
#define PHYSICS_H
|
||||
|
||||
#include <raymath.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Vector2 position;
|
||||
Vector2 direction;
|
||||
Vector2 velocity;
|
||||
float speed;
|
||||
} Physics;
|
||||
|
||||
void UpdatePhysicsDirection(Vector2 *direction, Vector2 vector)
|
||||
{
|
||||
*direction = Vector2Normalize(vector);
|
||||
}
|
||||
|
||||
void UpdatePhysicsVelocity(Vector2 *velocity, Vector2 *direction, float *speed)
|
||||
{
|
||||
*velocity = Vector2Scale(*direction, *speed * GetFrameTime());
|
||||
}
|
||||
|
||||
void UpdatePhysicsPosition(Vector2 *position, Vector2 *velocity)
|
||||
{
|
||||
*position = Vector2Add(*position, *velocity);
|
||||
}
|
||||
|
||||
void UpdatePhysicsSpeed(float *speed, float *newSpeed)
|
||||
{
|
||||
*speed = *newSpeed;
|
||||
}
|
||||
|
||||
void UpdatePhysics(Physics *physics, Vector2 direction)
|
||||
{
|
||||
UpdatePhysicsDirection(&physics->direction, direction);
|
||||
UpdatePhysicsVelocity(&physics->velocity, &physics->direction, &physics->speed);
|
||||
UpdatePhysicsPosition(&physics->position, &physics->velocity);
|
||||
}
|
||||
|
||||
#endif // PHYSICS_H
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
#ifndef POSTFX_H
|
||||
#define POSTFX_H
|
||||
|
||||
typedef struct {
|
||||
Shader shader;
|
||||
int resolutionLoc;
|
||||
int strengthLoc;
|
||||
float strength;
|
||||
} PostProcessing;
|
||||
|
||||
PostProcessing CreatePostFX(const char* shaderPath){
|
||||
PostProcessing postFX = {0};
|
||||
postFX.shader = LoadShader(NULL, shaderPath);
|
||||
|
||||
if (postFX.shader.id == 0) {
|
||||
TraceLog(LOG_ERROR, "Failed to load shader: %s", shaderPath);
|
||||
return postFX;
|
||||
}
|
||||
|
||||
postFX.strength = 0.3f;
|
||||
postFX.resolutionLoc = GetShaderLocation(postFX.shader, "resolution");
|
||||
postFX.strengthLoc = GetShaderLocation(postFX.shader, "strength");
|
||||
Vector2 resolution = { (float)GetScreenWidth(), (float)GetScreenHeight() };
|
||||
SetShaderValue(postFX.shader, postFX.resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
|
||||
return postFX;
|
||||
}
|
||||
|
||||
void UpdatePostFX(PostProcessing *postFX){
|
||||
if (postFX->shader.id == 0) return;
|
||||
SetShaderValue(postFX->shader, postFX->strengthLoc, &postFX->strength, SHADER_UNIFORM_FLOAT);
|
||||
Vector2 resolution = { (float)GetScreenWidth(), (float)GetScreenHeight() };
|
||||
SetShaderValue(postFX->shader, postFX->resolutionLoc, &resolution, SHADER_UNIFORM_VEC2);
|
||||
}
|
||||
|
||||
void RenderPostFX(PostProcessing *postFX, RenderTexture2D *target){
|
||||
BeginShaderMode(postFX->shader);
|
||||
DrawTextureRec(target->texture, (Rectangle){ 0, 0, target->texture.width, -target->texture.height }, (Vector2){ 0, 0 }, WHITE);
|
||||
EndShaderMode();
|
||||
}
|
||||
|
||||
void UnloadPostFX(PostProcessing *postFX){
|
||||
if (postFX->shader.id == 0) return;
|
||||
UnloadShader(postFX->shader);
|
||||
}
|
||||
|
||||
#endif // POSTFX_H
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
#ifndef SOUNDTRACK_H
|
||||
#define SOUNDTRACK_H
|
||||
|
||||
typedef struct {
|
||||
Music music;
|
||||
bool playing;
|
||||
} Soundtrack;
|
||||
|
||||
Soundtrack LoadSoundtrack(const char* musicPath){
|
||||
Soundtrack soundtrack = {
|
||||
.music = LoadMusicStream(musicPath),
|
||||
.playing = 0
|
||||
};
|
||||
PlayMusicStream(soundtrack.music);
|
||||
return soundtrack;
|
||||
}
|
||||
|
||||
void UpdateSoundtrack(Soundtrack *soundtrack){
|
||||
UpdateMusicStream(soundtrack->music);
|
||||
}
|
||||
|
||||
#endif // SOUNDTRACK_H
|
||||
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
#ifndef SPRITE_H
|
||||
#define SPRITE_H
|
||||
|
||||
#include <raymath.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Texture2D texture;
|
||||
Rectangle sourceRec;
|
||||
Rectangle destRec;
|
||||
Vector2 frameSize;
|
||||
Vector2 origin;
|
||||
float rotation;
|
||||
Color color;
|
||||
} Sprite;
|
||||
|
||||
void UpdateSpriteSourceRec(Sprite *sprite, const Vector2 *vector)
|
||||
{
|
||||
sprite->sourceRec.x = vector->x;
|
||||
sprite->sourceRec.y = vector->y;
|
||||
}
|
||||
|
||||
void UpdateSpriteDestRec(Sprite *sprite, const Vector2 *vector)
|
||||
{
|
||||
sprite->destRec.x = vector->x;
|
||||
sprite->destRec.y = vector->y;
|
||||
}
|
||||
|
||||
void UpdateSpriteRotation(Sprite *sprite, Vector2 *vector)
|
||||
{
|
||||
if (Vector2Length(*vector) <= 0.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
float targetRotation = (float)atan2(vector->y, vector->x) * (180.0f / PI) + 90.0f;
|
||||
sprite->rotation = Lerp(sprite->rotation, targetRotation, 0.1f);
|
||||
}
|
||||
|
||||
void RenderSprite(const Sprite *sprite)
|
||||
{
|
||||
DrawTexturePro(
|
||||
sprite->texture,
|
||||
sprite->sourceRec,
|
||||
sprite->destRec,
|
||||
sprite->origin,
|
||||
sprite->rotation,
|
||||
sprite->color);
|
||||
}
|
||||
|
||||
void PrintSprite(const Sprite *sprite)
|
||||
{
|
||||
printf("Sprite:\n");
|
||||
printf(" Sprite.texture: id = %d\n", sprite->texture.id);
|
||||
printf(" Sprite.sourceRec: x = %f, y = %f, width = %f, height = %f\n", sprite->sourceRec.x, sprite->sourceRec.y, sprite->sourceRec.width, sprite->sourceRec.height);
|
||||
printf(" Sprite.destRec: x = %f, y = %f, width = %f, height = %f\n", sprite->destRec.x, sprite->destRec.y, sprite->destRec.width, sprite->destRec.height);
|
||||
printf(" Sprite.frameSize: x = %f, y = %f\n", sprite->frameSize.x, sprite->frameSize.y);
|
||||
printf(" Sprite.origin: x = %f, y = %f\n", sprite->origin.x, sprite->origin.y);
|
||||
printf(" Sprite.rotation: %f\n", sprite->rotation);
|
||||
printf(" Sprite.color: r = %d, g = %d, b = %d, a = %d\n", sprite->color.r, sprite->color.g, sprite->color.b, sprite->color.a);
|
||||
}
|
||||
|
||||
#endif // SPRITE_H
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
#ifndef VECTOR_H
|
||||
#define VECTOR_H
|
||||
|
||||
typedef struct {
|
||||
Vector2 *data;
|
||||
size_t count;
|
||||
} Vector2Array;
|
||||
|
||||
bool IsVectorZero(Vector2 v) {
|
||||
float epsilon = 0.01f;
|
||||
return (fabs(v.x) < epsilon && fabs(v.y) < epsilon);
|
||||
}
|
||||
|
||||
Vector2 GetRandomVector(const Rectangle bounds) {
|
||||
return (Vector2){
|
||||
bounds.x + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.width,
|
||||
bounds.y + (float)GetRandomValue(0, 10000) / 10000.0f * bounds.height
|
||||
};
|
||||
}
|
||||
|
||||
#endif // VECTOR_H
|
||||
Vendored
+5932
File diff suppressed because it is too large
Load Diff
Vendored
+842
@@ -0,0 +1,842 @@
|
||||
/**********************************************************************************************
|
||||
*
|
||||
* rini v1.0 - A simple and easy-to-use config init files reader and writer
|
||||
*
|
||||
* DESCRIPTION:
|
||||
* Load and save config init properties
|
||||
*
|
||||
* FEATURES:
|
||||
* - Config files reading and writing
|
||||
* - Supported value types: int, string
|
||||
* - Support comment lines and empty lines
|
||||
* - Support custom line comment delimiter
|
||||
* - Support custom value delimiters
|
||||
* - Support value description comments
|
||||
* - Support custom description custom delimiter
|
||||
* - Support multi-word text values w/o quote delimiters
|
||||
* - Minimal C standard lib dependency (optional)
|
||||
* - Customizable maximum config values capacity
|
||||
*
|
||||
* LIMITATIONS:
|
||||
* - Config [sections] not supported
|
||||
* - Saving config file requires complete rewrite
|
||||
*
|
||||
* POSSIBLE IMPROVEMENTS:
|
||||
* - Support config [sections]
|
||||
* - Support disabled entries recognition
|
||||
*
|
||||
* CONFIGURATION:
|
||||
* #define RINI_IMPLEMENTATION
|
||||
* Generates the implementation of the library into the included file.
|
||||
* If not defined, the library is in header only mode and can be included in other headers
|
||||
* or source files without problems. But only ONE file should hold the implementation.
|
||||
*
|
||||
* #define RINI_MAX_LINE_SIZE
|
||||
* Defines the maximum size of line buffer to read from file.
|
||||
* Default value: 512 bytes (considering [key + text + desc] is 256 max size by default)
|
||||
*
|
||||
* #define RINI_MAX_KEY_SIZE
|
||||
* Defines the maximum size of value key
|
||||
* Default value: 64 bytes
|
||||
*
|
||||
* #define RINI_MAX_TEXT_SIZE
|
||||
* Defines the maximum size of value text
|
||||
* Default value: 128 bytes
|
||||
*
|
||||
* #define RINI_MAX_DESC_SIZE
|
||||
* Defines the maximum size of value description
|
||||
* Default value: 128 bytes
|
||||
*
|
||||
* #define RINI_MAX_VALUE_CAPACITY
|
||||
* Defines the maximum number of values supported
|
||||
* Default value: 128 entries support
|
||||
*
|
||||
* #define RINI_LINE_COMMENT_DELIMITER
|
||||
* Define character used to comment lines, placed at beginning of line
|
||||
* Most .ini files use semicolon ';' but '#' is also used
|
||||
* Default value: '#'
|
||||
*
|
||||
* #define RINI_LINE_SECTION_DELIMITER
|
||||
* Defines section lines start character
|
||||
* Sections loading is not supported, lines are just skipped for now
|
||||
* Default value: '['
|
||||
*
|
||||
* #define RINI_VALUE_DELIMITER
|
||||
* Defines a key value delimiter, in case it is defined.
|
||||
* Most .ini files use '=' as value delimiter but ':' is also used or just whitespace
|
||||
* Default value: ' '
|
||||
*
|
||||
* #define RINI_VALUE_QUOTATION_MARKS
|
||||
* Defines quotation marks to be used around text values
|
||||
* Text values are determined checking text with atoi(), only for integer values,
|
||||
* in case of float values they are always considered as text
|
||||
* Default value: '\"'
|
||||
*
|
||||
* #define RINI_DESCRIPTION_DELIMITER
|
||||
* Defines a property line-end comment delimiter
|
||||
* This implementation allows adding inline comments after the value.
|
||||
* Default value: '#'
|
||||
*
|
||||
* DEPENDENCIES: C standard library:
|
||||
* - stdio.h: fopen(), feof(), fgets(), fclose(), fprintf()
|
||||
* - stdlib.h: malloc(), calloc(), free()
|
||||
* - string.h: memset(), memcpy(), strcmp(), strlen()
|
||||
*
|
||||
* VERSIONS HISTORY:
|
||||
* 2.0 (26-Jan-2023) ADDED: Support custom comment lines (as config entries)
|
||||
* ADDED: Use of quotation-marks and marks customization
|
||||
* ADDED: rini_set_config_comment_line()
|
||||
* ADDED: rini_get_config_value_fallback(), with fallback return value
|
||||
* ADDED: rini_get_config_text_fallback(), with fallback return value
|
||||
* REMOVED: Config header requirement, use comments entries
|
||||
* REVIEWED: Some configuration default values, capacities
|
||||
* REVIEWED: rini_save_config(), removed parameter
|
||||
* REVIEWED: rini_save_config_from_memory(), removed parameter
|
||||
*
|
||||
* 1.0 (18-May-2023) First release, basic read/write functionality
|
||||
*
|
||||
*
|
||||
* LICENSE: zlib/libpng
|
||||
*
|
||||
* Copyright (c) 2023-2025 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
* This software is provided "as-is", without any express or implied warranty. In no event
|
||||
* will the authors be held liable for any damages arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose, including commercial
|
||||
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not claim that you
|
||||
* wrote the original software. If you use this software in a product, an acknowledgment
|
||||
* in the product documentation would be appreciated but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
|
||||
* as being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*
|
||||
**********************************************************************************************/
|
||||
|
||||
#ifndef RINI_H
|
||||
#define RINI_H
|
||||
|
||||
#define RINI_VERSION "1.0"
|
||||
|
||||
// Function specifiers in case library is build/used as a shared library (Windows)
|
||||
// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll
|
||||
#if defined(_WIN32)
|
||||
#if defined(BUILD_LIBTYPE_SHARED)
|
||||
#define RINIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll)
|
||||
#elif defined(USE_LIBTYPE_SHARED)
|
||||
#define RINIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Function specifiers definition
|
||||
#ifndef RINIAPI
|
||||
#define RINIAPI // Functions defined as 'extern' by default (implicit specifiers)
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Defines and Macros
|
||||
//----------------------------------------------------------------------------------
|
||||
// Allow custom memory allocators
|
||||
#ifndef RINI_MALLOC
|
||||
#define RINI_MALLOC(sz) malloc(sz)
|
||||
#endif
|
||||
#ifndef RINI_CALLOC
|
||||
#define RINI_CALLOC(n,sz) calloc(n,sz)
|
||||
#endif
|
||||
#ifndef RINI_FREE
|
||||
#define RINI_FREE(p) free(p)
|
||||
#endif
|
||||
|
||||
// Simple log system to avoid printf() calls if required
|
||||
// NOTE: Avoiding those calls, also avoids const strings memory usage
|
||||
#define RINI_SUPPORT_LOG_INFO
|
||||
#if defined(RINI_SUPPORT_LOG_INFO)
|
||||
#define RINI_LOG(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define RINI_LOG(...)
|
||||
#endif
|
||||
|
||||
#if !defined(RINI_MAX_LINE_SIZE)
|
||||
#define RINI_MAX_LINE_SIZE 512
|
||||
#endif
|
||||
|
||||
#if !defined(RINI_MAX_KEY_SIZE)
|
||||
#define RINI_MAX_KEY_SIZE 64
|
||||
#endif
|
||||
|
||||
#if !defined(RINI_MAX_TEXT_SIZE)
|
||||
#define RINI_MAX_TEXT_SIZE 128
|
||||
#endif
|
||||
|
||||
#if !defined(RINI_MAX_DESC_SIZE)
|
||||
#define RINI_MAX_DESC_SIZE 128
|
||||
#endif
|
||||
|
||||
#if !defined(RINI_MAX_VALUE_CAPACITY)
|
||||
#define RINI_MAX_VALUE_CAPACITY 128
|
||||
#endif
|
||||
|
||||
// Line comment delimiter (starting string)
|
||||
#if !defined(RINI_LINE_COMMENT_DELIMITER)
|
||||
#define RINI_LINE_COMMENT_DELIMITER '#'
|
||||
#endif
|
||||
|
||||
// Line section delimiter -NOT USED-
|
||||
#if !defined(RINI_LINE_SECTION_DELIMITER)
|
||||
#define RINI_LINE_SECTION_DELIMITER '['
|
||||
#endif
|
||||
|
||||
// Value delimiter, separator between key and value
|
||||
#if !defined(RINI_VALUE_DELIMITER)
|
||||
#define RINI_VALUE_DELIMITER ' '
|
||||
#endif
|
||||
|
||||
// Use quotation marks for text values
|
||||
// NOTE: Integer values do not use quotation-marks
|
||||
#define RINI_USE_TEXT_QUOTATION_MARKS 1
|
||||
// Text value quoation marks
|
||||
#if !defined(RINI_VALUE_QUOTATION_MARKS)
|
||||
#define RINI_VALUE_QUOTATION_MARKS '\"'
|
||||
#endif
|
||||
|
||||
// Description delimiter, separator between value and description
|
||||
#if !defined(RINI_DESCRIPTION_DELIMITER)
|
||||
#define RINI_DESCRIPTION_DELIMITER '#'
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Config value entry
|
||||
typedef struct {
|
||||
char key[RINI_MAX_KEY_SIZE]; // Config value key identifier
|
||||
char text[RINI_MAX_TEXT_SIZE]; // Config value text
|
||||
char desc[RINI_MAX_DESC_SIZE]; // Config value description
|
||||
} rini_config_value;
|
||||
|
||||
// Config data
|
||||
typedef struct {
|
||||
rini_config_value *values; // Config values array
|
||||
unsigned int count; // Config values count
|
||||
unsigned int capacity; // Config values capacity
|
||||
} rini_config;
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" { // Prevents name mangling of functions
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Functions declaration
|
||||
//------------------------------------------------------------------------------------
|
||||
RINIAPI rini_config rini_load_config(const char *file_name); // Load config from file (*.ini) or create a new config object (pass NULL)
|
||||
RINIAPI rini_config rini_load_config_from_memory(const char *text); // Load config from text buffer
|
||||
RINIAPI void rini_save_config(rini_config config, const char *file_name); // Save config to file, with custom header
|
||||
RINIAPI char *rini_save_config_to_memory(rini_config config); // Save config to text buffer ('\0' EOL)
|
||||
RINIAPI void rini_unload_config(rini_config *config); // Unload config data from memory
|
||||
|
||||
RINIAPI int rini_get_config_value(rini_config config, const char *key); // Get config value int for provided key, returns 0 if not found
|
||||
RINIAPI const char *rini_get_config_value_text(rini_config config, const char *key); // Get config value text for provided key
|
||||
RINIAPI const char *rini_get_config_value_description(rini_config config, const char *key); // Get config value description for provided key
|
||||
|
||||
RINIAPI int rini_get_config_value_fallback(rini_config config, const char *key, int fallback); // Get config value for provided key with default value fallback if not found or not valid
|
||||
RINIAPI const char *rini_get_config_value_text_fallback(rini_config config, const char *key, const char *fallback); // Get config value text for provided key with fallback if not found or not valid
|
||||
|
||||
RINIAPI int rini_set_config_comment_line(rini_config *config, const char *comment); // Set config comment line
|
||||
|
||||
// Set config value int/text and description for existing key or create a new entry
|
||||
// NOTE: When setting a text value, if id does not exist, a new entry is automatically created
|
||||
RINIAPI int rini_set_config_value(rini_config *config, const char *key, int value, const char *desc);
|
||||
RINIAPI int rini_set_config_value_text(rini_config *config, const char *key, const char *text, const char *desc);
|
||||
|
||||
// Set config value description for existing key
|
||||
// WARNING: Key must exist to add description, if a description exists, it is updated
|
||||
RINIAPI int rini_set_config_value_description(rini_config *config, const char *key, const char *desc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // RINI_H
|
||||
|
||||
/***********************************************************************************
|
||||
*
|
||||
* RINI IMPLEMENTATION
|
||||
*
|
||||
************************************************************************************/
|
||||
|
||||
#if defined(RINI_IMPLEMENTATION)
|
||||
|
||||
#include <stdio.h> // Required for: fopen(), feof(), fgets(), fclose(), fprintf()
|
||||
#include <stdlib.h> // Required for: malloc(), calloc(), free()
|
||||
#include <string.h> // Required for: memset(), memcpy(), strcmp(), strlen()
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Defines and macros
|
||||
//----------------------------------------------------------------------------------
|
||||
// ...
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Global variables definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// ...
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module internal functions declaration
|
||||
//----------------------------------------------------------------------------------
|
||||
static int rini_read_config_key(const char *buffer, char *key); // Get key from a buffer line containing key-value-(description)
|
||||
static int rini_read_config_value_text(const char *buffer, char *text, char *desc); // Get value text (and description) from a buffer line
|
||||
|
||||
static int rini_text_to_int(const char *text); // Convert text to int value (if possible), same as atoi()
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module functions definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Load config from file (.ini)
|
||||
rini_config rini_load_config(const char *file_name)
|
||||
{
|
||||
rini_config config = { 0 };
|
||||
unsigned int value_counter = 0;
|
||||
|
||||
// Init config data to max capacity
|
||||
config.capacity = RINI_MAX_VALUE_CAPACITY;
|
||||
config.values = (rini_config_value *)RINI_CALLOC(RINI_MAX_VALUE_CAPACITY, sizeof(rini_config_value));
|
||||
|
||||
if (file_name != NULL)
|
||||
{
|
||||
FILE *rini_file = fopen(file_name, "rt");
|
||||
|
||||
if (rini_file != NULL)
|
||||
{
|
||||
char buffer[RINI_MAX_LINE_SIZE] = { 0 }; // Buffer to read every text line
|
||||
|
||||
// First pass to count valid config lines
|
||||
while (fgets(buffer, RINI_MAX_LINE_SIZE, rini_file))
|
||||
{
|
||||
// WARNING: fgets() keeps line endings, doesn't have any special options for converting line endings,
|
||||
// but on Windows, when reading file 'rt', line endings are converted from \r\n to just \n
|
||||
|
||||
// Skip commented lines and empty lines
|
||||
// NOTE: We are also skipping sections delimiters
|
||||
if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) &&
|
||||
(buffer[0] != RINI_LINE_SECTION_DELIMITER) &&
|
||||
(buffer[0] != '\n') && (buffer[0] != '\0')) value_counter++;
|
||||
}
|
||||
|
||||
// WARNING: We can't store more values than its max capacity
|
||||
config.count = (value_counter > RINI_MAX_VALUE_CAPACITY)? RINI_MAX_VALUE_CAPACITY : value_counter;
|
||||
|
||||
if (config.count > 0)
|
||||
{
|
||||
rewind(rini_file);
|
||||
value_counter = 0;
|
||||
|
||||
// Second pass to read config data
|
||||
while (fgets(buffer, RINI_MAX_LINE_SIZE, rini_file))
|
||||
{
|
||||
// WARNING: fgets() keeps line endings, doesn't have any special options for converting line endings,
|
||||
// but on Windows, when reading file 'rt', line endings are converted from \r\n to just \n
|
||||
|
||||
// Skip commented lines and empty lines
|
||||
if ((buffer[0] != RINI_LINE_COMMENT_DELIMITER) &&
|
||||
(buffer[0] != RINI_LINE_SECTION_DELIMITER) &&
|
||||
(buffer[0] != '\n') && (buffer[0] != '\0'))
|
||||
{
|
||||
// Get key identifier string
|
||||
memset(config.values[value_counter].key, 0, RINI_MAX_KEY_SIZE);
|
||||
rini_read_config_key(buffer, config.values[value_counter].key);
|
||||
rini_read_config_value_text(buffer, config.values[value_counter].text, config.values[value_counter].desc);
|
||||
|
||||
value_counter++;
|
||||
|
||||
// Stop reading if first count reached to avoid overflow in case count == RINI_MAX_VALUE_CAPACITY
|
||||
if (value_counter >= config.count) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(rini_file);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// Load config from text buffer
|
||||
// NOTE: Comments and empty lines are ignored
|
||||
rini_config rini_load_config_from_memory(const char *text)
|
||||
{
|
||||
#define RINI_MAX_TEXT_LINES RINI_MAX_VALUE_CAPACITY*2 // Consider possible comments and empty lines
|
||||
|
||||
rini_config config = { 0 };
|
||||
unsigned int value_counter = 0;
|
||||
|
||||
// Init config data to max capacity
|
||||
config.capacity = RINI_MAX_VALUE_CAPACITY;
|
||||
config.values = (rini_config_value *)RINI_CALLOC(RINI_MAX_VALUE_CAPACITY, sizeof(rini_config_value));
|
||||
|
||||
if (text != NULL)
|
||||
{
|
||||
// Split text by line-breaks
|
||||
const char *lines[RINI_MAX_TEXT_LINES] = { 0 };
|
||||
int textSize = (int)strlen(text);
|
||||
lines[0] = text;
|
||||
int line_counter = 1;
|
||||
|
||||
for (int i = 0, k = 1; (i < textSize) && (line_counter < RINI_MAX_TEXT_LINES); i++)
|
||||
{
|
||||
if (text[i] == '\n')
|
||||
{
|
||||
lines[k] = &text[i + 1]; // WARNING: next value is valid?
|
||||
line_counter += 1;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Count possible values in lines
|
||||
for (int l = 0; l < line_counter; l++)
|
||||
{
|
||||
// Skip commented lines and empty lines
|
||||
// NOTE: We are also skipping sections delimiters
|
||||
if ((lines[l][0] != RINI_LINE_COMMENT_DELIMITER) &&
|
||||
(lines[l][0] != RINI_LINE_SECTION_DELIMITER) &&
|
||||
(lines[l][0] != '\n') && (lines[l][0] != '\0')) value_counter++;
|
||||
}
|
||||
|
||||
// WARNING: We can't store more values than its max capacity
|
||||
config.count = (value_counter > RINI_MAX_VALUE_CAPACITY)? RINI_MAX_VALUE_CAPACITY : value_counter;
|
||||
|
||||
// Process lines to get keys and values
|
||||
if (config.count > 0)
|
||||
{
|
||||
value_counter = 0;
|
||||
|
||||
// Second pass to read config data
|
||||
for (int l = 0; l < line_counter; l++)
|
||||
{
|
||||
// Skip commented lines and empty lines
|
||||
if ((lines[l][0] != RINI_LINE_COMMENT_DELIMITER) &&
|
||||
(lines[l][0] != RINI_LINE_SECTION_DELIMITER) &&
|
||||
(lines[l][0] != '\n') && (lines[l][0] != '\0'))
|
||||
{
|
||||
// Get key identifier string
|
||||
memset(config.values[value_counter].key, 0, RINI_MAX_KEY_SIZE);
|
||||
rini_read_config_key(lines[l], config.values[value_counter].key);
|
||||
rini_read_config_value_text(lines[l], config.values[value_counter].text, config.values[value_counter].desc);
|
||||
|
||||
value_counter++;
|
||||
|
||||
// Stop reading if first count reached to avoid overflow in case count == RINI_MAX_VALUE_CAPACITY
|
||||
if (value_counter >= config.count) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// Save config to file (*.ini)
|
||||
void rini_save_config(rini_config config, const char *file_name)
|
||||
{
|
||||
FILE *rini_file = fopen(file_name, "wt");
|
||||
|
||||
if (rini_file != NULL)
|
||||
{
|
||||
for (unsigned int i = 0; i < config.count; i++)
|
||||
{
|
||||
if ((config.values[i].key[0] == '\0') && (config.values[i].text[0] == RINI_LINE_COMMENT_DELIMITER))
|
||||
{
|
||||
if (config.values[i].desc[0] != '\0') fprintf(rini_file, "%c %s\n", RINI_LINE_COMMENT_DELIMITER, config.values[i].desc);
|
||||
else fprintf(rini_file, "%c\n", RINI_LINE_COMMENT_DELIMITER);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if RINI_USE_TEXT_QUOTATION_MARKS
|
||||
// If text is not an integer value, append text quotation-marks
|
||||
// TODO: Check also for float values? atof()
|
||||
bool valueIsInt = false;
|
||||
int value = atoi(config.values[i].text); // WARNING: Returns 0 if input not valid --> Check if (config.values[i].text len == 1 && config.values[i].text[0] == '0')
|
||||
if (((value == 0) && (strlen(config.values[i].text) == 1) && (config.values[i].text[0] == '0')) || (value != 0)) valueIsInt = true;
|
||||
|
||||
if (valueIsInt)
|
||||
{
|
||||
fprintf(rini_file, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER,
|
||||
config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(rini_file, "%-28s %c %c%6s%c %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER,
|
||||
RINI_VALUE_QUOTATION_MARKS, config.values[i].text, RINI_VALUE_QUOTATION_MARKS,
|
||||
RINI_DESCRIPTION_DELIMITER, config.values[i].desc);
|
||||
}
|
||||
#else
|
||||
fprintf(rini_file, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER,
|
||||
config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
fclose(rini_file);
|
||||
}
|
||||
}
|
||||
|
||||
// Save config to text buffer ('\0' EOL)
|
||||
char *rini_save_config_to_memory(rini_config config)
|
||||
{
|
||||
#define RINI_MAX_TEXT_FILE_SIZE 4096
|
||||
|
||||
// Verify required config size is smaller than memory buffer size
|
||||
// NOTE: We add 64 extra possible characters by entry line
|
||||
int requiredSize = 0;
|
||||
for (unsigned int i = 0; i < config.count; i++) requiredSize += ((int)strlen(config.values[i].key) + (int)strlen(config.values[i].text) + (int)strlen(config.values[i].desc) + 64);
|
||||
if (requiredSize > RINI_MAX_TEXT_FILE_SIZE) RINI_LOG("WARNING: Required config.ini size is bigger than max supported memory size, increase RINI_MAX_TEXT_FILE_SIZE\n");
|
||||
|
||||
// NOTE: Using a static buffer to avoid de-allocation requirement on user side
|
||||
static char text[RINI_MAX_TEXT_FILE_SIZE] = { 0 };
|
||||
memset(text, 0, RINI_MAX_TEXT_FILE_SIZE);
|
||||
int offset = 0;
|
||||
|
||||
for (unsigned int i = 0; i < config.count; i++)
|
||||
{
|
||||
if ((config.values[i].key[0] == '\0') && (config.values[i].text[0] == RINI_LINE_COMMENT_DELIMITER))
|
||||
{
|
||||
if (config.values[i].desc[0] != '\0') offset += sprintf(text + offset, "%c %s\n", RINI_LINE_COMMENT_DELIMITER, config.values[i].desc);
|
||||
else offset += sprintf(text + offset, "%c\n", RINI_LINE_COMMENT_DELIMITER);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if RINI_USE_TEXT_QUOTATION_MARKS
|
||||
// If text is not an integer value, append text quotation-marks
|
||||
// TODO: Check also for float values? atof()
|
||||
bool valueIsInt = false;
|
||||
int value = atoi(config.values[i].text); // WARNING: Returns 0 if input not valid --> Check if (config.values[i].text len == 1 && config.values[i].text[0] == '0')
|
||||
if (((value == 0) && (strlen(config.values[i].text) == 1) && (config.values[i].text[0] == '0')) || (value != 0)) valueIsInt = true;
|
||||
|
||||
if (valueIsInt)
|
||||
{
|
||||
offset += sprintf(text + offset, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER,
|
||||
config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc);
|
||||
}
|
||||
else
|
||||
{
|
||||
offset += sprintf(text + offset, "%-28s %c %c%6s%c %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER,
|
||||
RINI_VALUE_QUOTATION_MARKS, config.values[i].text, RINI_VALUE_QUOTATION_MARKS,
|
||||
RINI_DESCRIPTION_DELIMITER, config.values[i].desc);
|
||||
}
|
||||
#else
|
||||
offset += sprintf(text + offset, "%-28s %c %6s %c %s\n", config.values[i].key, RINI_VALUE_DELIMITER,
|
||||
config.values[i].text, RINI_DESCRIPTION_DELIMITER, config.values[i].desc);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// Unload config data
|
||||
void rini_unload_config(rini_config *config)
|
||||
{
|
||||
RINI_FREE(config->values);
|
||||
|
||||
config->values = NULL;
|
||||
config->count = 0;
|
||||
config->capacity = 0;
|
||||
}
|
||||
|
||||
// Get config value for provided key, returns 0 if not found or not valid
|
||||
int rini_get_config_value(rini_config config, const char *key)
|
||||
{
|
||||
int value = 0;
|
||||
|
||||
for (unsigned int i = 0; i < config.count; i++)
|
||||
{
|
||||
if (strcmp(key, config.values[i].key) == 0) // Key found
|
||||
{
|
||||
value = rini_text_to_int(config.values[i].text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Get config value for provided key with default value fallback if not found or not valid
|
||||
int rini_get_config_value_fallback(rini_config config, const char *key, int fallback)
|
||||
{
|
||||
int value = fallback;
|
||||
|
||||
for (unsigned int i = 0; i < config.count; i++)
|
||||
{
|
||||
if (strcmp(key, config.values[i].key) == 0) // Key found
|
||||
{
|
||||
// TODO: Detect if conversion fails...
|
||||
value = rini_text_to_int(config.values[i].text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Get config text for string id
|
||||
const char *rini_get_config_value_text(rini_config config, const char *key)
|
||||
{
|
||||
const char *text = NULL;
|
||||
|
||||
for (unsigned int i = 0; i < config.count; i++)
|
||||
{
|
||||
if (strcmp(key, config.values[i].key) == 0) // Key found
|
||||
{
|
||||
text = config.values[i].text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// Get config value text for provided key with fallback if not found or not valid
|
||||
RINIAPI const char *rini_get_config_value_text_fallback(rini_config config, const char *key, const char *fallback)
|
||||
{
|
||||
const char *text = fallback;
|
||||
|
||||
for (unsigned int i = 0; i < config.count; i++)
|
||||
{
|
||||
if (strcmp(key, config.values[i].key) == 0) // Key found
|
||||
{
|
||||
text = config.values[i].text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// Get config description for string id
|
||||
const char *rini_get_config_value_description(rini_config config, const char *key)
|
||||
{
|
||||
const char *desc = NULL;
|
||||
|
||||
for (unsigned int i = 0; i < config.count; i++)
|
||||
{
|
||||
if (strcmp(key, config.values[i].key) == 0) // Key found
|
||||
{
|
||||
desc = config.values[i].desc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
// Set config comment line
|
||||
int rini_set_config_comment_line(rini_config *config, const char *comment)
|
||||
{
|
||||
int result = -1;
|
||||
char text[2] = { RINI_LINE_COMMENT_DELIMITER, '\0' };
|
||||
|
||||
result = rini_set_config_value_text(config, NULL, text, comment);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set config value and description for existing key or create a new entry
|
||||
int rini_set_config_value(rini_config *config, const char *key, int value, const char *desc)
|
||||
{
|
||||
int result = -1;
|
||||
char value_text[RINI_MAX_TEXT_SIZE] = { 0 };
|
||||
|
||||
sprintf(value_text, "%i", value);
|
||||
|
||||
result = rini_set_config_value_text(config, key, value_text, desc);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set config value text and description for existing key or create a new entry
|
||||
// NOTE: When setting a text value, if id does not exist, a new entry is automatically created
|
||||
int rini_set_config_value_text(rini_config *config, const char *key, const char *text, const char *desc)
|
||||
{
|
||||
int result = -1;
|
||||
|
||||
if ((text == NULL) || (text[0] == '\0')) return result;
|
||||
|
||||
if (key != NULL)
|
||||
{
|
||||
// Try to find key and update text and description
|
||||
for (unsigned int i = 0; i < config->count; i++)
|
||||
{
|
||||
if (strcmp(key, config->values[i].key) == 0) // Key found
|
||||
{
|
||||
memset(config->values[i].text, 0, RINI_MAX_TEXT_SIZE);
|
||||
memcpy(config->values[i].text, text, strlen(text));
|
||||
|
||||
memset(config->values[i].desc, 0, RINI_MAX_DESC_SIZE);
|
||||
if (desc != NULL) memcpy(config->values[i].desc, desc, strlen(desc));
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Key not found, we add a new entry if possible
|
||||
if (result == -1)
|
||||
{
|
||||
if (config->count < config->capacity)
|
||||
{
|
||||
// NOTE: Supporting comment line entries
|
||||
if ((key == NULL) && (text[0] == RINI_LINE_COMMENT_DELIMITER))
|
||||
{
|
||||
config->values[config->count].key[0] = '\0';
|
||||
config->values[config->count].text[0] = RINI_LINE_COMMENT_DELIMITER;
|
||||
if (desc != NULL) for (int i = 0; (i < RINI_MAX_DESC_SIZE) && (desc[i] != '\0'); i++) config->values[config->count].desc[i] = desc[i];
|
||||
else config->values[config->count].desc[0] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE: We do a manual copy to avoid possible overflows on input data
|
||||
for (int i = 0; (i < RINI_MAX_KEY_SIZE) && (key[i] != '\0'); i++) config->values[config->count].key[i] = key[i];
|
||||
for (int i = 0; (i < RINI_MAX_TEXT_SIZE) && (text[i] != '\0'); i++) config->values[config->count].text[i] = text[i];
|
||||
for (int i = 0; (i < RINI_MAX_DESC_SIZE) && (desc[i] != '\0'); i++) config->values[config->count].desc[i] = desc[i];
|
||||
}
|
||||
|
||||
config->count++;
|
||||
result = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set config value description for existing key
|
||||
// WARNING: Key must exist to add description, if a description exists, it is updated
|
||||
int rini_set_config_value_description(rini_config *config, const char *key, const char *desc)
|
||||
{
|
||||
int result = 1;
|
||||
|
||||
for (unsigned int i = 0; i < config->count; i++)
|
||||
{
|
||||
if (strcmp(key, config->values[i].key) == 0) // Key found
|
||||
{
|
||||
memset(config->values[i].desc, 0, RINI_MAX_DESC_SIZE);
|
||||
if (desc != NULL) memcpy(config->values[i].desc, desc, strlen(desc));
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module internal functions declaration
|
||||
//----------------------------------------------------------------------------------
|
||||
// Get string id from a buffer line containing id-value pair
|
||||
static int rini_read_config_key(const char *buffer, char *key)
|
||||
{
|
||||
int len = 0;
|
||||
while ((buffer[len] != '\0') && (buffer[len] != ' ') && (buffer[len] != RINI_VALUE_DELIMITER)) len++; // Skip keyentifier
|
||||
|
||||
memcpy(key, buffer, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Get config string-value from a buffer line containing id-value pair
|
||||
static int rini_read_config_value_text(const char *buffer, char *text, char *desc)
|
||||
{
|
||||
char *buffer_ptr = (char *)buffer;
|
||||
|
||||
// Expected config line structure:
|
||||
// [key][spaces?][delimiter?][spaces?][quot-mark?][textValue][quot-mark?][spaces?][[;][#]description?]
|
||||
// We need to skip spaces, check for delimiter (if required), skip spaces, and get text value
|
||||
|
||||
while ((buffer_ptr[0] != '\0') && (buffer_ptr[0] != ' ')) buffer_ptr++; // Skip keyentifier
|
||||
|
||||
while ((buffer_ptr[0] != '\0') && (buffer_ptr[0] == ' ')) buffer_ptr++; // Skip line spaces before text value or delimiter
|
||||
|
||||
#if defined(RINI_VALUE_DELIMITER)
|
||||
if (buffer_ptr[0] == RINI_VALUE_DELIMITER)
|
||||
{
|
||||
buffer_ptr++; // Skip delimiter
|
||||
|
||||
while ((buffer_ptr[0] != '\0') && (buffer_ptr[0] == ' ')) buffer_ptr++; // Skip line spaces before text value
|
||||
}
|
||||
#endif
|
||||
|
||||
// Now buffer_ptr should be pointing to the start of value
|
||||
|
||||
int len = 0;
|
||||
while ((buffer_ptr[len] != '\0') &&
|
||||
(buffer_ptr[len] != '\r') &&
|
||||
(buffer_ptr[len] != '\n')) len++; // Get text-value and description length (to the end of line)
|
||||
|
||||
// Now we got the length from text-value start to end of line
|
||||
|
||||
int value_len = len;
|
||||
int desc_pos = 0;
|
||||
#if defined(RINI_DESCRIPTION_DELIMITER)
|
||||
// Scan text looking for text-value description (if used)
|
||||
for (; desc_pos < len; desc_pos++)
|
||||
{
|
||||
if (buffer_ptr[desc_pos] == RINI_DESCRIPTION_DELIMITER)
|
||||
{
|
||||
value_len = desc_pos - 1;
|
||||
while (buffer_ptr[value_len] == ' ') value_len--;
|
||||
|
||||
value_len++;
|
||||
|
||||
desc_pos++; // Skip delimiter and following spaces
|
||||
while (buffer_ptr[desc_pos] == ' ') desc_pos++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if RINI_USE_TEXT_QUOTATION_MARKS
|
||||
// Remove starting quotation-mark from text (if being used)
|
||||
if (buffer_ptr[0] == RINI_VALUE_QUOTATION_MARKS)
|
||||
{
|
||||
buffer_ptr++; desc_pos--; len--; value_len--;
|
||||
|
||||
// Remove ending quotation-mark from text (if being used)
|
||||
if (buffer_ptr[value_len - 1] == RINI_VALUE_QUOTATION_MARKS) { value_len--; }
|
||||
}
|
||||
#endif
|
||||
|
||||
// Clear text buffers to be updated
|
||||
memset(text, 0, RINI_MAX_TEXT_SIZE);
|
||||
memset(desc, 0, RINI_MAX_DESC_SIZE);
|
||||
|
||||
// Copy value-text and description to provided pointers
|
||||
memcpy(text, buffer_ptr, value_len);
|
||||
memcpy(desc, buffer_ptr + desc_pos, ((len - desc_pos) > (RINI_MAX_DESC_SIZE - 1))? (RINI_MAX_DESC_SIZE - 1) : (len - desc_pos));
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Convert text to int value (if possible), same as atoi()
|
||||
static int rini_text_to_int(const char *text)
|
||||
{
|
||||
int value = 0;
|
||||
int sign = 1;
|
||||
|
||||
if ((text[0] == '+') || (text[0] == '-'))
|
||||
{
|
||||
if (text[0] == '-') sign = -1;
|
||||
text++;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10 + (int)(text[i] - '0');
|
||||
|
||||
//if (strlen(text) != i) // Text starts with numbers but contains some text after that...
|
||||
|
||||
return value*sign;
|
||||
}
|
||||
|
||||
#endif // RINI_IMPLEMENTATION
|
||||
Reference in New Issue
Block a user