Skip to content

Instantly share code, notes, and snippets.

@7etsuo
Created August 1, 2025 12:38
Show Gist options
  • Save 7etsuo/b50137e20fed9778b4503f23ad1417fa to your computer and use it in GitHub Desktop.
Save 7etsuo/b50137e20fed9778b4503f23ad1417fa to your computer and use it in GitHub Desktop.
echos.c
#include <math.h>
#include <raylib.h>
#include <raymath.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 720
#define WORLD_SIZE 100
#define CHUNK_SIZE 16
#define MAX_ECHO_ENERGY 10
#define ECHO_PULSE_RADIUS 30.0f
#define ECHO_PULSE_DURATION 3.0f
#define VISIBILITY_RADIUS 5.0f
#define MAX_ENEMIES 50
#define MAX_CRYSTALS 100
#define MAX_ANCHORED_AREAS 100
#define MAX_SOUND_PARTICLES 200
#define MAX_ECHO_BRIDGES 20
// Enumerations
typedef enum
{
TOOL_NONE,
TOOL_GRAVITY,
TOOL_TIME_DILATION,
TOOL_ECHO_BRIDGE
} ResonanceTool;
typedef enum
{
ENEMY_ANGER,
ENEMY_FEAR,
ENEMY_CURIOSITY
} EnemyType;
typedef enum
{
VIEW_FIRST_PERSON,
VIEW_ISOMETRIC
} ViewMode;
// Structures
typedef struct
{
Vector3 position;
float radius;
float timeLeft;
float strength;
} EchoPulse;
typedef struct
{
Vector3 position;
bool collected;
float glowTimer;
} EchoCrystal;
typedef struct
{
Vector3 position;
float radius;
bool active;
float visibility;
} AnchoredArea;
typedef struct
{
Vector3 position;
Vector3 velocity;
EnemyType type;
bool active;
float speed;
float detectRadius;
float behavior;
} Enemy;
typedef struct
{
float heights[WORLD_SIZE][WORLD_SIZE];
bool revealed[WORLD_SIZE][WORLD_SIZE];
float visibility[WORLD_SIZE][WORLD_SIZE];
bool anchored[WORLD_SIZE][WORLD_SIZE];
float memory[WORLD_SIZE][WORLD_SIZE];
} World;
typedef struct
{
Vector3 position;
float slowFactor;
float radius;
float timeLeft;
} TimeDilationZone;
typedef struct
{
Vector3 start;
Vector3 end;
bool active;
float width;
} EchoBridge;
typedef struct
{
Vector3 position;
Vector3 velocity;
float life;
Color color;
} SoundParticle;
typedef struct
{
int aggressiveActions;
int passiveActions;
float noiseLevel;
float lastActionTime;
} PlayerBehavior;
// Global variables
typedef struct
{
// Core systems
World world;
Camera camera;
Camera3D isometricCamera;
ViewMode cameraMode;
Vector3 playerPosition;
float playerRotation;
// Echo system
int echoEnergy;
EchoPulse currentPulse;
bool pulsing;
float pulseTimer;
float cameraTransitionTimer;
// Game objects
EchoCrystal crystals[MAX_CRYSTALS];
Enemy enemies[MAX_ENEMIES];
AnchoredArea anchoredAreas[MAX_ANCHORED_AREAS];
int anchoredCount;
// Tools
ResonanceTool selectedTool;
TimeDilationZone timeDilationZones[10];
EchoBridge echoBridges[MAX_ECHO_BRIDGES];
int bridgeCount;
// Effects
SoundParticle soundParticles[MAX_SOUND_PARTICLES];
// Player behavior tracking
PlayerBehavior behavior;
// Game state
bool gameOver;
float deltaTime;
} GameState;
// Function prototypes
void InitGame (GameState *game);
void UpdateGame (GameState *game);
void DrawGame (GameState *game);
void GenerateWorld (GameState *game);
void UpdatePlayerMovement (GameState *game);
void UpdateCameraSystem (GameState *game);
void HandleEchoPulse (GameState *game);
void UpdateEchoPulse (GameState *game);
void DrawWorld (GameState *game);
void DrawEchoPulseEffect (GameState *game);
void UpdateWorldVisibility (GameState *game);
void HandleWorldAnchoring (GameState *game);
void UpdateEnemies (GameState *game);
void SpawnEnemy (GameState *game, EnemyType type);
void UpdateCrystals (GameState *game);
void DrawCrystals (GameState *game);
void DrawEnemies (GameState *game);
void HandleResonanceTools (GameState *game);
void UpdateTimeDilationZones (GameState *game);
void DrawTimeDilationZones (GameState *game);
void DrawEchoBridges (GameState *game);
void UpdateSoundParticles (GameState *game);
void DrawSoundParticles (GameState *game);
void CreateSoundParticle (GameState *game, Vector3 position, Color color);
void UpdateWorldMemory (GameState *game);
void DrawUI (GameState *game);
float GetTerrainHeight (GameState *game, float x, float z);
bool IsPositionRevealed (GameState *game, float x, float z);
bool IsPositionAnchored (GameState *game, float x, float z);
void ApplyGravityManipulation (GameState *game, Vector3 position,
float strength);
void CreateTimeDilationZone (GameState *game, Vector3 position);
void CreateEchoBridge (GameState *game, Vector3 start, Vector3 end);
// Main function
int
main (void)
{
// WSL2 compatibility - disable MSAA and vsync
SetConfigFlags (FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT);
InitWindow (1280, 720, "Echoes of the Unseen");
// Disable VSync for software renderer
SetTargetFPS (60);
GameState game = { 0 };
InitGame (&game);
// WSL window visibility hack - draw a few frames to ensure window appears
for (int i = 0; i < 3; i++)
{
BeginDrawing ();
ClearBackground (BLACK);
DrawText ("Loading...", SCREEN_WIDTH / 2 - 50, SCREEN_HEIGHT / 2, 20,
WHITE);
EndDrawing ();
}
// Main game loop
int frameCount = 0;
while (!WindowShouldClose () && !game.gameOver)
{
UpdateGame (&game);
DrawGame (&game);
// Debug output every 60 frames (1 second at 60 FPS)
frameCount++;
if (frameCount % 60 == 0)
{
printf ("Frame %d - Player pos: %.2f, %.2f, %.2f\n", frameCount,
game.playerPosition.x, game.playerPosition.y,
game.playerPosition.z);
}
}
CloseWindow ();
return 0;
}
// Initialize game
void
InitGame (GameState *game)
{
srand (time (NULL));
// Initialize player
game->playerPosition
= (Vector3){ WORLD_SIZE / 2.0f, 2.0f, WORLD_SIZE / 2.0f };
game->playerRotation = 0.0f;
game->echoEnergy = MAX_ECHO_ENERGY;
game->selectedTool = TOOL_NONE;
// Initialize cameras
game->cameraMode = VIEW_FIRST_PERSON;
game->camera.position = game->playerPosition;
game->camera.target = Vector3Add (game->playerPosition, (Vector3){ 0, 0, 1 });
game->camera.up = (Vector3){ 0, 1, 0 };
game->camera.fovy = 60.0f;
game->camera.projection = CAMERA_PERSPECTIVE;
// Isometric camera setup
game->isometricCamera.position = (Vector3){ 50, 50, 50 };
game->isometricCamera.target = (Vector3){ WORLD_SIZE / 2, 0, WORLD_SIZE / 2 };
game->isometricCamera.up = (Vector3){ 0, 1, 0 };
game->isometricCamera.fovy = 45.0f;
game->isometricCamera.projection = CAMERA_ORTHOGRAPHIC;
// Generate world
GenerateWorld (game);
// Initialize arrays
game->anchoredCount = 0;
game->bridgeCount = 0;
for (int i = 0; i < 10; i++)
{
game->timeDilationZones[i].timeLeft = 0;
}
for (int i = 0; i < MAX_ENEMIES; i++)
{
game->enemies[i].active = false;
}
// Initialize crystals
for (int i = 0; i < MAX_CRYSTALS; i++)
{
game->crystals[i].position
= (Vector3){ GetRandomValue (10, WORLD_SIZE - 10), 0,
GetRandomValue (10, WORLD_SIZE - 10) };
game->crystals[i].position.y
= GetTerrainHeight (game, game->crystals[i].position.x,
game->crystals[i].position.z)
+ 0.5f;
game->crystals[i].collected = false;
game->crystals[i].glowTimer = GetRandomValue (0, 360) * DEG2RAD;
}
// Initialize visibility around player
int px = (int)game->playerPosition.x;
int pz = (int)game->playerPosition.z;
for (int x = px - VISIBILITY_RADIUS; x <= px + VISIBILITY_RADIUS; x++)
{
for (int z = pz - VISIBILITY_RADIUS; z <= pz + VISIBILITY_RADIUS; z++)
{
if (x >= 0 && x < WORLD_SIZE && z >= 0 && z < WORLD_SIZE)
{
float dist
= Vector2Distance ((Vector2){ x, z }, (Vector2){ px, pz });
if (dist <= VISIBILITY_RADIUS)
{
game->world.visibility[x][z]
= 1.0f - (dist / VISIBILITY_RADIUS);
game->world.revealed[x][z] = true;
}
}
}
}
}
// Generate procedural world
void
GenerateWorld (GameState *game)
{
// Simple perlin-noise-like terrain generation
for (int x = 0; x < WORLD_SIZE; x++)
{
for (int z = 0; z < WORLD_SIZE; z++)
{
float height = 0;
float amplitude = 5.0f;
float frequency = 0.05f;
for (int i = 0; i < 4; i++)
{
height += amplitude * sinf (x * frequency) * cosf (z * frequency);
amplitude *= 0.5f;
frequency *= 2.0f;
}
game->world.heights[x][z] = height;
game->world.revealed[x][z] = false;
game->world.visibility[x][z] = 0.0f;
game->world.anchored[x][z] = false;
game->world.memory[x][z] = 0.0f;
}
}
}
// Update game logic
void
UpdateGame (GameState *game)
{
game->deltaTime = GetFrameTime ();
// Handle window resize
if (IsWindowResized ())
{
// Update camera aspect ratio
game->camera.fovy = 60.0f;
game->isometricCamera.fovy = 45.0f;
}
UpdatePlayerMovement (game);
UpdateCameraSystem (game);
HandleEchoPulse (game);
UpdateEchoPulse (game);
UpdateWorldVisibility (game);
HandleWorldAnchoring (game);
HandleResonanceTools (game);
UpdateEnemies (game);
UpdateCrystals (game);
UpdateTimeDilationZones (game);
UpdateSoundParticles (game);
UpdateWorldMemory (game);
}
// Update player movement
void
UpdatePlayerMovement (GameState *game)
{
// Toggle fullscreen with F11 (WSLg workaround)
if (IsKeyPressed (KEY_F11))
{
ToggleFullscreen ();
}
if (game->cameraMode == VIEW_ISOMETRIC)
return;
float moveSpeed = 5.0f * game->deltaTime;
float rotSpeed = 2.0f * game->deltaTime;
// Rotation
if (IsKeyDown (KEY_A))
game->playerRotation -= rotSpeed;
if (IsKeyDown (KEY_D))
game->playerRotation += rotSpeed;
// Movement
Vector3 forward
= { sinf (game->playerRotation), 0, cosf (game->playerRotation) };
Vector3 right
= { cosf (game->playerRotation), 0, -sinf (game->playerRotation) };
Vector3 movement = { 0, 0, 0 };
if (IsKeyDown (KEY_W))
movement = Vector3Add (movement, forward);
if (IsKeyDown (KEY_S))
movement = Vector3Subtract (movement, forward);
if (IsKeyDown (KEY_Q))
movement = Vector3Subtract (movement, right);
if (IsKeyDown (KEY_E))
movement = Vector3Add (movement, right);
if (Vector3Length (movement) > 0)
{
movement = Vector3Normalize (movement);
Vector3 newPos = Vector3Add (game->playerPosition,
Vector3Scale (movement, moveSpeed));
// Boundary check
if (newPos.x >= 0 && newPos.x < WORLD_SIZE && newPos.z >= 0
&& newPos.z < WORLD_SIZE)
{
newPos.y = GetTerrainHeight (game, newPos.x, newPos.z) + 1.5f;
game->playerPosition = newPos;
}
}
}
// Update camera system
void
UpdateCameraSystem (GameState *game)
{
if (game->cameraTransitionTimer > 0)
{
game->cameraTransitionTimer -= game->deltaTime;
if (game->cameraTransitionTimer <= 0)
{
game->cameraMode = VIEW_FIRST_PERSON;
}
}
if (game->cameraMode == VIEW_FIRST_PERSON)
{
game->camera.position = game->playerPosition;
game->camera.target = Vector3Add (
game->playerPosition, (Vector3){ sinf (game->playerRotation), 0,
cosf (game->playerRotation) });
}
else
{
// Update isometric camera to follow pulse
if (game->pulsing)
{
game->isometricCamera.target = game->playerPosition;
float camDist = 30.0f + game->currentPulse.radius;
game->isometricCamera.position = Vector3Add (
game->playerPosition, (Vector3){ camDist, camDist, camDist });
}
}
}
// Handle echo pulse
void
HandleEchoPulse (GameState *game)
{
if (IsKeyPressed (KEY_SPACE) && game->echoEnergy > 0 && !game->pulsing)
{
game->pulsing = true;
game->echoEnergy--;
game->currentPulse.position = game->playerPosition;
game->currentPulse.radius = 0;
game->currentPulse.timeLeft = ECHO_PULSE_DURATION;
game->currentPulse.strength = 1.0f;
game->pulseTimer = 0;
game->cameraMode = VIEW_ISOMETRIC;
game->cameraTransitionTimer = ECHO_PULSE_DURATION;
// Track player behavior
game->behavior.noiseLevel += 2.0f;
game->behavior.aggressiveActions++;
// Create sound particles
for (int i = 0; i < 20; i++)
{
CreateSoundParticle (game, game->currentPulse.position, SKYBLUE);
}
}
}
// Update echo pulse
void
UpdateEchoPulse (GameState *game)
{
if (!game->pulsing)
return;
game->pulseTimer += game->deltaTime;
game->currentPulse.radius = game->pulseTimer * 20.0f;
game->currentPulse.timeLeft -= game->deltaTime;
game->currentPulse.strength
= game->currentPulse.timeLeft / ECHO_PULSE_DURATION;
if (game->currentPulse.timeLeft <= 0)
{
game->pulsing = false;
}
// Reveal terrain hit by pulse
if (game->pulsing)
{
for (int x = 0; x < WORLD_SIZE; x++)
{
for (int z = 0; z < WORLD_SIZE; z++)
{
float dist = Vector2Distance (
(Vector2){ x, z },
(Vector2){ game->currentPulse.position.x,
game->currentPulse.position.z });
if (dist <= game->currentPulse.radius
&& dist >= game->currentPulse.radius - 2.0f)
{
game->world.revealed[x][z] = true;
game->world.visibility[x][z]
= fmaxf (game->world.visibility[x][z],
game->currentPulse.strength);
}
}
}
}
}
// Update world visibility
void
UpdateWorldVisibility (GameState *game)
{
// Fade visibility over time
for (int x = 0; x < WORLD_SIZE; x++)
{
for (int z = 0; z < WORLD_SIZE; z++)
{
if (!game->world.anchored[x][z] && game->world.visibility[x][z] > 0)
{
game->world.visibility[x][z] -= 0.1f * game->deltaTime;
if (game->world.visibility[x][z] < 0)
{
game->world.visibility[x][z] = 0;
}
}
}
}
// Maintain visibility around player
int px = (int)game->playerPosition.x;
int pz = (int)game->playerPosition.z;
int radius = (int)VISIBILITY_RADIUS;
for (int x = px - radius; x <= px + radius; x++)
{
for (int z = pz - radius; z <= pz + radius; z++)
{
if (x >= 0 && x < WORLD_SIZE && z >= 0 && z < WORLD_SIZE)
{
float dist
= Vector2Distance ((Vector2){ x, z }, (Vector2){ px, pz });
if (dist <= VISIBILITY_RADIUS)
{
float vis = 1.0f - (dist / VISIBILITY_RADIUS);
game->world.visibility[x][z]
= fmaxf (game->world.visibility[x][z], vis);
game->world.revealed[x][z] = true;
}
}
}
}
}
// Handle world anchoring
void
HandleWorldAnchoring (GameState *game)
{
if (IsKeyPressed (KEY_F) && game->echoEnergy > 0)
{
int px = (int)game->playerPosition.x;
int pz = (int)game->playerPosition.z;
// Anchor area around player
for (int x = px - 5; x <= px + 5; x++)
{
for (int z = pz - 5; z <= pz + 5; z++)
{
if (x >= 0 && x < WORLD_SIZE && z >= 0 && z < WORLD_SIZE)
{
float dist = Vector2Distance ((Vector2){ x, z },
(Vector2){ px, pz });
if (dist <= 5 && game->world.revealed[x][z])
{
game->world.anchored[x][z] = true;
game->world.visibility[x][z] = 1.0f;
}
}
}
}
game->echoEnergy--;
game->behavior.aggressiveActions++;
// Add anchored area
if (game->anchoredCount < MAX_ANCHORED_AREAS)
{
game->anchoredAreas[game->anchoredCount].position
= game->playerPosition;
game->anchoredAreas[game->anchoredCount].radius = 5.0f;
game->anchoredAreas[game->anchoredCount].active = true;
game->anchoredCount++;
}
// Create effect
for (int i = 0; i < 10; i++)
{
CreateSoundParticle (game, game->playerPosition, GOLD);
}
}
}
// Update enemies
void
UpdateEnemies (GameState *game)
{
// Spawn enemies based on player behavior
if (game->behavior.aggressiveActions > 5 && GetRandomValue (0, 100) < 2)
{
SpawnEnemy (game, ENEMY_ANGER);
}
if (game->behavior.noiseLevel < 1.0f && GetRandomValue (0, 100) < 1)
{
SpawnEnemy (game, ENEMY_FEAR);
}
// Update existing enemies
for (int i = 0; i < MAX_ENEMIES; i++)
{
if (!game->enemies[i].active)
continue;
Enemy *enemy = &game->enemies[i];
// Only update if enemy is in revealed area
if (!IsPositionRevealed (game, enemy->position.x, enemy->position.z))
continue;
// Check for time dilation
float timeFactor = 1.0f;
for (int j = 0; j < 10; j++)
{
if (game->timeDilationZones[j].timeLeft > 0)
{
float dist = Vector3Distance (
enemy->position, game->timeDilationZones[j].position);
if (dist < game->timeDilationZones[j].radius)
{
timeFactor = game->timeDilationZones[j].slowFactor;
break;
}
}
}
// Enemy AI based on type
switch (enemy->type)
{
case ENEMY_ANGER:
// Aggressive - chase player
if (Vector3Distance (enemy->position, game->playerPosition)
< enemy->detectRadius)
{
Vector3 dir = Vector3Normalize (
Vector3Subtract (game->playerPosition, enemy->position));
enemy->velocity = Vector3Scale (dir, enemy->speed * timeFactor);
}
break;
case ENEMY_FEAR:
// Elusive - run from player
if (Vector3Distance (enemy->position, game->playerPosition)
< enemy->detectRadius)
{
Vector3 dir = Vector3Normalize (
Vector3Subtract (enemy->position, game->playerPosition));
enemy->velocity = Vector3Scale (dir, enemy->speed * timeFactor);
}
break;
case ENEMY_CURIOSITY:
// Curious - circle player
if (Vector3Distance (enemy->position, game->playerPosition)
< enemy->detectRadius)
{
Vector3 toPlayer
= Vector3Subtract (game->playerPosition, enemy->position);
Vector3 perpendicular = { -toPlayer.z, 0, toPlayer.x };
perpendicular = Vector3Normalize (perpendicular);
enemy->velocity
= Vector3Scale (perpendicular, enemy->speed * timeFactor);
}
break;
}
// Update position
Vector3 newPos = Vector3Add (
enemy->position, Vector3Scale (enemy->velocity, game->deltaTime));
// Bounds check
if (newPos.x >= 0 && newPos.x < WORLD_SIZE && newPos.z >= 0
&& newPos.z < WORLD_SIZE)
{
enemy->position = newPos;
enemy->position.y
= GetTerrainHeight (game, enemy->position.x, enemy->position.z)
+ 1.0f;
}
// Check collision with player
if (Vector3Distance (enemy->position, game->playerPosition) < 1.0f)
{
game->echoEnergy = fmaxf (0, game->echoEnergy - 2);
}
}
// Decay behavior values
game->behavior.noiseLevel *= 0.99f;
}
// Spawn enemy
void
SpawnEnemy (GameState *game, EnemyType type)
{
for (int i = 0; i < MAX_ENEMIES; i++)
{
if (!game->enemies[i].active)
{
Enemy *enemy = &game->enemies[i];
enemy->active = true;
enemy->type = type;
// Spawn at edge of revealed area
float angle = GetRandomValue (0, 360) * DEG2RAD;
float dist = ECHO_PULSE_RADIUS + GetRandomValue (5, 10);
enemy->position = Vector3Add (
game->playerPosition,
(Vector3){ cosf (angle) * dist, 0, sinf (angle) * dist });
// Check bounds before spawning
if (enemy->position.x < 0 || enemy->position.x >= WORLD_SIZE
|| enemy->position.z < 0 || enemy->position.z >= WORLD_SIZE)
{
enemy->active = false;
continue;
}
enemy->position.y
= GetTerrainHeight (game, enemy->position.x, enemy->position.z)
+ 1.0f;
// Set properties based on type
switch (type)
{
case ENEMY_ANGER:
enemy->speed = 4.0f;
enemy->detectRadius = 15.0f;
break;
case ENEMY_FEAR:
enemy->speed = 6.0f;
enemy->detectRadius = 20.0f;
break;
case ENEMY_CURIOSITY:
enemy->speed = 3.0f;
enemy->detectRadius = 10.0f;
break;
}
enemy->velocity = Vector3Zero ();
break;
}
}
}
// Update crystals
void
UpdateCrystals (GameState *game)
{
for (int i = 0; i < MAX_CRYSTALS; i++)
{
if (game->crystals[i].collected)
continue;
game->crystals[i].glowTimer += game->deltaTime * 2.0f;
// Check collection
if (Vector3Distance (game->crystals[i].position, game->playerPosition)
< 1.5f)
{
game->crystals[i].collected = true;
game->echoEnergy = fminf (game->echoEnergy + 1, MAX_ECHO_ENERGY);
// Create collection effect
for (int j = 0; j < 10; j++)
{
CreateSoundParticle (game, game->crystals[i].position, SKYBLUE);
}
}
}
}
// Handle resonance tools
void
HandleResonanceTools (GameState *game)
{
// Tool selection
if (IsKeyPressed (KEY_ONE))
game->selectedTool = TOOL_GRAVITY;
if (IsKeyPressed (KEY_TWO))
game->selectedTool = TOOL_TIME_DILATION;
if (IsKeyPressed (KEY_THREE))
game->selectedTool = TOOL_ECHO_BRIDGE;
// Tool usage
if (IsMouseButtonPressed (MOUSE_LEFT_BUTTON) && game->echoEnergy > 0)
{
Ray ray = GetMouseRay (GetMousePosition (),
game->cameraMode == VIEW_FIRST_PERSON
? game->camera
: game->isometricCamera);
// Simple ground intersection
if (ray.direction.y < 0)
{
float t = -ray.position.y / ray.direction.y;
Vector3 hitPoint
= Vector3Add (ray.position, Vector3Scale (ray.direction, t));
switch (game->selectedTool)
{
case TOOL_GRAVITY:
ApplyGravityManipulation (
game, hitPoint, IsKeyDown (KEY_LEFT_SHIFT) ? -1.0f : 1.0f);
game->echoEnergy--;
game->behavior.aggressiveActions++;
break;
case TOOL_TIME_DILATION:
CreateTimeDilationZone (game, hitPoint);
game->echoEnergy--;
break;
case TOOL_ECHO_BRIDGE:
{
static Vector3 bridgeStart = { 0 };
static bool placingBridge = false;
if (!placingBridge)
{
bridgeStart = hitPoint;
placingBridge = true;
}
else
{
CreateEchoBridge (game, bridgeStart, hitPoint);
placingBridge = false;
game->echoEnergy--;
}
break;
}
default:
break;
}
}
}
}
// Apply gravity manipulation
void
ApplyGravityManipulation (GameState *game, Vector3 position, float strength)
{
int cx = (int)position.x;
int cz = (int)position.z;
float radius = 5.0f;
for (int x = cx - radius; x <= cx + radius; x++)
{
for (int z = cz - radius; z <= cz + radius; z++)
{
if (x >= 0 && x < WORLD_SIZE && z >= 0 && z < WORLD_SIZE)
{
float dist
= Vector2Distance ((Vector2){ x, z }, (Vector2){ cx, cz });
if (dist <= radius)
{
float factor = 1.0f - (dist / radius);
game->world.heights[x][z] += strength * factor * 2.0f;
game->world.memory[x][z] += fabsf (strength) * 0.5f;
}
}
}
}
// Create effect
for (int i = 0; i < 20; i++)
{
CreateSoundParticle (game, position, strength > 0 ? GREEN : RED);
}
}
// Create time dilation zone
void
CreateTimeDilationZone (GameState *game, Vector3 position)
{
for (int i = 0; i < 10; i++)
{
if (game->timeDilationZones[i].timeLeft <= 0)
{
game->timeDilationZones[i].position = position;
game->timeDilationZones[i].radius = 8.0f;
game->timeDilationZones[i].slowFactor = 0.3f;
game->timeDilationZones[i].timeLeft = 10.0f;
// Create effect
for (int j = 0; j < 15; j++)
{
CreateSoundParticle (game, position, PURPLE);
}
break;
}
}
}
// Create echo bridge
void
CreateEchoBridge (GameState *game, Vector3 start, Vector3 end)
{
if (game->bridgeCount < MAX_ECHO_BRIDGES)
{
game->echoBridges[game->bridgeCount].start = start;
game->echoBridges[game->bridgeCount].end = end;
game->echoBridges[game->bridgeCount].active = true;
game->echoBridges[game->bridgeCount].width = 2.0f;
game->bridgeCount++;
// Reveal and anchor bridge path
Vector3 dir = Vector3Normalize (Vector3Subtract (end, start));
float length = Vector3Distance (start, end);
for (float t = 0; t < length; t += 0.5f)
{
Vector3 point = Vector3Add (start, Vector3Scale (dir, t));
int x = (int)point.x;
int z = (int)point.z;
if (x >= 0 && x < WORLD_SIZE && z >= 0 && z < WORLD_SIZE)
{
game->world.revealed[x][z] = true;
game->world.anchored[x][z] = true;
game->world.visibility[x][z] = 1.0f;
}
}
// Create effect
for (int i = 0; i < 30; i++)
{
float t = (float)i / 30.0f;
Vector3 point = Vector3Lerp (start, end, t);
CreateSoundParticle (game, point, YELLOW);
}
}
}
// Update time dilation zones
void
UpdateTimeDilationZones (GameState *game)
{
for (int i = 0; i < 10; i++)
{
if (game->timeDilationZones[i].timeLeft > 0)
{
game->timeDilationZones[i].timeLeft -= game->deltaTime;
}
}
}
// Update sound particles
void
UpdateSoundParticles (GameState *game)
{
for (int i = 0; i < MAX_SOUND_PARTICLES; i++)
{
if (game->soundParticles[i].life > 0)
{
game->soundParticles[i].life -= game->deltaTime;
game->soundParticles[i].position = Vector3Add (
game->soundParticles[i].position,
Vector3Scale (game->soundParticles[i].velocity, game->deltaTime));
game->soundParticles[i].velocity.y -= 2.0f * game->deltaTime;
}
}
}
// Create sound particle
void
CreateSoundParticle (GameState *game, Vector3 position, Color color)
{
for (int i = 0; i < MAX_SOUND_PARTICLES; i++)
{
if (game->soundParticles[i].life <= 0)
{
game->soundParticles[i].position = position;
game->soundParticles[i].velocity
= (Vector3){ (float)(GetRandomValue (-100, 100)) / 50.0f,
(float)(GetRandomValue (100, 300)) / 100.0f,
(float)(GetRandomValue (-100, 100)) / 50.0f };
game->soundParticles[i].life = 1.0f;
game->soundParticles[i].color = color;
break;
}
}
}
// Update world memory
void
UpdateWorldMemory (GameState *game)
{
// Check for instability from overuse
for (int x = 0; x < WORLD_SIZE; x++)
{
for (int z = 0; z < WORLD_SIZE; z++)
{
if (game->world.memory[x][z] > 5.0f)
{
// Terrain instability
if (GetRandomValue (0, 100) < 1)
{
game->world.heights[x][z]
+= (float)(GetRandomValue (-10, 10)) / 10.0f;
// Spawn anger enemy
if (GetRandomValue (0, 100) < 10)
{
SpawnEnemy (game, ENEMY_ANGER);
}
}
}
// Decay memory slowly
game->world.memory[x][z] *= 0.999f;
}
}
}
// Get terrain height at position
float
GetTerrainHeight (GameState *game, float x, float z)
{
int ix = (int)x;
int iz = (int)z;
if (ix < 0 || ix >= WORLD_SIZE - 1 || iz < 0 || iz >= WORLD_SIZE - 1)
{
return 0;
}
// Bilinear interpolation
float fx = x - ix;
float fz = z - iz;
float h00 = game->world.heights[ix][iz];
float h10 = game->world.heights[ix + 1][iz];
float h01 = game->world.heights[ix][iz + 1];
float h11 = game->world.heights[ix + 1][iz + 1];
float h0 = h00 * (1 - fx) + h10 * fx;
float h1 = h01 * (1 - fx) + h11 * fx;
return h0 * (1 - fz) + h1 * fz;
}
// Check if position is revealed
bool
IsPositionRevealed (GameState *game, float x, float z)
{
int ix = (int)x;
int iz = (int)z;
if (ix < 0 || ix >= WORLD_SIZE || iz < 0 || iz >= WORLD_SIZE)
{
return false;
}
return game->world.revealed[ix][iz];
}
// Check if position is anchored
bool
IsPositionAnchored (GameState *game, float x, float z)
{
int ix = (int)x;
int iz = (int)z;
if (ix < 0 || ix >= WORLD_SIZE || iz < 0 || iz >= WORLD_SIZE)
{
return false;
}
return game->world.anchored[ix][iz];
}
// Draw game
void
DrawGame (GameState *game)
{
BeginDrawing ();
ClearBackground (BLACK);
// Draw 3D scene
BeginMode3D (game->cameraMode == VIEW_FIRST_PERSON ? game->camera
: game->isometricCamera);
DrawWorld (game);
DrawCrystals (game);
DrawEnemies (game);
DrawTimeDilationZones (game);
DrawEchoBridges (game);
DrawSoundParticles (game);
if (game->pulsing)
{
DrawEchoPulseEffect (game);
}
EndMode3D ();
// Draw UI on top
DrawUI (game);
// Draw starting hint if player hasn't used echo pulse yet
static int firstPulseUsed = 0;
if (!firstPulseUsed && game->echoEnergy == MAX_ECHO_ENERGY)
{
DrawText ("Press SPACE to reveal the world with Echo Pulse!",
SCREEN_WIDTH / 2 - 250, SCREEN_HEIGHT / 2, 25, WHITE);
if (game->pulsing)
firstPulseUsed = 1;
}
EndDrawing ();
}
// Draw world
void
DrawWorld (GameState *game)
{
for (int x = 0; x < WORLD_SIZE - 1; x++)
{
for (int z = 0; z < WORLD_SIZE - 1; z++)
{
if (game->world.visibility[x][z] <= 0)
continue;
float vis = game->world.visibility[x][z];
Color color;
if (game->world.anchored[x][z])
{
color = ColorAlpha (GOLD, vis);
}
else if (game->pulsing && game->cameraMode == VIEW_ISOMETRIC)
{
// Wireframe during pulse
color = ColorAlpha (SKYBLUE, vis * 0.5f);
}
else
{
// Normal terrain color
float height = game->world.heights[x][z];
int g = 50 + (int)(height * 10);
if (g < 0)
g = 0;
if (g > 255)
g = 255;
color = ColorAlpha ((Color){ g / 2, g, g / 3, 255 }, vis);
}
// Draw terrain quad
Vector3 v1 = { x, game->world.heights[x][z], z };
Vector3 v2 = { x + 1, game->world.heights[x + 1][z], z };
Vector3 v3 = { x + 1, game->world.heights[x + 1][z + 1], z + 1 };
Vector3 v4 = { x, game->world.heights[x][z + 1], z + 1 };
if (game->pulsing && game->cameraMode == VIEW_ISOMETRIC)
{
// Draw wireframe
DrawLine3D (v1, v2, color);
DrawLine3D (v2, v3, color);
DrawLine3D (v3, v4, color);
DrawLine3D (v4, v1, color);
}
else
{
// Draw solid
DrawTriangle3D (v1, v2, v3, color);
DrawTriangle3D (v1, v3, v4, color);
}
}
}
}
// Draw echo pulse effect
void
DrawEchoPulseEffect (GameState *game)
{
int segments = 32;
float angleStep = 2 * PI / segments;
for (int i = 0; i < segments; i++)
{
float angle1 = i * angleStep;
float angle2 = (i + 1) * angleStep;
Vector3 p1 = { game->currentPulse.position.x
+ cosf (angle1) * game->currentPulse.radius,
game->currentPulse.position.y,
game->currentPulse.position.z
+ sinf (angle1) * game->currentPulse.radius };
Vector3 p2 = { game->currentPulse.position.x
+ cosf (angle2) * game->currentPulse.radius,
game->currentPulse.position.y,
game->currentPulse.position.z
+ sinf (angle2) * game->currentPulse.radius };
p1.y = GetTerrainHeight (game, p1.x, p1.z) + 0.5f;
p2.y = GetTerrainHeight (game, p2.x, p2.z) + 0.5f;
DrawLine3D (p1, p2, ColorAlpha (SKYBLUE, game->currentPulse.strength));
// Vertical lines
Vector3 p1Top = p1;
p1Top.y += 5.0f * game->currentPulse.strength;
DrawLine3D (p1, p1Top,
ColorAlpha (SKYBLUE, game->currentPulse.strength * 0.5f));
}
}
// Draw crystals
void
DrawCrystals (GameState *game)
{
for (int i = 0; i < MAX_CRYSTALS; i++)
{
if (game->crystals[i].collected)
continue;
Vector3 pos = game->crystals[i].position;
// Only draw if visible
if (!IsPositionRevealed (game, pos.x, pos.z))
continue;
float vis = game->world.visibility[(int)pos.x][(int)pos.z];
if (vis <= 0)
continue;
// Glowing effect
float glow = (sinf (game->crystals[i].glowTimer) + 1.0f) * 0.5f;
Color color = ColorAlpha (SKYBLUE, vis * (0.5f + glow * 0.5f));
DrawCube (pos, 0.3f, 0.6f, 0.3f, color);
DrawCubeWires (pos, 0.35f, 0.65f, 0.35f, ColorAlpha (WHITE, vis * glow));
}
}
// Draw enemies
void
DrawEnemies (GameState *game)
{
for (int i = 0; i < MAX_ENEMIES; i++)
{
if (!game->enemies[i].active)
continue;
Enemy *enemy = &game->enemies[i];
// Only draw if visible
if (!IsPositionRevealed (game, enemy->position.x, enemy->position.z))
continue;
float vis
= game->world
.visibility[(int)enemy->position.x][(int)enemy->position.z];
if (vis <= 0)
continue;
Color color;
switch (enemy->type)
{
case ENEMY_ANGER:
color = ColorAlpha (RED, vis);
DrawSphere (enemy->position, 0.5f, color);
break;
case ENEMY_FEAR:
color = ColorAlpha (DARKBLUE, vis * 0.5f);
DrawCube (enemy->position, 0.4f, 0.4f, 0.4f, color);
break;
case ENEMY_CURIOSITY:
color = ColorAlpha (YELLOW, vis);
DrawCylinder (enemy->position, 0.3f, 0.3f, 0.6f, 8, color);
break;
}
}
}
// Draw time dilation zones
void
DrawTimeDilationZones (GameState *game)
{
for (int i = 0; i < 10; i++)
{
if (game->timeDilationZones[i].timeLeft <= 0)
continue;
TimeDilationZone *zone = &game->timeDilationZones[i];
float alpha = zone->timeLeft / 10.0f;
DrawCylinderWires (zone->position, zone->radius, zone->radius, 0.1f, 16,
ColorAlpha (PURPLE, alpha * 0.3f));
// Draw rotating rings
float angle = GetTime () * 0.5f;
int rings = 3;
for (int r = 0; r < rings; r++)
{
float ringRadius = zone->radius * (r + 1) / rings;
int segments = 16;
for (int s = 0; s < segments; s++)
{
float a1 = (s * 2 * PI / segments) + angle * (r + 1);
float a2 = ((s + 1) * 2 * PI / segments) + angle * (r + 1);
Vector3 p1 = Vector3Add (
zone->position, (Vector3){ cosf (a1) * ringRadius, r * 0.5f,
sinf (a1) * ringRadius });
Vector3 p2 = Vector3Add (
zone->position, (Vector3){ cosf (a2) * ringRadius, r * 0.5f,
sinf (a2) * ringRadius });
DrawLine3D (p1, p2, ColorAlpha (PURPLE, alpha * 0.5f));
}
}
}
}
// Draw echo bridges
void
DrawEchoBridges (GameState *game)
{
for (int i = 0; i < game->bridgeCount; i++)
{
if (!game->echoBridges[i].active)
continue;
EchoBridge *bridge = &game->echoBridges[i];
// Draw bridge segments
Vector3 dir
= Vector3Normalize (Vector3Subtract (bridge->end, bridge->start));
Vector3 perpendicular = Vector3Normalize ((Vector3){ -dir.z, 0, dir.x });
float length = Vector3Distance (bridge->start, bridge->end);
int segments = (int)(length / 0.5f);
for (int s = 0; s < segments; s++)
{
float t1 = (float)s / segments;
float t2 = (float)(s + 1) / segments;
Vector3 p1 = Vector3Lerp (bridge->start, bridge->end, t1);
Vector3 p2 = Vector3Lerp (bridge->start, bridge->end, t2);
p1.y = GetTerrainHeight (game, p1.x, p1.z) + 0.1f;
p2.y = GetTerrainHeight (game, p2.x, p2.z) + 0.1f;
// Draw bridge width
for (float w = -bridge->width / 2; w <= bridge->width / 2; w += 0.5f)
{
Vector3 offset = Vector3Scale (perpendicular, w);
Vector3 p1w = Vector3Add (p1, offset);
Vector3 p2w = Vector3Add (p2, offset);
DrawLine3D (p1w, p2w, ColorAlpha (YELLOW, 0.8f));
}
// Draw cross beams
if (s % 2 == 0)
{
Vector3 p1Left = Vector3Add (
p1, Vector3Scale (perpendicular, -bridge->width / 2));
Vector3 p1Right = Vector3Add (
p1, Vector3Scale (perpendicular, bridge->width / 2));
DrawLine3D (p1Left, p1Right, ColorAlpha (YELLOW, 0.6f));
}
}
}
}
// Draw sound particles
void
DrawSoundParticles (GameState *game)
{
for (int i = 0; i < MAX_SOUND_PARTICLES; i++)
{
if (game->soundParticles[i].life > 0)
{
float alpha = game->soundParticles[i].life;
Color color = ColorAlpha (game->soundParticles[i].color, alpha);
DrawSphere (game->soundParticles[i].position, 0.1f, color);
}
}
}
// Draw UI
void
DrawUI (GameState *game)
{
// Echo energy bar
DrawRectangle (10, 10, 200, 30, ColorAlpha (BLACK, 0.5f));
DrawRectangle (10, 10, 200 * game->echoEnergy / MAX_ECHO_ENERGY, 30, SKYBLUE);
DrawRectangleLines (10, 10, 200, 30, WHITE);
DrawText (
TextFormat ("Echo Energy: %d/%d", game->echoEnergy, MAX_ECHO_ENERGY), 15,
15, 20, WHITE);
// Selected tool
const char *toolName = "None";
switch (game->selectedTool)
{
case TOOL_GRAVITY:
toolName = "Gravity Manipulation";
break;
case TOOL_TIME_DILATION:
toolName = "Time Dilation";
break;
case TOOL_ECHO_BRIDGE:
toolName = "Echo Bridge";
break;
default:
break;
}
DrawText (TextFormat ("Tool: %s", toolName), 10, 50, 20, WHITE);
// Controls
DrawText ("Controls:", 10, SCREEN_HEIGHT - 170, 20, WHITE);
DrawText ("WASD/QE - Move | A/D - Rotate", 10, SCREEN_HEIGHT - 140, 18, GRAY);
DrawText ("SPACE - Echo Pulse | F - Anchor Area", 10, SCREEN_HEIGHT - 120, 18,
GRAY);
DrawText ("1/2/3 - Select Tool | LMB - Use Tool", 10, SCREEN_HEIGHT - 100, 18,
GRAY);
DrawText ("SHIFT + LMB - Reverse Gravity Tool", 10, SCREEN_HEIGHT - 80, 18,
GRAY);
DrawText ("F11 - Toggle Fullscreen | ESC - Exit", 10, SCREEN_HEIGHT - 60, 18,
GRAY);
// World memory warning
float maxMemory = 0;
for (int x = 0; x < WORLD_SIZE; x++)
{
for (int z = 0; z < WORLD_SIZE; z++)
{
if (game->world.memory[x][z] > maxMemory)
{
maxMemory = game->world.memory[x][z];
}
}
}
if (maxMemory > 3.0f)
{
DrawText ("WARNING: World Memory Unstable!", SCREEN_WIDTH / 2 - 150, 50,
20, ColorAlpha (RED, sinf (GetTime () * 5) * 0.5f + 0.5f));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment