kitchen: README + MISSIONS/engine-c keep-drop notes
35
KEEP-DROP.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
KEEP_OK=1
|
||||||
|
|
||||||
|
# KEEP vs DROP — OpenKKnD amanita pull (TODO=361 / 023-kknd)
|
||||||
|
# Built: Director shell (Sparmodus); MERGE into _hernani-private/openkknd 2026-09-16
|
||||||
|
# Amanita origins DELETE_OK 2026-09-16 (Freigabe chat) · no Godot openkknd2 auto-merge · no push without Freigabe
|
||||||
|
|
||||||
|
## KEEP (live under _hernani-private/openkknd/ — Freigabe flatten-as-subdirs)
|
||||||
|
|
||||||
|
| Subdir | Origin |
|
||||||
|
|--------|--------|
|
||||||
|
| `engine-c/` | vx-openkknd/ (C11+SDL2) |
|
||||||
|
| `MISSIONS/` | MISSION.md from openkknd-{qwen,hermes,qwen14b,deepseek} |
|
||||||
|
| `openkknd2-am-coder/` | unique trail (no build/) |
|
||||||
|
| `kknd2-runs/` | historical run logs |
|
||||||
|
| `llm-configs-openkknd2/` | amanita llm-configs openkknd2 notes |
|
||||||
|
|
||||||
|
Staging copy still under `$HOME/git/llm/llm-work-input/openkknd-amanita-pull/_keep/`.
|
||||||
|
|
||||||
|
## DROP (leave in staging; do not promote; amanita delete only after human Freigabe)
|
||||||
|
|
||||||
|
- Full trees openkknd-{qwen,hermes,qwen14b,deepseek} except MISSION.md — near-dup C vs vx-openkknd
|
||||||
|
- Binary rebuild optional (`openkknd` / `openkknd_dbg`) — rebuild via `engine-c/build.sh` on Linux
|
||||||
|
- Empty asset placeholders if unused
|
||||||
|
- `openkknd2-am-coder/build/` object junk
|
||||||
|
|
||||||
|
## DO NOT
|
||||||
|
|
||||||
|
- Litter Pitchwell **root** with C sources — keep under `engine-c/` only (subdirs OK; root flatten = no)
|
||||||
|
- Auto-merge am-coder into Godot `$HOME/git/src/openkknd2`
|
||||||
|
- Re-create amanita openkknd\* trees without need (DELETE_OK done)
|
||||||
|
- Treat C-engine as “orthogonal forever” — **superseded**: one RTS tree = `_hernani-private/openkknd` with `engine-c/` + kitchen
|
||||||
|
|
||||||
|
## Proposal (text only)
|
||||||
|
|
||||||
|
Dauerhome: subdirs under `_hernani-private/openkknd/`. Separate `openkknd-c-engine/` repo **not** required unless human reopens Decide.
|
||||||
25
MISSIONS/deepseek.MISSION.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# KKND2 Auftrag — openkknd-deepseek (deepseek-coder:6.7b)
|
||||||
|
|
||||||
|
You own this tree: `/home/vx/src/openkknd-deepseek`
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Continue / improve **openkknd** — Krush Kill 'n' Destroy–style **2D RTS** in **C11 + SDL2** (+ SDL2_ttf, zlib).
|
||||||
|
|
||||||
|
Reference (read-only): `/home/vx/openkknd` — do **not** modify it. Work only here.
|
||||||
|
|
||||||
|
## Required outcomes
|
||||||
|
1. Project builds with `./build.sh` (fix compile/link errors with write/edit).
|
||||||
|
2. Keep modular layout: `src/*.c`, `include/*.h`, `assets/` as needed.
|
||||||
|
3. Fix warnings if cheap (e.g. MAX_PARTICLES redefinition) after green build.
|
||||||
|
4. Add/update `README.md` with build/run instructions.
|
||||||
|
5. `git init` if missing; commit when something works.
|
||||||
|
6. Prefer real shell tools (`exec`) — do not only print tool JSON as text.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Linux (CachyOS), user vx, gcc + sdl2 + SDL2_ttf. No sudo.
|
||||||
|
- Stay inside this directory.
|
||||||
|
|
||||||
|
## First steps
|
||||||
|
1. cd /home/vx/src/openkknd-deepseek && ls -la
|
||||||
|
2. ./build.sh — fix failures
|
||||||
|
3. README + git commit after green build
|
||||||
26
MISSIONS/hermes.MISSION.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# KKND2 Auftrag — openkknd-hermes (hermes3)
|
||||||
|
|
||||||
|
You own this tree: `/home/vx/src/openkknd-hermes`
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Continue / improve **openkknd** — a Krush Kill 'n' Destroy–style **2D RTS** in **C11 + SDL2** (+ SDL2_ttf, zlib).
|
||||||
|
|
||||||
|
Reference (read-only if needed): `/home/vx/openkknd` — do **not** modify it. Work only here.
|
||||||
|
|
||||||
|
## Required outcomes
|
||||||
|
1. Project builds with `./build.sh` (or fix Makefile/build.sh until it does).
|
||||||
|
2. Keep modular layout: `src/*.c`, `include/*.h`, `assets/` as needed.
|
||||||
|
3. Game loop + map/units/buildings should remain coherent; fix compile errors first.
|
||||||
|
4. Add a short `README.md` describing how to build and run.
|
||||||
|
5. `git init` if missing; commit meaningful steps when something works.
|
||||||
|
6. Prefer real shell tools (`exec`) over inventing files without writing them.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Host: Linux (CachyOS), user `vx`, packages: gcc, sdl2, SDL2_ttf.
|
||||||
|
- No network installs that need root/sudo.
|
||||||
|
- Stay inside this directory.
|
||||||
|
|
||||||
|
## First steps
|
||||||
|
1. `cd /home/vx/src/openkknd-hermes && ls -la`
|
||||||
|
2. Try `./build.sh` and fix failures.
|
||||||
|
3. Improve gameplay or structure only after a green build.
|
||||||
26
MISSIONS/qwen.MISSION.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# KKND2 Auftrag — openkknd-qwen (qwen2.5-coder)
|
||||||
|
|
||||||
|
You own this tree: `/home/vx/src/openkknd-qwen`
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Continue / improve **openkknd** — a Krush Kill 'n' Destroy–style **2D RTS** in **C11 + SDL2** (+ SDL2_ttf, zlib).
|
||||||
|
|
||||||
|
Reference (read-only if needed): `/home/vx/openkknd` — do **not** modify it. Work only here.
|
||||||
|
|
||||||
|
## Required outcomes
|
||||||
|
1. Project builds with `./build.sh` (or fix Makefile/build.sh until it does).
|
||||||
|
2. Keep modular layout: `src/*.c`, `include/*.h`, `assets/` as needed.
|
||||||
|
3. Game loop + map/units/buildings should remain coherent; fix compile errors first.
|
||||||
|
4. Add a short `README.md` describing how to build and run.
|
||||||
|
5. `git init` if missing; commit meaningful steps when something works.
|
||||||
|
6. Prefer real shell tools (`exec`) over inventing files without writing them.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Host: Linux (CachyOS), user `vx`, packages: gcc, sdl2, SDL2_ttf.
|
||||||
|
- No network installs that need root/sudo.
|
||||||
|
- Stay inside this directory.
|
||||||
|
|
||||||
|
## First steps
|
||||||
|
1. `cd /home/vx/src/openkknd-qwen && ls -la`
|
||||||
|
2. Try `./build.sh` and fix failures.
|
||||||
|
3. Improve gameplay or structure only after a green build.
|
||||||
25
MISSIONS/qwen14b.MISSION.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# KKND2 Auftrag — openkknd-qwen14b (qwen2.5-coder:14b)
|
||||||
|
|
||||||
|
You own this tree: `/home/vx/src/openkknd-qwen14b`
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Continue / improve **openkknd** — Krush Kill 'n' Destroy–style **2D RTS** in **C11 + SDL2** (+ SDL2_ttf, zlib).
|
||||||
|
|
||||||
|
Reference (read-only): `/home/vx/openkknd` — do **not** modify it. Work only here.
|
||||||
|
|
||||||
|
## Required outcomes
|
||||||
|
1. Project builds with `./build.sh` (fix compile/link errors with write/edit).
|
||||||
|
2. Keep modular layout: `src/*.c`, `include/*.h`, `assets/` as needed.
|
||||||
|
3. Fix warnings if cheap (e.g. MAX_PARTICLES redefinition) after green build.
|
||||||
|
4. Add/update `README.md` with build/run instructions.
|
||||||
|
5. `git init` if missing; commit when something works.
|
||||||
|
6. Prefer real shell tools (`exec`) — do not only print tool JSON as text.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Linux (CachyOS), user vx, gcc + sdl2 + SDL2_ttf. No sudo.
|
||||||
|
- Stay inside this directory.
|
||||||
|
|
||||||
|
## First steps
|
||||||
|
1. cd /home/vx/src/openkknd-qwen14b && ls -la
|
||||||
|
2. ./build.sh — fix failures
|
||||||
|
3. README + git commit after green build
|
||||||
22
engine-c/build.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Build script for openkknd - the merged KKND-style 2D RTS.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
CC=${CC:-gcc}
|
||||||
|
CFLAGS="-O2 -Wall -Iinclude"
|
||||||
|
SRCS="src/unit_card.c src/rng.c src/config.c src/png.c src/snapshot.c src/map.c src/sprites.c \
|
||||||
|
src/units.c src/buildings.c src/projectiles.c src/render.c \
|
||||||
|
src/input.c src/ai.c src/audio.c src/save.c src/engine.c src/main.c"
|
||||||
|
|
||||||
|
LIBS=$(pkg-config --cflags --libs sdl2 SDL2_ttf 2>/dev/null)
|
||||||
|
if [ -z "$LIBS" ]; then
|
||||||
|
LIBS="-lSDL2 -lSDL2_ttf"
|
||||||
|
fi
|
||||||
|
LIBS="$LIBS -lz"
|
||||||
|
|
||||||
|
echo "openkknd: compiling..."
|
||||||
|
$CC $CFLAGS $SRCS -o openkknd $LIBS -lm
|
||||||
|
echo "openkknd: built -> ./openkknd"
|
||||||
|
echo "usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd]"
|
||||||
BIN
engine-c/gallery/m0_evolved.png
Normal file
|
After Width: | Height: | Size: 7 KiB |
BIN
engine-c/gallery/m0_series9.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
engine-c/gallery/m0_survivor.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
engine-c/gallery/m1_evolved.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
engine-c/gallery/m1_series9.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
engine-c/gallery/m1_survivor.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
engine-c/gallery/m2_evolved.png
Normal file
|
After Width: | Height: | Size: 7 KiB |
BIN
engine-c/gallery/m2_series9.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
engine-c/gallery/m2_survivor.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
engine-c/gallery/m3_evolved.png
Normal file
|
After Width: | Height: | Size: 7 KiB |
BIN
engine-c/gallery/m3_series9.png
Normal file
|
After Width: | Height: | Size: 8 KiB |
BIN
engine-c/gallery/m3_survivor.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
engine-c/gallery/m4_evolved.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
engine-c/gallery/m4_series9.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
engine-c/gallery/m4_survivor.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
642
engine-c/include/kknd.h
Normal file
|
|
@ -0,0 +1,642 @@
|
||||||
|
#ifndef KKND_H
|
||||||
|
#define KKND_H
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* openkknd - Krush, Kill 'n' Destroy style 2D Real-Time Strategy engine
|
||||||
|
* Single merged codebase (was: openkknd + openkknd_full).
|
||||||
|
*
|
||||||
|
* This header declares the complete shared type system and the public
|
||||||
|
* interface of every engine subsystem. The implementation lives in the
|
||||||
|
* matching src/*.c translation units.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <SDL2/SDL_image.h>
|
||||||
|
#include <SDL2/SDL_ttf.h>
|
||||||
|
|
||||||
|
#include "unit_card.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Global tunables */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
#define KKND_NAME "openkknd"
|
||||||
|
#define KKND_VERSION "1.0.0"
|
||||||
|
|
||||||
|
#define TILE 32 /* pixel size of one map tile */
|
||||||
|
#define MAP_W 128 /* map width in tiles */
|
||||||
|
#define MAP_H 128 /* map height in tiles */
|
||||||
|
#define MAP_PIX_W (MAP_W * TILE)
|
||||||
|
#define MAP_PIX_H (MAP_H * TILE)
|
||||||
|
|
||||||
|
#define MAX_UNITS 3000
|
||||||
|
#define MAX_BUILDINGS 1024
|
||||||
|
#define MAX_BULLETS 6000
|
||||||
|
#define MAX_PARTICLES 4000
|
||||||
|
#define MAX_PATH 1024
|
||||||
|
#define MAX_SELECTION 256
|
||||||
|
#define MAX_FACTIONS 4
|
||||||
|
|
||||||
|
#define VIEW_W 1280
|
||||||
|
#define VIEW_H 720
|
||||||
|
|
||||||
|
#define START_SCRAP 1500 /* starting resource for each side */
|
||||||
|
#define SCRAP_PER_TILE 1000 /* scrap contained in one scrap field */
|
||||||
|
|
||||||
|
#define SAVE_MAGIC 0x4B4B4E44 /* 'KKND' */
|
||||||
|
#define SAVE_VERSION 2
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Enumerations */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef enum {
|
||||||
|
T_WASTELAND = 0,
|
||||||
|
T_RUBBLE,
|
||||||
|
T_ROAD,
|
||||||
|
T_GRASS,
|
||||||
|
T_WATER,
|
||||||
|
T_ROCK,
|
||||||
|
T_SCRAP,
|
||||||
|
T_COUNT
|
||||||
|
} TerrainType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FACTION_NONE = 0,
|
||||||
|
FACTION_SURVIVOR, /* player - post-war military remnants */
|
||||||
|
FACTION_EVOLVED, /* ai - mutated wasteland beasties */
|
||||||
|
FACTION_SERIES9, /* neutral - rogue war machines */
|
||||||
|
FACTION_NEUTRAL
|
||||||
|
} Faction;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UT_NONE = 0,
|
||||||
|
UT_INFANTRY,
|
||||||
|
UT_BUGGY,
|
||||||
|
UT_TANK,
|
||||||
|
UT_HARVESTER,
|
||||||
|
UT_ARTILLERY,
|
||||||
|
UT_JEEP,
|
||||||
|
UT_MUTANT,
|
||||||
|
UT_BEAST,
|
||||||
|
UT_RAVAGER,
|
||||||
|
UT_FLAME,
|
||||||
|
UT_MISSILE,
|
||||||
|
UT_MEDIC,
|
||||||
|
UT_COUNT
|
||||||
|
} UnitType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
AB_NONE = 0,
|
||||||
|
AB_SIEGE, /* tanks/artillery: +range, immobile while active */
|
||||||
|
AB_HEAL /* medics: pulse heal nearby allies */
|
||||||
|
} AbilityType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BT_NONE = 0,
|
||||||
|
BT_BASE, /* command centre / HQ */
|
||||||
|
BT_WARFACTORY,
|
||||||
|
BT_REFINERY,
|
||||||
|
BT_POWER,
|
||||||
|
BT_TURRET,
|
||||||
|
BT_WALL,
|
||||||
|
BT_REPAIR,
|
||||||
|
BT_RESEARCH,
|
||||||
|
BT_COUNT
|
||||||
|
} BuildingType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
STATE_IDLE = 0,
|
||||||
|
STATE_MOVING,
|
||||||
|
STATE_ATTACKING,
|
||||||
|
STATE_GATHERING,
|
||||||
|
STATE_RETURNING,
|
||||||
|
STATE_DEAD
|
||||||
|
} UnitState;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ORDER_NONE = 0,
|
||||||
|
ORDER_MOVE,
|
||||||
|
ORDER_ATTACKMOVE,
|
||||||
|
ORDER_ATTACK,
|
||||||
|
ORDER_GATHER,
|
||||||
|
ORDER_RETURN,
|
||||||
|
ORDER_BUILD,
|
||||||
|
ORDER_STOP
|
||||||
|
} OrderType;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_MENU = 0,
|
||||||
|
GAME_PLAYING,
|
||||||
|
GAME_PAUSED,
|
||||||
|
GAME_WON,
|
||||||
|
GAME_LOST
|
||||||
|
} GameState;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DIFF_EASY = 0,
|
||||||
|
DIFF_NORMAL,
|
||||||
|
DIFF_HARD,
|
||||||
|
DIFF_COUNT
|
||||||
|
} Difficulty;
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Small math helpers */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct { float x, y; } Vec2;
|
||||||
|
|
||||||
|
static inline float kknd_dist2(float ax, float ay, float bx, float by) {
|
||||||
|
float dx = ax - bx, dy = ay - by;
|
||||||
|
return dx * dx + dy * dy;
|
||||||
|
}
|
||||||
|
static inline float kknd_dist(float ax, float ay, float bx, float by) {
|
||||||
|
return (float)sqrtf(kknd_dist2(ax, ay, bx, by));
|
||||||
|
}
|
||||||
|
static inline float kknd_clampf(float v, float lo, float hi) {
|
||||||
|
return v < lo ? lo : (v > hi ? hi : v);
|
||||||
|
}
|
||||||
|
static inline int kknd_clampi(int v, int lo, int hi) {
|
||||||
|
return v < lo ? lo : (v > hi ? hi : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Deterministic RNG (xoroshiro128**) */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct {
|
||||||
|
uint64_t s[2];
|
||||||
|
} RNG;
|
||||||
|
|
||||||
|
void rng_seed(RNG *r, uint64_t seed);
|
||||||
|
uint64_t rng_u64(RNG *r);
|
||||||
|
uint32_t rng_u32(RNG *r);
|
||||||
|
int rng_range(RNG *r, int lo, int hi);
|
||||||
|
float rng_f32(RNG *r);
|
||||||
|
float rng_frange(RNG *r, float lo, float hi);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Map */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t terrain; /* TerrainType */
|
||||||
|
uint8_t walkable; /* 0 = blocked, 1 = passable */
|
||||||
|
uint8_t scrap; /* remaining scrap (0..255) */
|
||||||
|
uint8_t variant; /* cosmetic variation */
|
||||||
|
uint8_t occupied; /* building footprint marker */
|
||||||
|
} Tile;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int w, h;
|
||||||
|
Tile *tiles; /* w*h */
|
||||||
|
uint8_t *fog; /* human visibility: 0 hidden,1 explored,2 visible */
|
||||||
|
int scrap_fields; /* number of scrap deposits */
|
||||||
|
int style; /* terrain theme */
|
||||||
|
int spawn_x[MAX_FACTIONS];
|
||||||
|
int spawn_y[MAX_FACTIONS];
|
||||||
|
} GameMap;
|
||||||
|
|
||||||
|
void map_init(GameMap *m, RNG *rng);
|
||||||
|
void map_free(GameMap *m);
|
||||||
|
Tile *map_tile(GameMap *m, int tx, int ty);
|
||||||
|
int map_walkable(GameMap *m, int tx, int ty);
|
||||||
|
int map_in_bounds(GameMap *m, int tx, int ty);
|
||||||
|
void map_world_to_tile(float wx, float wy, int *tx, int *ty);
|
||||||
|
void map_tile_to_world(int tx, int ty, float *wx, float *wy);
|
||||||
|
int map_find_nearest_scrap(GameMap *m, float wx, float wy, float *outx, float *outy);
|
||||||
|
void map_deplete_scrap(GameMap *m, int tx, int ty, int amount);
|
||||||
|
typedef struct Game Game;
|
||||||
|
void map_update_fog(Game *g, Faction f);
|
||||||
|
|
||||||
|
/* A* pathfinding ------------------------------------------------------- */
|
||||||
|
typedef struct {
|
||||||
|
int tx, ty; /* tile coordinate */
|
||||||
|
} PathNode;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PathNode nodes[MAX_PATH];
|
||||||
|
int len;
|
||||||
|
} Path;
|
||||||
|
|
||||||
|
int path_find(GameMap *m, int sx, int sy, int gx, int gy, Path *out);
|
||||||
|
void path_smooth(GameMap *m, Path *p);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Units */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct Unit {
|
||||||
|
int id;
|
||||||
|
UnitType type;
|
||||||
|
Faction faction;
|
||||||
|
float x, y; /* world position (pixels) */
|
||||||
|
float hp;
|
||||||
|
float max_hp;
|
||||||
|
float attack;
|
||||||
|
float defense;
|
||||||
|
float speed; /* pixels / second */
|
||||||
|
float range; /* pixels */
|
||||||
|
float sight; /* pixels */
|
||||||
|
float heading; /* radians, for sprite rotation */
|
||||||
|
UnitState state;
|
||||||
|
OrderType order;
|
||||||
|
int target_unit; /* id, -1 = none */
|
||||||
|
int target_bld; /* id, -1 = none */
|
||||||
|
float dest_x, dest_y;
|
||||||
|
Path path;
|
||||||
|
int path_idx;
|
||||||
|
float atk_cd; /* attack cooldown timer */
|
||||||
|
float cargo; /* harvester load */
|
||||||
|
int selected;
|
||||||
|
int dead;
|
||||||
|
float anim; /* walk/attack animation phase */
|
||||||
|
float repath_timer; /* throttle re-pathing */
|
||||||
|
int hold; /* hold position (don't chase) */
|
||||||
|
int attack_move; /* move toward dest but engage en route */
|
||||||
|
AbilityType ability; /* special ability, 0 = none */
|
||||||
|
float ability_cd; /* cooldown remaining */
|
||||||
|
int ability_on; /* active toggle (siege) */
|
||||||
|
float reload_mul; /* reload speed multiplier (upgrades) */
|
||||||
|
} Unit;
|
||||||
|
|
||||||
|
#define MAX_WRECKS 1024
|
||||||
|
typedef struct {
|
||||||
|
float x, y;
|
||||||
|
UnitType type;
|
||||||
|
Faction faction;
|
||||||
|
float rot;
|
||||||
|
} Wreck;
|
||||||
|
|
||||||
|
#define MAX_PARTICLES 2048
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Buildings */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct Building {
|
||||||
|
int id;
|
||||||
|
BuildingType type;
|
||||||
|
Faction faction;
|
||||||
|
int tx, ty; /* top-left tile */
|
||||||
|
int w, h; /* footprint in tiles */
|
||||||
|
float hp;
|
||||||
|
float max_hp;
|
||||||
|
float build; /* 0..1 construction progress */
|
||||||
|
int producing; /* UnitType being built, 0 = none */
|
||||||
|
float prod_timer;
|
||||||
|
float prod_total;
|
||||||
|
int queue[MAX_SELECTION];
|
||||||
|
int queue_len;
|
||||||
|
float rally_x, rally_y;
|
||||||
|
int selected;
|
||||||
|
int dead;
|
||||||
|
float atk_cd;
|
||||||
|
} Building;
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Projectiles & particles */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct {
|
||||||
|
float x, y, tx, ty;
|
||||||
|
float speed;
|
||||||
|
float damage;
|
||||||
|
Faction faction;
|
||||||
|
int target_unit;
|
||||||
|
int target_bld;
|
||||||
|
float life;
|
||||||
|
int kind; /* 0 bullet, 1 shell, 2 laser, 3 missile */
|
||||||
|
} Bullet;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
float x, y;
|
||||||
|
float vx, vy;
|
||||||
|
float life;
|
||||||
|
float max_life;
|
||||||
|
uint8_t r, g, b;
|
||||||
|
int kind; /* 0 smoke, 1 fire, 2 spark, 3 debris */
|
||||||
|
} Particle;
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Camera */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct {
|
||||||
|
float x, y; /* world top-left of view */
|
||||||
|
float zoom;
|
||||||
|
int shake;
|
||||||
|
} Camera;
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Sprite cache */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct {
|
||||||
|
SDL_Texture *unit[UT_COUNT][MAX_FACTIONS];
|
||||||
|
SDL_Texture *bld[BT_COUNT][MAX_FACTIONS];
|
||||||
|
SDL_Texture *terrain[T_COUNT];
|
||||||
|
SDL_Texture *bullet[4];
|
||||||
|
TTF_Font *font_small;
|
||||||
|
TTF_Font *font_med;
|
||||||
|
TTF_Font *font_big;
|
||||||
|
} Sprites;
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Research / upgrades (declared early so Game can embed it) */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
#define UP_DAMAGE 0
|
||||||
|
#define UP_ARMOR 1
|
||||||
|
#define UP_SPEED 2
|
||||||
|
#define UP_RELOAD 3
|
||||||
|
#define UP_BUILD 4
|
||||||
|
#define UP_COUNT 5
|
||||||
|
#define UP_MAX_LVL 3
|
||||||
|
|
||||||
|
typedef struct { int lvl[UP_COUNT]; } Upgrades;
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Game state */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct Game {
|
||||||
|
GameMap map;
|
||||||
|
Unit units[MAX_UNITS];
|
||||||
|
int unit_count;
|
||||||
|
int unit_next_id;
|
||||||
|
Building buildings[MAX_BUILDINGS];
|
||||||
|
int bld_count;
|
||||||
|
int bld_next_id;
|
||||||
|
Bullet bullets[MAX_BULLETS];
|
||||||
|
int bullet_count;
|
||||||
|
Particle particles[MAX_PARTICLES];
|
||||||
|
int particle_count;
|
||||||
|
|
||||||
|
int scrap[MAX_FACTIONS];
|
||||||
|
int power[MAX_FACTIONS];
|
||||||
|
int pop[MAX_FACTIONS];
|
||||||
|
int pop_cap[MAX_FACTIONS];
|
||||||
|
|
||||||
|
int selection[MAX_SELECTION];
|
||||||
|
int sel_count;
|
||||||
|
|
||||||
|
/* control groups (Ctrl+1..9 sets, 1..9 recalls) */
|
||||||
|
int groups[9][MAX_SELECTION];
|
||||||
|
int group_len[9];
|
||||||
|
|
||||||
|
Camera cam;
|
||||||
|
Sprites spr;
|
||||||
|
SDL_Renderer *ren;
|
||||||
|
SDL_Window *win;
|
||||||
|
|
||||||
|
GameState state;
|
||||||
|
Difficulty diff;
|
||||||
|
Faction human;
|
||||||
|
float time;
|
||||||
|
float speed_mul;
|
||||||
|
int winner;
|
||||||
|
int pause;
|
||||||
|
|
||||||
|
/* drag-selection rectangle (screen space) */
|
||||||
|
int drag_x0, drag_y0, drag_x1, drag_y1, dragging;
|
||||||
|
|
||||||
|
/* build placement ghost */
|
||||||
|
BuildingType place_type;
|
||||||
|
int placing;
|
||||||
|
|
||||||
|
float ai_timer;
|
||||||
|
float ai_wave[MAX_FACTIONS];
|
||||||
|
int frames;
|
||||||
|
|
||||||
|
/* on-screen message log (ring buffer) */
|
||||||
|
char log_msg[32][96];
|
||||||
|
int log_count;
|
||||||
|
int log_next;
|
||||||
|
|
||||||
|
/* research / upgrades */
|
||||||
|
Upgrades upg[MAX_FACTIONS];
|
||||||
|
int research_lab[MAX_FACTIONS];
|
||||||
|
int research_cur[MAX_FACTIONS];
|
||||||
|
float research_prog[MAX_FACTIONS];
|
||||||
|
|
||||||
|
/* mission */
|
||||||
|
int mission_id;
|
||||||
|
int objective;
|
||||||
|
Faction enemy; /* actual enemy for the active match */
|
||||||
|
int unlocked; /* highest unlocked mission id + 1 */
|
||||||
|
int menu_sel; /* highlighted mission in the menu */
|
||||||
|
float obj_timer;
|
||||||
|
|
||||||
|
/* wreckage decals */
|
||||||
|
Wreck *wrecks;
|
||||||
|
int wreck_count;
|
||||||
|
int wreck_next;
|
||||||
|
} Game;
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Configuration / unit stats */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
UnitType type;
|
||||||
|
Faction faction;
|
||||||
|
int cost;
|
||||||
|
float hp;
|
||||||
|
float attack;
|
||||||
|
float defense;
|
||||||
|
float speed;
|
||||||
|
float range;
|
||||||
|
float sight;
|
||||||
|
int cargo;
|
||||||
|
int build_time; /* seconds to produce */
|
||||||
|
AbilityType ability; /* special ability, 0 = none */
|
||||||
|
const char *desc;
|
||||||
|
} UnitDef;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
BuildingType type;
|
||||||
|
int cost;
|
||||||
|
int build_time;
|
||||||
|
float hp;
|
||||||
|
int w, h;
|
||||||
|
int provides_power;
|
||||||
|
int pop_provided;
|
||||||
|
int produces[UT_COUNT > 8 ? UT_COUNT : 8];
|
||||||
|
int produces_count;
|
||||||
|
int is_turret;
|
||||||
|
float atk;
|
||||||
|
float range;
|
||||||
|
int is_research; /* research lab flag */
|
||||||
|
const char *desc;
|
||||||
|
} BldDef;
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Missions / campaign */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
#define OBJ_DESTROY_ALL 0
|
||||||
|
#define OBJ_SURVIVE 1
|
||||||
|
#define OBJ_GATHER 2
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int id;
|
||||||
|
const char *name;
|
||||||
|
const char *brief;
|
||||||
|
int objective;
|
||||||
|
Faction enemy;
|
||||||
|
int diff;
|
||||||
|
int scrap_goal; /* for OBJ_GATHER */
|
||||||
|
float survive_time; /* for OBJ_SURVIVE */
|
||||||
|
int map_style; /* terrain theme */
|
||||||
|
uint32_t seed;
|
||||||
|
} Mission;
|
||||||
|
|
||||||
|
/* Build the unit-card registries (merged feature) and stat tables. */
|
||||||
|
void config_init(void);
|
||||||
|
const UnitDef *unit_def(UnitType t, Faction f);
|
||||||
|
const BldDef *bld_def(BuildingType t);
|
||||||
|
UnitRegistry *unit_card_registry(void); /* for save/inspect */
|
||||||
|
UnitRegistry *bld_card_registry(void);
|
||||||
|
int mission_count(void);
|
||||||
|
const Mission *mission_get(int id);
|
||||||
|
const char *research_name(int up);
|
||||||
|
int research_cost(int up, int level);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Sprites */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
int sprites_init(Sprites *s, SDL_Renderer *r);
|
||||||
|
void sprites_free(Sprites *s);
|
||||||
|
SDL_Texture *sprites_make_unit(SDL_Renderer *r, UnitType t, Faction f);
|
||||||
|
SDL_Texture *sprites_make_bld(SDL_Renderer *r, BuildingType t, Faction f);
|
||||||
|
SDL_Texture *sprites_make_terrain(SDL_Renderer *r, TerrainType t);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Units */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
int unit_spawn(Game *g, UnitType t, Faction f, float x, float y);
|
||||||
|
void unit_remove(Game *g, int idx);
|
||||||
|
void unit_update(Game *g, float dt);
|
||||||
|
void unit_issue_move(Game *g, int idx, float x, float y, int attackmove);
|
||||||
|
void unit_issue_move_group(Game *g, const int *idxs, int n, float cx, float cy, int attackmove);
|
||||||
|
void unit_issue_attack_unit(Game *g, int idx, int target);
|
||||||
|
void unit_issue_attack_bld(Game *g, int idx, int target);
|
||||||
|
void unit_issue_gather(Game *g, int idx, float x, float y);
|
||||||
|
void unit_issue_gather_nearest(Game *g, int idx);
|
||||||
|
void unit_issue_return(Game *g, int idx);
|
||||||
|
void unit_issue_stop(Game *g, int idx);
|
||||||
|
void unit_issue_hold(Game *g, int idx);
|
||||||
|
void unit_ability_use(Game *g, int idx);
|
||||||
|
void unit_apply_damage(Game *g, Unit *u, float dmg);
|
||||||
|
Unit *unit_by_id(Game *g, int id);
|
||||||
|
void unit_select_in_rect(Game *g, int x0, int y0, int x1, int y1, int add);
|
||||||
|
void unit_select_single(Game *g, int idx, int add);
|
||||||
|
void unit_clear_selection(Game *g);
|
||||||
|
void unit_select_all(Game *g, Faction f);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Buildings */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
int bld_spawn(Game *g, BuildingType t, Faction f, int tx, int ty);
|
||||||
|
void bld_remove(Game *g, int idx);
|
||||||
|
void bld_update(Game *g, float dt);
|
||||||
|
void bld_queue_unit(Game *g, int idx, UnitType t);
|
||||||
|
Building *bld_by_id(Game *g, int id);
|
||||||
|
int bld_can_place(Game *g, BuildingType t, int tx, int ty);
|
||||||
|
void bld_apply_damage(Game *g, Building *b, float dmg);
|
||||||
|
int bld_research(Game *g, Faction f, int up);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Projectiles & particles */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
void bullet_spawn(Game *g, float x, float y, int target_unit, int target_bld,
|
||||||
|
float dmg, Faction f, int kind);
|
||||||
|
void bullet_update(Game *g, float dt);
|
||||||
|
void particle_spawn(Game *g, float x, float y, int kind, uint8_t cr, uint8_t cg, uint8_t cb);
|
||||||
|
void particle_burst(Game *g, float x, float y, int n, int kind, uint8_t cr, uint8_t cg, uint8_t cb);
|
||||||
|
void particle_update(Game *g, float dt);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Rendering */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
typedef struct {
|
||||||
|
int x, y, w, h;
|
||||||
|
int kind; /* 0 = structure button, 1 = unit button */
|
||||||
|
int id; /* BuildingType or UnitType */
|
||||||
|
const char *label;
|
||||||
|
int enabled;
|
||||||
|
int cost;
|
||||||
|
} HudButton;
|
||||||
|
|
||||||
|
int render_init(Game *g);
|
||||||
|
void render_frame(Game *g);
|
||||||
|
void render_world(Game *g);
|
||||||
|
void render_units(Game *g);
|
||||||
|
void render_buildings(Game *g);
|
||||||
|
void render_bullets(Game *g);
|
||||||
|
void render_particles(Game *g);
|
||||||
|
void render_selection(Game *g);
|
||||||
|
void render_hud(Game *g);
|
||||||
|
void render_minimap(Game *g);
|
||||||
|
void render_text(Sprites *s, SDL_Renderer *r, TTF_Font *f,
|
||||||
|
const char *txt, int x, int y, SDL_Color c);
|
||||||
|
void render_build_menu(Game *g);
|
||||||
|
void hud_layout(Game *g, HudButton *out, int *n);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Input */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
void input_handle(Game *g, SDL_Event *e);
|
||||||
|
void input_camera(Game *g, float dt);
|
||||||
|
int input_screen_to_world(Game *g, int sx, int sy, float *wx, float *wy);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* AI */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
void ai_update(Game *g, float dt);
|
||||||
|
void ai_command(Faction f, int diff);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Audio */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
int audio_init(void);
|
||||||
|
void audio_shutdown(void);
|
||||||
|
void audio_sfx(int id); /* 0 move,1 shoot,2 explode,3 build,4 select,5 error */
|
||||||
|
void audio_music_start(void);
|
||||||
|
void audio_music_stop(void);
|
||||||
|
void audio_music_tick(float dt);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Save / load */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
int save_game(Game *g, const char *path);
|
||||||
|
int load_game(Game *g, const char *path);
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Engine */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
extern int g_headless; /* when 1, run simulation without any renderer */
|
||||||
|
extern int g_auto; /* when 1, the human faction is also AI-driven */
|
||||||
|
void engine_init(Game *g, const char *title);
|
||||||
|
void engine_shutdown(Game *g);
|
||||||
|
|
||||||
|
void engine_setup_match(Game *g, int mission_id);
|
||||||
|
void engine_update(Game *g, float dt);
|
||||||
|
void engine_check_win(Game *g);
|
||||||
|
void engine_log(const char *fmt, ...);
|
||||||
|
void game_log(Game *g, const char *fmt, ...);
|
||||||
|
|
||||||
|
/* Helpers */
|
||||||
|
int png_save(const char *path, int w, int h, const uint8_t *rgba);
|
||||||
|
void snapshot_capture(Game *g, const char *path);
|
||||||
|
Faction enemy_of(Faction f);
|
||||||
|
const char *faction_name(Faction f);
|
||||||
|
const char *unit_name(UnitType t, Faction f);
|
||||||
|
SDL_Color faction_color(Faction f);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* KKND_H */
|
||||||
83
engine-c/include/unit_card.h
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// openkknd - Unit Card System (SIMD Optimized)
|
||||||
|
// Header file for unit card implementation
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <immintrin.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char name[32];
|
||||||
|
char faction[16];
|
||||||
|
uint32_t cost;
|
||||||
|
uint32_t hp;
|
||||||
|
uint32_t max_hp;
|
||||||
|
uint32_t attack;
|
||||||
|
uint32_t defense;
|
||||||
|
uint32_t speed;
|
||||||
|
uint32_t range;
|
||||||
|
char description[256];
|
||||||
|
char image_path[128];
|
||||||
|
uint8_t unit_type;
|
||||||
|
uint8_t special_abilities_count;
|
||||||
|
char special_abilities[64][16];
|
||||||
|
} UnitCard;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
UnitCard **units;
|
||||||
|
size_t unit_count;
|
||||||
|
size_t capacity;
|
||||||
|
} UnitRegistry;
|
||||||
|
|
||||||
|
// Unit type constants
|
||||||
|
#define UNIT_INFANTRY 1
|
||||||
|
#define UNIT_VEHICLE 2
|
||||||
|
#define UNIT_SPECIAL 3
|
||||||
|
#define UNIT_ALL 0
|
||||||
|
|
||||||
|
// Flag for SIMD instructions
|
||||||
|
#define HAVE_AVX2 1
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Core unit functions
|
||||||
|
UnitCard* create_unit_card(const char* name, const char* faction,
|
||||||
|
uint32_t cost, uint32_t hp, uint32_t attack,
|
||||||
|
uint32_t defense, uint32_t speed, uint32_t range,
|
||||||
|
const char* description, const char* image,
|
||||||
|
uint8_t unit_type);
|
||||||
|
|
||||||
|
void free_unit_card(UnitCard* unit);
|
||||||
|
|
||||||
|
// Registry functions
|
||||||
|
UnitRegistry* create_registry();
|
||||||
|
void destroy_registry(UnitRegistry* registry);
|
||||||
|
|
||||||
|
int add_unit(UnitRegistry* registry, UnitCard* unit);
|
||||||
|
UnitCard* get_unit_by_name(UnitRegistry* registry, const char* name);
|
||||||
|
UnitCard** get_units_by_faction(UnitRegistry* registry, const char* faction,
|
||||||
|
size_t* out_count);
|
||||||
|
UnitCard** get_units_by_type(UnitRegistry* registry, uint8_t unit_type,
|
||||||
|
size_t* out_count);
|
||||||
|
|
||||||
|
// SIMD optimized functions
|
||||||
|
void spread_damage_SIMD(const UnitCard* units[], size_t unit_count,
|
||||||
|
uint32_t damage, uint32_t* remaining_hp);
|
||||||
|
|
||||||
|
void apply_healing_SIMD(UnitCard** units, size_t unit_count,
|
||||||
|
uint32_t heal_amount);
|
||||||
|
|
||||||
|
// Serialization functions
|
||||||
|
int save_units_to_json(UnitRegistry* registry, const char* filepath);
|
||||||
|
int load_units_from_json(UnitRegistry* registry, const char* filepath);
|
||||||
|
|
||||||
|
// Memory management
|
||||||
|
void unit_card_to_json(UnitCard* unit, char* buffer, size_t buffer_size);
|
||||||
|
UnitCard* json_to_unit_card(const char* json_string);
|
||||||
|
|
||||||
|
// Performance statistics
|
||||||
|
void print_performance_stats(UnitRegistry* registry);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
BIN
engine-c/openkknd
Executable file
BIN
engine-c/openkknd_dbg
Executable file
BIN
engine-c/savegame.kknd
Normal file
BIN
engine-c/shot_0001.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
124
engine-c/src/ai.c
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* ai.c - Enemy/human commander. Grows an economy and launches attack waves.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
static Building *ai_find_bld(Game *g, Faction f, BuildingType t) {
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead || b->faction != f || b->type != t) continue;
|
||||||
|
if (b->build < 1.0f) continue;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ai_count_units(Game *g, Faction f, int combat_only) {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead || u->faction != f) continue;
|
||||||
|
if (combat_only && (u->type == UT_HARVESTER)) continue;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ai_count_harvesters(Game *g, Faction f) {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead || u->faction != f) continue;
|
||||||
|
if (u->type == UT_HARVESTER) n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ai_run_faction(Game *g, Faction f) {
|
||||||
|
Building *base = ai_find_bld(g, f, BT_BASE);
|
||||||
|
Building *wf = ai_find_bld(g, f, BT_WARFACTORY);
|
||||||
|
Building *ref = ai_find_bld(g, f, BT_REFINERY);
|
||||||
|
if (!base) return;
|
||||||
|
|
||||||
|
/* early defensive turret */
|
||||||
|
if (g->time < 60 && g->scrap[f] > 600) {
|
||||||
|
int bx = base->tx + 3, by = base->ty - 2;
|
||||||
|
if (bld_can_place(g, BT_TURRET, bx, by)) {
|
||||||
|
if (bld_spawn(g, BT_TURRET, f, bx, by) >= 0)
|
||||||
|
g->scrap[f] -= bld_def(BT_TURRET)->cost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* research lab + upgrades */
|
||||||
|
if (!ai_find_bld(g, f, BT_RESEARCH) && g->scrap[f] > 400) {
|
||||||
|
int bx = base->tx - 3, by = base->ty + 2;
|
||||||
|
if (bld_can_place(g, BT_RESEARCH, bx, by)) {
|
||||||
|
int r = bld_spawn(g, BT_RESEARCH, f, bx, by);
|
||||||
|
if (r >= 0) g->scrap[f] -= bld_def(BT_RESEARCH)->cost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (g->research_lab[f] > 0 && g->research_cur[f] < 0) {
|
||||||
|
int best = -1, bestlv = 99;
|
||||||
|
for (int u = 0; u < UP_COUNT; u++) {
|
||||||
|
if (g->upg[f].lvl[u] >= UP_MAX_LVL) continue;
|
||||||
|
if (g->upg[f].lvl[u] < bestlv) { bestlv = g->upg[f].lvl[u]; best = u; }
|
||||||
|
}
|
||||||
|
if (best >= 0) bld_research(g, f, best);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keep harvesters flowing */
|
||||||
|
if (ref && ai_count_harvesters(g, f) < 4 && g->scrap[f] >= 300)
|
||||||
|
bld_queue_unit(g, (int)(ref - g->buildings), UT_HARVESTER);
|
||||||
|
|
||||||
|
/* pump combat units */
|
||||||
|
if (wf) {
|
||||||
|
int combat = ai_count_units(g, f, 1);
|
||||||
|
int cap = (g->diff == DIFF_HARD) ? 40 : (g->diff == DIFF_NORMAL ? 28 : 18);
|
||||||
|
if (combat < cap) {
|
||||||
|
int r = rng_range(&(RNG){0}, 0, 6);
|
||||||
|
UnitType pick = (r == 0) ? UT_BUGGY : (r == 1) ? UT_TANK
|
||||||
|
: (r == 2) ? UT_ARTILLERY : (r == 3) ? UT_JEEP
|
||||||
|
: (r == 4) ? UT_RAVAGER : (r == 5) ? UT_FLAME
|
||||||
|
: UT_MISSILE;
|
||||||
|
bld_queue_unit(g, (int)(wf - g->buildings), pick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* attack wave every ~35s once we have a force */
|
||||||
|
g->ai_wave[f] += (g->diff == DIFF_HARD ? 4.0f : (g->diff == DIFF_NORMAL ? 6.0f : 9.0f));
|
||||||
|
if (g->ai_wave[f] >= 35.0f) {
|
||||||
|
g->ai_wave[f] = 0;
|
||||||
|
Faction foe = (f == g->human) ? g->enemy : g->human;
|
||||||
|
int hx = g->map.spawn_x[foe], hy = g->map.spawn_y[foe];
|
||||||
|
float tx, ty; map_tile_to_world(hx, hy, &tx, &ty);
|
||||||
|
int launched = 0;
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead || u->faction != f) continue;
|
||||||
|
if (u->type == UT_HARVESTER) continue;
|
||||||
|
unit_issue_move(g, (int)(u - g->units), tx, ty, 1);
|
||||||
|
launched++;
|
||||||
|
}
|
||||||
|
if (launched > 0) {
|
||||||
|
engine_log("%s warband (%d) advances on the %s!",
|
||||||
|
faction_name(f), launched, faction_name(foe));
|
||||||
|
if (f == g->human)
|
||||||
|
game_log(g, "Warband (%d units) sent to attack!", launched);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ai_command(Faction f, int diff) { (void)f; (void)diff; }
|
||||||
|
|
||||||
|
void ai_update(Game *g, float dt) {
|
||||||
|
g->ai_timer -= dt;
|
||||||
|
if (g->ai_timer > 0) return;
|
||||||
|
|
||||||
|
float interval = (g->diff == DIFF_HARD) ? 4.0f :
|
||||||
|
(g->diff == DIFF_NORMAL ? 6.0f : 9.0f);
|
||||||
|
g->ai_timer = interval;
|
||||||
|
|
||||||
|
ai_run_faction(g, g->enemy);
|
||||||
|
if (g_auto) ai_run_faction(g, g->human);
|
||||||
|
}
|
||||||
76
engine-c/src/audio.c
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* audio.c - Procedural sound effects via SDL audio queue (no asset files).
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
static SDL_AudioDeviceID g_dev = 0;
|
||||||
|
static int g_audio_ok = 0;
|
||||||
|
|
||||||
|
int audio_init(void) {
|
||||||
|
SDL_AudioSpec want, have;
|
||||||
|
memset(&want, 0, sizeof want);
|
||||||
|
want.freq = 22050;
|
||||||
|
want.format = AUDIO_S16SYS;
|
||||||
|
want.channels = 1;
|
||||||
|
want.samples = 1024;
|
||||||
|
g_dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||||
|
if (g_dev == 0) { return 0; }
|
||||||
|
g_audio_ok = 1;
|
||||||
|
SDL_PauseAudioDevice(g_dev, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_shutdown(void) {
|
||||||
|
if (g_dev) SDL_CloseAudioDevice(g_dev);
|
||||||
|
g_dev = 0; g_audio_ok = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void queue_tone(float freq, float dur, float vol, int kind) {
|
||||||
|
if (!g_audio_ok) return;
|
||||||
|
int n = (int)(22050 * dur);
|
||||||
|
int16_t *buf = (int16_t *)calloc((size_t)n, sizeof(int16_t));
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
float t = (float)i / 22050.0f;
|
||||||
|
float env = (kind == 0) ? (1.0f - t / dur) : 1.0f;
|
||||||
|
float s = sinf(2*3.14159f*freq*t);
|
||||||
|
if (kind == 1) s = (sinf(2*3.14159f*freq*t) + 0.3f*sinf(2*3.14159f*freq*2*t));
|
||||||
|
if (kind == 2) s = (sinf(2*3.14159f*freq*t) * (1.0f - t/dur)) ;
|
||||||
|
buf[i] = (int16_t)(s * vol * 32767 * env);
|
||||||
|
}
|
||||||
|
SDL_QueueAudio(g_dev, buf, (Uint32)(n * sizeof(int16_t)));
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_sfx(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case 0: queue_tone(440, 0.08f, 0.25f, 0); break; /* move */
|
||||||
|
case 1: queue_tone(220, 0.06f, 0.30f, 1); break; /* shoot */
|
||||||
|
case 2: queue_tone(90, 0.35f, 0.45f, 2); break; /* boom */
|
||||||
|
case 3: queue_tone(660, 0.10f, 0.30f, 0); break; /* build */
|
||||||
|
case 4: queue_tone(880, 0.05f, 0.25f, 0); break; /* select */
|
||||||
|
case 5: queue_tone(140, 0.12f, 0.35f, 1); break; /* error */
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_music_start(void) { /* procedural drone; intentionally silent by default */ }
|
||||||
|
void audio_music_stop(void) { /* no-op */ }
|
||||||
|
|
||||||
|
/* simple looping wasteland bassline (procedural, no asset files) */
|
||||||
|
static float g_music_t = 0;
|
||||||
|
static int g_music_step = 0;
|
||||||
|
static const float g_bass[8] = { 110.0f, 0, 146.8f, 0, 130.8f, 0, 98.0f, 0 };
|
||||||
|
static const float g_mel[8] = { 0, 329.6f, 0, 392.0f, 0, 293.7f, 0, 261.6f };
|
||||||
|
|
||||||
|
void audio_music_tick(float dt) {
|
||||||
|
if (!g_audio_ok) return;
|
||||||
|
g_music_t += dt;
|
||||||
|
const float step_dur = 0.40f;
|
||||||
|
if (g_music_t < step_dur) return;
|
||||||
|
g_music_t -= step_dur;
|
||||||
|
int s = g_music_step % 8;
|
||||||
|
if (g_bass[s] > 0) queue_tone(g_bass[s], step_dur * 0.95f, 0.10f, 2);
|
||||||
|
if (g_mel[s] > 0) queue_tone(g_mel[s], step_dur * 0.55f, 0.045f, 0);
|
||||||
|
g_music_step++;
|
||||||
|
}
|
||||||
214
engine-c/src/buildings.c
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* buildings.c - Structures, placement, production and defensive fire.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
Building *bld_by_id(Game *g, int id) {
|
||||||
|
for (int i = 0; i < g->bld_count; i++)
|
||||||
|
if (!g->buildings[i].dead && g->buildings[i].id == id)
|
||||||
|
return &g->buildings[i];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bld_can_place(Game *g, BuildingType t, int tx, int ty) {
|
||||||
|
const BldDef *d = bld_def(t);
|
||||||
|
for (int y = ty; y < ty + d->h; y++)
|
||||||
|
for (int x = tx; x < tx + d->w; x++) {
|
||||||
|
if (!map_in_bounds(&g->map, x, y)) return 0;
|
||||||
|
Tile *tl = map_tile(&g->map, x, y);
|
||||||
|
if (!tl->walkable || tl->occupied) return 0;
|
||||||
|
if (tl->terrain == T_WATER || tl->terrain == T_ROCK) return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bld_spawn(Game *g, BuildingType t, Faction f, int tx, int ty) {
|
||||||
|
if (g->bld_count >= MAX_BUILDINGS) return -1;
|
||||||
|
if (!bld_can_place(g, t, tx, ty)) return -2;
|
||||||
|
const BldDef *d = bld_def(t);
|
||||||
|
Building *b = &g->buildings[g->bld_count++];
|
||||||
|
memset(b, 0, sizeof(Building));
|
||||||
|
b->id = g->bld_next_id++;
|
||||||
|
b->type = t; b->faction = f;
|
||||||
|
b->tx = tx; b->ty = ty; b->w = d->w; b->h = d->h;
|
||||||
|
b->max_hp = (float)d->hp;
|
||||||
|
b->build = 0; /* starts under construction */
|
||||||
|
b->hp = (float)d->hp * 0.1f;
|
||||||
|
b->producing = 0; b->prod_timer = 0; b->prod_total = 0;
|
||||||
|
b->queue_len = 0;
|
||||||
|
b->rally_x = (float)((tx + d->w/2) * TILE + TILE*2);
|
||||||
|
b->rally_y = (float)((ty + d->h/2) * TILE);
|
||||||
|
b->selected = 0; b->dead = 0; b->atk_cd = 0;
|
||||||
|
for (int y = ty; y < ty + d->h; y++)
|
||||||
|
for (int x = tx; x < tx + d->w; x++)
|
||||||
|
map_tile(&g->map, x, y)->occupied = 1;
|
||||||
|
return g->bld_count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bld_remove(Game *g, int idx) {
|
||||||
|
if (idx < 0 || idx >= g->bld_count) return;
|
||||||
|
Building *b = &g->buildings[idx];
|
||||||
|
for (int y = b->ty; y < b->ty + b->h; y++)
|
||||||
|
for (int x = b->tx; x < b->tx + b->w; x++) {
|
||||||
|
Tile *t = map_tile(&g->map, x, y);
|
||||||
|
if (t) t->occupied = 0;
|
||||||
|
}
|
||||||
|
if (b->selected) {
|
||||||
|
for (int i = 0; i < g->sel_count; i++)
|
||||||
|
if (g->selection[i] == b->id) {
|
||||||
|
g->selection[i] = g->selection[g->sel_count-1];
|
||||||
|
g->sel_count--; i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g->buildings[idx] = g->buildings[g->bld_count - 1];
|
||||||
|
g->bld_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bld_apply_damage(Game *g, Building *b, float dmg) {
|
||||||
|
float eff = dmg - 3.0f;
|
||||||
|
if (eff < 1) eff = 1;
|
||||||
|
b->hp -= eff;
|
||||||
|
particle_burst(g, (float)(b->tx*TILE+b->w*TILE/2),
|
||||||
|
(float)(b->ty*TILE+b->h*TILE/2), 2, 2, 255, 200, 80);
|
||||||
|
if (b->hp <= 0 && !b->dead) {
|
||||||
|
b->dead = 1;
|
||||||
|
particle_burst(g, (float)(b->tx*TILE+b->w*TILE/2),
|
||||||
|
(float)(b->ty*TILE+b->h*TILE/2), 20, 1, 255, 140, 40);
|
||||||
|
particle_burst(g, (float)(b->tx*TILE+b->w*TILE/2),
|
||||||
|
(float)(b->ty*TILE+b->h*TILE/2), 12, 3, 90, 80, 70);
|
||||||
|
audio_sfx(2);
|
||||||
|
if (g->cam.shake < 9) g->cam.shake = 9;
|
||||||
|
if (b->faction == g->human)
|
||||||
|
game_log(g, "Structure lost: %s", bld_def(b->type)->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bld_queue_unit(Game *g, int idx, UnitType t) {
|
||||||
|
Building *b = &g->buildings[idx];
|
||||||
|
const BldDef *d = bld_def(b->type);
|
||||||
|
int allowed = 0;
|
||||||
|
for (int i = 0; i < d->produces_count; i++)
|
||||||
|
if (d->produces[i] == t) { allowed = 1; break; }
|
||||||
|
if (!allowed) { audio_sfx(5); return; }
|
||||||
|
if (b->queue_len >= MAX_SELECTION) { audio_sfx(5); return; }
|
||||||
|
const UnitDef *ud = unit_def(t, b->faction);
|
||||||
|
if (g->scrap[b->faction] < ud->cost) { audio_sfx(5); return; }
|
||||||
|
b->queue[b->queue_len++] = t;
|
||||||
|
g->scrap[b->faction] -= ud->cost;
|
||||||
|
audio_sfx(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bld_update(Game *g, float dt) {
|
||||||
|
for (int f = 0; f < MAX_FACTIONS; f++) g->research_lab[f] = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead) continue;
|
||||||
|
const BldDef *d = bld_def(b->type);
|
||||||
|
if (d->is_research && b->build >= 1.0f) g->research_lab[b->faction]++;
|
||||||
|
|
||||||
|
/* construction progress (faster with Logistics upgrade) */
|
||||||
|
if (b->build < 1.0f) {
|
||||||
|
float rate = (float)d->build_time;
|
||||||
|
rate /= (1.0f + 0.12f * (float)g->upg[b->faction].lvl[UP_BUILD]);
|
||||||
|
b->build += dt / rate;
|
||||||
|
if (b->build >= 1.0f) { b->build = 1.0f; b->hp = b->max_hp; audio_sfx(3); }
|
||||||
|
else b->hp = b->max_hp * (0.1f + 0.9f * b->build);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b->atk_cd > 0) b->atk_cd -= dt;
|
||||||
|
|
||||||
|
/* turret auto-fire */
|
||||||
|
if (d->is_turret && b->build >= 1.0f && b->atk_cd <= 0) {
|
||||||
|
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||||
|
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||||
|
int best = -1; float bestd = d->range * d->range;
|
||||||
|
for (int j = 0; j < g->unit_count; j++) {
|
||||||
|
Unit *u = &g->units[j];
|
||||||
|
if (u->dead || u->faction == b->faction) continue;
|
||||||
|
float dd = kknd_dist2(bx, by, u->x, u->y);
|
||||||
|
if (dd < bestd) { bestd = dd; best = u->id; }
|
||||||
|
}
|
||||||
|
if (best >= 0) {
|
||||||
|
bullet_spawn(g, bx, by, best, -1, d->atk, b->faction, 2);
|
||||||
|
b->atk_cd = 0.6f;
|
||||||
|
audio_sfx(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* production (faster with Logistics upgrade) */
|
||||||
|
if (b->build >= 1.0f && b->producing == 0 && b->queue_len > 0) {
|
||||||
|
UnitType t = (UnitType)b->queue[0];
|
||||||
|
const UnitDef *ud = unit_def(t, b->faction);
|
||||||
|
b->producing = t;
|
||||||
|
b->prod_total = (float)ud->build_time;
|
||||||
|
b->prod_timer = 0;
|
||||||
|
for (int k = 0; k < b->queue_len - 1; k++) b->queue[k] = b->queue[k+1];
|
||||||
|
b->queue_len--;
|
||||||
|
}
|
||||||
|
if (b->producing != 0) {
|
||||||
|
const UnitDef *ud = unit_def((UnitType)b->producing, b->faction);
|
||||||
|
float total = (float)ud->build_time;
|
||||||
|
total /= (1.0f + 0.12f * (float)g->upg[b->faction].lvl[UP_BUILD]);
|
||||||
|
b->prod_timer += dt;
|
||||||
|
if (b->prod_timer >= total) {
|
||||||
|
unit_spawn(g, (UnitType)b->producing, b->faction, b->rally_x, b->rally_y);
|
||||||
|
b->producing = 0; b->prod_timer = 0;
|
||||||
|
audio_sfx(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* repair depot heals nearby units */
|
||||||
|
if (d->type == BT_REPAIR && b->build >= 1.0f) {
|
||||||
|
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||||
|
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||||
|
for (int j = 0; j < g->unit_count; j++) {
|
||||||
|
Unit *u = &g->units[j];
|
||||||
|
if (u->dead || u->faction != b->faction) continue;
|
||||||
|
if (kknd_dist2(bx, by, u->x, u->y) < (TILE*5)*(TILE*5) &&
|
||||||
|
u->hp < u->max_hp) {
|
||||||
|
u->hp += dt * 15.0f;
|
||||||
|
if (u->hp > u->max_hp) u->hp = u->max_hp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* research progression */
|
||||||
|
for (int f = 0; f < MAX_FACTIONS; f++) {
|
||||||
|
if (g->research_lab[f] <= 0) { g->research_cur[f] = -1; g->research_prog[f] = 0; continue; }
|
||||||
|
int up = g->research_cur[f];
|
||||||
|
if (up < 0) continue;
|
||||||
|
int lvl = g->upg[f].lvl[up];
|
||||||
|
if (lvl >= UP_MAX_LVL) { g->research_cur[f] = -1; g->research_prog[f] = 0; continue; }
|
||||||
|
g->research_prog[f] += dt * (float)g->research_lab[f] / 18.0f;
|
||||||
|
if (g->research_prog[f] >= 1.0f) {
|
||||||
|
g->upg[f].lvl[up] = lvl + 1;
|
||||||
|
g->research_prog[f] = 0;
|
||||||
|
g->research_cur[f] = -1;
|
||||||
|
audio_sfx(3);
|
||||||
|
engine_log("%s researched %s (lvl %d)", faction_name((Faction)f),
|
||||||
|
research_name(up), lvl + 1);
|
||||||
|
if ((Faction)f == g->human)
|
||||||
|
game_log(g, "Research complete: %s (lvl %d)", research_name(up), lvl + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = g->bld_count - 1; i >= 0; i--)
|
||||||
|
if (g->buildings[i].dead) bld_remove(g, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bld_research(Game *g, Faction f, int up) {
|
||||||
|
if (up < 0 || up >= UP_COUNT) return 0;
|
||||||
|
if (g->research_lab[f] <= 0) return 0;
|
||||||
|
if (g->research_cur[f] >= 0) return 0;
|
||||||
|
if (g->upg[f].lvl[up] >= UP_MAX_LVL) return 0;
|
||||||
|
int cost = research_cost(up, g->upg[f].lvl[up]);
|
||||||
|
if (g->scrap[f] < cost) { audio_sfx(5); return 0; }
|
||||||
|
g->scrap[f] -= cost;
|
||||||
|
g->research_cur[f] = up;
|
||||||
|
g->research_prog[f] = 0;
|
||||||
|
audio_sfx(3);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
273
engine-c/src/config.c
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* config.c - Unit & building statistics, themed on KKND 1 (recreated).
|
||||||
|
*
|
||||||
|
* Two factions are modelled after the original game:
|
||||||
|
* FACTION_SURVIVOR - underground military remnants, high-tech war machines
|
||||||
|
* FACTION_EVOLVED - surface mutant tribes, warped animals & beasts
|
||||||
|
* The resource is "Scrap" (salvaged post-war materiel), harvested from
|
||||||
|
* scrap fields by harvesters and processed at refineries.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
static const UnitDef UNIT_DEFS[] = {
|
||||||
|
/* ---- Survivor war machines ---- */
|
||||||
|
{ "Rifleman", UT_INFANTRY, FACTION_SURVIVOR, 50, 400, 40, 5, 62, 96, 220, 0, 2, AB_NONE,
|
||||||
|
"Booted foot soldier with a rapid rifle." },
|
||||||
|
{ "Scout Car", UT_BUGGY, FACTION_SURVIVOR, 200, 600, 55, 10, 132, 112, 260, 0, 6, AB_NONE,
|
||||||
|
"Fast lightly-armoured recon buggy with twin MG." },
|
||||||
|
{ "Battle Tank",UT_TANK, FACTION_SURVIVOR, 500, 1400,130, 40, 82, 132, 280, 0, 12, AB_SIEGE,
|
||||||
|
"Heavily armoured main battle tank. [Siege] boosts range." },
|
||||||
|
{ "Hammer", UT_ARTILLERY, FACTION_SURVIVOR, 450, 700,160, 15, 60, 264, 330, 0, 14, AB_SIEGE,
|
||||||
|
"Long-range self-propelled artillery. [Siege] extends reach." },
|
||||||
|
{ "Scavenger", UT_HARVESTER, FACTION_SURVIVOR, 300, 900, 0, 10, 72, 0, 200, 1000, 8, AB_NONE,
|
||||||
|
"Armoured truck that hauls scrap to the refinery." },
|
||||||
|
{ "Medic", UT_MEDIC, FACTION_SURVIVOR, 250, 600, 0, 10, 90, 120, 220, 0, 7, AB_HEAL,
|
||||||
|
"Field medic that pulses repairs to nearby allies." },
|
||||||
|
{ "Pathfinder", UT_JEEP, FACTION_SURVIVOR, 150, 500, 45, 8, 150, 100, 240, 0, 5, AB_NONE,
|
||||||
|
"Fast scout jeep with a roof-mounted machine gun." },
|
||||||
|
{ "Mammoth", UT_RAVAGER, FACTION_SURVIVOR, 800, 2600,200, 70, 70, 140, 300, 0, 18, AB_NONE,
|
||||||
|
"Super-heavy siege tank that levels anything." },
|
||||||
|
{ "Flamer", UT_FLAME, FACTION_SURVIVOR, 250, 700, 90, 12, 70, 52, 200, 0, 7, AB_NONE,
|
||||||
|
"Flame trooper that clears clustered enemies." },
|
||||||
|
{ "Rocketeer", UT_MISSILE, FACTION_SURVIVOR, 350, 650,120, 10, 64, 220, 300, 0, 10, AB_NONE,
|
||||||
|
"Mobile rocket launcher with punishing range." },
|
||||||
|
|
||||||
|
/* ---- Evolved mutant warbeasts ---- */
|
||||||
|
{ "Berserker", UT_INFANTRY, FACTION_EVOLVED, 40, 320, 40, 4, 62, 96, 220, 0, 2, AB_NONE,
|
||||||
|
"Barefoot mutant warrior loosing mutated arrows." },
|
||||||
|
{ "Razorbeast", UT_BUGGY, FACTION_EVOLVED, 200, 650, 60, 12, 142, 104, 260, 0, 6, AB_NONE,
|
||||||
|
"Mutated quadruped that charges and tears." },
|
||||||
|
{ "Ravager", UT_TANK, FACTION_EVOLVED, 500, 1500,140, 45, 76, 126, 280, 0, 12, AB_SIEGE,
|
||||||
|
"Hulking armored brute of the mutant horde. [Siege] boosts range." },
|
||||||
|
{ "Spitter", UT_ARTILLERY, FACTION_EVOLVED, 450, 720,150, 14, 56, 258, 330, 0, 14, AB_SIEGE,
|
||||||
|
"Acid-spitting mutant with lobbing attack. [Siege] extends reach." },
|
||||||
|
{ "Gnawer", UT_HARVESTER, FACTION_EVOLVED, 300, 900, 0, 10, 72, 0, 200, 1000, 8, AB_NONE,
|
||||||
|
"Swarm-beast that drags scrap back to the hive." },
|
||||||
|
{ "Mendbeast", UT_MEDIC, FACTION_EVOLVED, 250, 600, 0, 10, 88, 118, 220, 0, 7, AB_HEAL,
|
||||||
|
"Regenerating mutant that heals the brood around it." },
|
||||||
|
{ "Cackler", UT_MUTANT, FACTION_EVOLVED, 60, 360, 38, 5, 70, 92, 220, 0, 3, AB_NONE,
|
||||||
|
"Skittering mutant with a crude slug-thrower." },
|
||||||
|
{ "Loper", UT_BEAST, FACTION_EVOLVED, 180, 620, 58, 10, 150, 100, 240, 0, 5, AB_NONE,
|
||||||
|
"Loping pack-hunter beast that strikes fast." },
|
||||||
|
{ "Warlord", UT_RAVAGER, FACTION_EVOLVED, 800, 2700,210, 75, 68, 134, 300, 0, 18, AB_NONE,
|
||||||
|
"Mountain-sized mutant warlord of the horde." },
|
||||||
|
{ "Pyro", UT_FLAME, FACTION_EVOLVED, 250, 700, 90, 12, 70, 52, 200, 0, 7, AB_NONE,
|
||||||
|
"Mutant with a gout of war-fire for close work." },
|
||||||
|
{ "Spore", UT_MISSILE, FACTION_EVOLVED, 350, 650,120, 10, 64, 220, 300, 0, 10, AB_NONE,
|
||||||
|
"Fungal spore-launcher with punishing range." },
|
||||||
|
|
||||||
|
/* ---- Series 9: rogue war machines (third faction) ---- */
|
||||||
|
{ "S9 Drone", UT_INFANTRY, FACTION_SERIES9, 50, 380, 42, 6, 64, 98, 220, 0, 2, AB_NONE,
|
||||||
|
"Autonomous combat drone with a pulse repeater." },
|
||||||
|
{ "S9 Crawler", UT_BUGGY, FACTION_SERIES9, 200, 620, 58, 12, 140, 108, 260, 0, 6, AB_NONE,
|
||||||
|
"Treaded recon crawler with twin autocannons." },
|
||||||
|
{ "S9 Crusher", UT_TANK, FACTION_SERIES9, 500, 1500,135, 45, 80, 130, 280, 0, 12, AB_SIEGE,
|
||||||
|
"Heavy war-robot crush-tank. [Siege] boosts range." },
|
||||||
|
{ "S9 Mortar", UT_ARTILLERY, FACTION_SERIES9, 450, 720,155, 16, 58, 262, 330, 0, 14, AB_SIEGE,
|
||||||
|
"Self-levelling robotic mortar platform. [Siege] extends reach." },
|
||||||
|
{ "S9 Hauler", UT_HARVESTER, FACTION_SERIES9, 300, 900, 0, 10, 72, 0, 200, 1000, 8, AB_NONE,
|
||||||
|
"Salvage automaton that hauls scrap to the foundry." },
|
||||||
|
{ "S9 Mender", UT_MEDIC, FACTION_SERIES9, 250, 600, 0, 10, 90, 120, 220, 0, 7, AB_HEAL,
|
||||||
|
"Repair automaton that welds wounded machines." },
|
||||||
|
{ "S9 Scorch", UT_FLAME, FACTION_SERIES9, 250, 700, 95, 12, 70, 52, 200, 0, 7, AB_NONE,
|
||||||
|
"Incendiary war-bot that melts infantry." },
|
||||||
|
{ "S9 Rocket", UT_MISSILE, FACTION_SERIES9, 350, 650,125, 10, 64, 220, 300, 0, 10, AB_NONE,
|
||||||
|
"Shoulder-mounted rocket drone with long reach." },
|
||||||
|
|
||||||
|
{ NULL, UT_NONE, FACTION_NONE, 0,0,0,0,0,0,0,0, 0, AB_NONE, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const BldDef BLD_DEFS[] = {
|
||||||
|
{ "Command Centre", BT_BASE, 0, 0, 5000, 2, 2, 0, 12, {0},0, 0, 0, 0, 0,
|
||||||
|
"Faction headquarters. Trains infantry, provides pop." },
|
||||||
|
{ "War Factory", BT_WARFACTORY, 400, 12, 2500, 2, 2, 0, 8,
|
||||||
|
{ UT_BUGGY, UT_TANK, UT_ARTILLERY, UT_JEEP, UT_RAVAGER, UT_FLAME, UT_MISSILE, UT_MEDIC }, 8, 0, 0, 0, 0,
|
||||||
|
"Builds ground war machines." },
|
||||||
|
{ "Refinery", BT_REFINERY, 300, 10, 2000, 2, 2, 0, 6,
|
||||||
|
{ UT_HARVESTER }, 1, 0, 0, 0, 0,
|
||||||
|
"Processes scrap delivered by harvesters." },
|
||||||
|
{ "Power Plant", BT_POWER, 200, 8, 1500, 1, 1, 100, 4, {0},0, 0, 0, 0, 0,
|
||||||
|
"Generates power for the war effort." },
|
||||||
|
{ "Research Lab", BT_RESEARCH, 400, 12, 1500, 2, 2, 0, 0, {0},0, 0, 0, 0, 1,
|
||||||
|
"Researches damage, armour, speed, reload and build upgrades." },
|
||||||
|
{ "Repair Depot", BT_REPAIR, 300, 10, 1500, 2, 2, 0, 4, {0},0, 0, 0, 0, 0,
|
||||||
|
"Slowly repairs damaged units in range." },
|
||||||
|
{ "Turret", BT_TURRET, 250, 6, 1800, 1, 1, 0, 3, {0},0, 1, 60, 200, 0,
|
||||||
|
"Automated defensive gun emplacement." },
|
||||||
|
{ "Wall", BT_WALL, 25, 2, 800, 1, 1, 0, 0, {0},0, 0, 0, 0, 0,
|
||||||
|
"Cheap blast wall to channel the enemy." },
|
||||||
|
{ NULL, BT_NONE, 0,0,0,0,0,0,0,{0},0,0,0,0,0, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static UnitRegistry *g_unit_cards = NULL;
|
||||||
|
static UnitRegistry *g_bld_cards = NULL;
|
||||||
|
|
||||||
|
const UnitDef *unit_def(UnitType t, Faction f) {
|
||||||
|
for (int i = 0; UNIT_DEFS[i].name; i++) {
|
||||||
|
if (UNIT_DEFS[i].type == t && UNIT_DEFS[i].faction == f)
|
||||||
|
return &UNIT_DEFS[i];
|
||||||
|
}
|
||||||
|
/* fall back to survivor stats for unknown pairings */
|
||||||
|
for (int i = 0; UNIT_DEFS[i].name; i++)
|
||||||
|
if (UNIT_DEFS[i].type == t && UNIT_DEFS[i].faction == FACTION_SURVIVOR)
|
||||||
|
return &UNIT_DEFS[i];
|
||||||
|
return &UNIT_DEFS[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const BldDef *bld_def(BuildingType t) {
|
||||||
|
for (int i = 0; BLD_DEFS[i].name; i++)
|
||||||
|
if (BLD_DEFS[i].type == t) return &BLD_DEFS[i];
|
||||||
|
return &BLD_DEFS[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_init(void) {
|
||||||
|
if (g_unit_cards) return;
|
||||||
|
g_unit_cards = create_registry();
|
||||||
|
g_bld_cards = create_registry();
|
||||||
|
|
||||||
|
/* Build the unit-card registry from the same tables so the merged
|
||||||
|
* unit_card system stays the authoritative stats source. */
|
||||||
|
for (int i = 0; UNIT_DEFS[i].name; i++) {
|
||||||
|
const UnitDef *d = &UNIT_DEFS[i];
|
||||||
|
uint8_t ut = (d->type == UT_INFANTRY) ? UNIT_INFANTRY
|
||||||
|
: (d->type == UT_HARVESTER) ? UNIT_SPECIAL
|
||||||
|
: UNIT_VEHICLE;
|
||||||
|
UnitCard *c = create_unit_card(
|
||||||
|
d->name, faction_name(d->faction), (uint32_t)d->cost,
|
||||||
|
(uint32_t)d->hp, (uint32_t)d->attack, (uint32_t)d->defense,
|
||||||
|
(uint32_t)d->speed, (uint32_t)d->range, d->desc,
|
||||||
|
"assets/units/proc", ut);
|
||||||
|
if (c) add_unit(g_unit_cards, c);
|
||||||
|
}
|
||||||
|
for (int i = 0; BLD_DEFS[i].name; i++) {
|
||||||
|
const BldDef *d = &BLD_DEFS[i];
|
||||||
|
UnitCard *c = create_unit_card(
|
||||||
|
d->name, "Building", (uint32_t)d->cost, (uint32_t)d->hp,
|
||||||
|
(uint32_t)(d->is_turret ? d->atk : 0), 0,
|
||||||
|
(uint32_t)d->build_time, 0, d->desc,
|
||||||
|
"assets/buildings/proc", UNIT_SPECIAL);
|
||||||
|
if (c) add_unit(g_bld_cards, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitRegistry *unit_card_registry(void) { return g_unit_cards; }
|
||||||
|
UnitRegistry *bld_card_registry(void) { return g_bld_cards; }
|
||||||
|
|
||||||
|
Faction enemy_of(Faction f) {
|
||||||
|
if (f == FACTION_SURVIVOR) return FACTION_EVOLVED;
|
||||||
|
if (f == FACTION_EVOLVED) return FACTION_SURVIVOR;
|
||||||
|
return FACTION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *faction_name(Faction f) {
|
||||||
|
switch (f) {
|
||||||
|
case FACTION_SURVIVOR: return "Survivors";
|
||||||
|
case FACTION_EVOLVED: return "Evolved";
|
||||||
|
case FACTION_SERIES9: return "Series 9";
|
||||||
|
default: return "Neutral";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *unit_name(UnitType t, Faction f) {
|
||||||
|
return unit_def(t, f)->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Color faction_color(Faction f) {
|
||||||
|
switch (f) {
|
||||||
|
case FACTION_SURVIVOR: return (SDL_Color){ 90, 160, 70, 255 }; /* olive green */
|
||||||
|
case FACTION_EVOLVED: return (SDL_Color){170, 70, 180, 255 }; /* mutant purple */
|
||||||
|
case FACTION_SERIES9: return (SDL_Color){200, 200, 60, 255 };
|
||||||
|
default: return (SDL_Color){140, 140, 140, 255 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Campaign / missions */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
static const Mission MISSIONS[] = {
|
||||||
|
{ 0, "First Strike",
|
||||||
|
"The mutant Evolved have crawled from the irradiated wasteland. "
|
||||||
|
"Crush their hive and secure the scrap fields before they dig in.",
|
||||||
|
OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_NORMAL, 0, 0, 0, 1337u },
|
||||||
|
{ 1, "Hold the Line",
|
||||||
|
"Survive the relentless Evolved assault for eight minutes while your "
|
||||||
|
"own war machine gears up in the rear.",
|
||||||
|
OBJ_SURVIVE, FACTION_EVOLVED, DIFF_NORMAL, 0, 480.0f, 1, 24601u },
|
||||||
|
{ 2, "Scrap Rush",
|
||||||
|
"Out-produce the Evolved: stockpile 6000 scrap before their warbands "
|
||||||
|
"overrun your refinery line.",
|
||||||
|
OBJ_GATHER, FACTION_EVOLVED, DIFF_NORMAL, 6000, 0, 2, 9001u },
|
||||||
|
{ 3, "Hardened Nest",
|
||||||
|
"A fortified Evolved brood holds the far wastes behind walls of rubble. "
|
||||||
|
"Tear the nest down on Hard difficulty.",
|
||||||
|
OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_HARD, 0, 0, 3, 31337u },
|
||||||
|
{ 4, "Rogue Machines",
|
||||||
|
"The Series 9 war machines have awakened and turned on everyone. "
|
||||||
|
"Crush the rogue robots before they scrape the wastes bare.",
|
||||||
|
OBJ_DESTROY_ALL, FACTION_SERIES9, DIFF_NORMAL, 0, 0, 2, 667u },
|
||||||
|
{ 5, "Crossfire",
|
||||||
|
"The Evolved and Series 9 are at each other's throats. Pick your moment, "
|
||||||
|
"then smash the survivor of their war.",
|
||||||
|
OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_HARD, 0, 0, 1, 555u },
|
||||||
|
{ 6, "Scorched Earth",
|
||||||
|
"A scorched plateau offers no cover. Gather 9000 scrap in the open "
|
||||||
|
"while two enemy factions hunt you.",
|
||||||
|
OBJ_GATHER, FACTION_SERIES9, DIFF_HARD, 9000, 0, 0, 808u },
|
||||||
|
{ 7, "Last Stand",
|
||||||
|
"Your perimeter is the only thing left. Hold for twelve minutes against "
|
||||||
|
"a three-way onslaught.",
|
||||||
|
OBJ_SURVIVE, FACTION_EVOLVED, DIFF_HARD, 0, 720.0f, 3, 404u },
|
||||||
|
{ 8, "Deep Recon",
|
||||||
|
"Push into the mutant swamps and destroy the Evolved command links. "
|
||||||
|
"The terrain hides ambushes at every turn.",
|
||||||
|
OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_NORMAL, 0, 0, 1, 2718u },
|
||||||
|
{ 9, "Machine Purge",
|
||||||
|
"Series 9 has massed a legion of scrap-hungry tanks. Wipe the assembly "
|
||||||
|
"grounds from the map.",
|
||||||
|
OBJ_DESTROY_ALL, FACTION_SERIES9, DIFF_HARD, 0, 0, 2, 90210u },
|
||||||
|
{ 10, "Broken Alliance",
|
||||||
|
"The Evolved courted the machines and lost. Now both want you dead. "
|
||||||
|
"End the pact by force.",
|
||||||
|
OBJ_DESTROY_ALL, FACTION_SERIES9, DIFF_HARD, 0, 0, 3, 1337u },
|
||||||
|
{ 11, "Final Approach",
|
||||||
|
"The enemy heartland lies ahead, bristling with turrets and walls. "
|
||||||
|
"Break through and destroy their core.",
|
||||||
|
OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_HARD, 0, 0, 3, 4242u },
|
||||||
|
{ 12, "Krush, Kill 'n' Destroy",
|
||||||
|
"All-out war. Every faction fields its finest. Be the last army "
|
||||||
|
"standing on the poisoned earth.",
|
||||||
|
OBJ_DESTROY_ALL, FACTION_SERIES9, DIFF_HARD, 0, 0, 0, 1u },
|
||||||
|
{ -1, NULL, NULL, 0, FACTION_NONE, 0, 0, 0, 0, 0u }
|
||||||
|
};
|
||||||
|
|
||||||
|
int mission_count(void) {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; MISSIONS[i].name; i++) n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Mission *mission_get(int id) {
|
||||||
|
for (int i = 0; MISSIONS[i].name; i++)
|
||||||
|
if (MISSIONS[i].id == id) return &MISSIONS[i];
|
||||||
|
return &MISSIONS[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
/* Research */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
|
const char *research_name(int up) {
|
||||||
|
switch (up) {
|
||||||
|
case UP_DAMAGE: return "Munitions";
|
||||||
|
case UP_ARMOR: return "Plating";
|
||||||
|
case UP_SPEED: return "Drivetrain";
|
||||||
|
case UP_RELOAD: return "Autoloaders";
|
||||||
|
case UP_BUILD: return "Logistics";
|
||||||
|
default: return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int research_cost(int up, int level) {
|
||||||
|
(void)up;
|
||||||
|
return 250 * (level + 1);
|
||||||
|
}
|
||||||
55
engine-c/src/demo_unitcard.c
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "unit_card.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("=== openkknd - Demo Game ===\n");
|
||||||
|
printf("KKND-style RTS Game Engine\n");
|
||||||
|
printf("Version 1.0.0\n");
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
// Create unit registry
|
||||||
|
UnitRegistry* registry = create_registry();
|
||||||
|
|
||||||
|
// Test unit creation
|
||||||
|
UnitCard* marine = create_unit_card(
|
||||||
|
"Terran Marine", "Allied", 100, 100, 15,
|
||||||
|
12, 4, 6, "Standard Terran infantry",
|
||||||
|
"assets/units/marine.png", UNIT_INFANTRY
|
||||||
|
);
|
||||||
|
|
||||||
|
if (marine) {
|
||||||
|
printf("✓ Created unit: %s (%s)\n", marine->name, marine->faction);
|
||||||
|
printf(" Cost: %d, HP: %d, Attack: %d, Defense: %d, Speed: %d, Range: %d\n",
|
||||||
|
marine->cost, marine->hp, marine->attack, marine->defense,
|
||||||
|
marine->speed, marine->range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test unit addition
|
||||||
|
if (add_unit(registry, marine) == 0) {
|
||||||
|
printf("✓ Unit added to registry\n");
|
||||||
|
|
||||||
|
// Print registry statistics
|
||||||
|
print_performance_stats(registry);
|
||||||
|
|
||||||
|
// Test unit lookup
|
||||||
|
UnitCard* found = get_unit_by_name(registry, "Terran Marine");
|
||||||
|
if (found) {
|
||||||
|
printf("✓ Unit lookup successful: %s\n", found->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
destroy_registry(registry);
|
||||||
|
free_unit_card(marine);
|
||||||
|
|
||||||
|
printf("\n✓ Demo game completed successfully!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n✗ Demo game failed\n");
|
||||||
|
destroy_registry(registry);
|
||||||
|
if (marine) free_unit_card(marine);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
188
engine-c/src/engine.c
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* engine.c - Match setup, main simulation step and win/lose logic.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
int g_headless = 0;
|
||||||
|
int g_auto = 0;
|
||||||
|
|
||||||
|
void engine_log(const char *fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
char buf[512];
|
||||||
|
vsnprintf(buf, sizeof buf, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
printf("[kknd] %s\n", buf);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_log(Game *g, const char *fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(g->log_msg[g->log_next], 96, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
g->log_next = (g->log_next + 1) % 32;
|
||||||
|
if (g->log_count < 32) g->log_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void engine_init(Game *g, const char *title) {
|
||||||
|
memset(g, 0, sizeof(Game));
|
||||||
|
g->wrecks = (Wreck *)calloc(MAX_WRECKS, sizeof(Wreck));
|
||||||
|
config_init();
|
||||||
|
if (!g_headless) {
|
||||||
|
audio_init();
|
||||||
|
if (!render_init(g)) {
|
||||||
|
fprintf(stderr, "Failed to initialise renderer\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(void)title;
|
||||||
|
}
|
||||||
|
g->state = GAME_MENU;
|
||||||
|
g->human = FACTION_SURVIVOR;
|
||||||
|
g->diff = DIFF_NORMAL;
|
||||||
|
g->speed_mul = 1.0f;
|
||||||
|
g->unlocked = 1;
|
||||||
|
g->menu_sel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void engine_shutdown(Game *g) {
|
||||||
|
free(g->wrecks);
|
||||||
|
sprites_free(&g->spr);
|
||||||
|
if (g->ren) SDL_DestroyRenderer(g->ren);
|
||||||
|
if (g->win) SDL_DestroyWindow(g->win);
|
||||||
|
audio_shutdown();
|
||||||
|
SDL_Quit();
|
||||||
|
map_free(&g->map);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void place_start(Game *g, Faction f) {
|
||||||
|
int sx = g->map.spawn_x[f], sy = g->map.spawn_y[f];
|
||||||
|
int bx = sx - 1, by = sy - 1;
|
||||||
|
bld_spawn(g, BT_BASE, f, bx, by);
|
||||||
|
bld_spawn(g, BT_REFINERY, f, bx, sy + 2);
|
||||||
|
bld_spawn(g, BT_WARFACTORY, f, sx + 2, by);
|
||||||
|
bld_spawn(g, BT_POWER, f, sx + 2, sy + 2);
|
||||||
|
|
||||||
|
float wx, wy; map_tile_to_world(sx, sy, &wx, &wy);
|
||||||
|
/* harvesters */
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
unit_spawn(g, UT_HARVESTER, f, wx + (i*24-12), wy + 30);
|
||||||
|
/* starting infantry */
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
unit_spawn(g, (f==FACTION_SURVIVOR?UT_INFANTRY:UT_INFANTRY), f,
|
||||||
|
wx - 40 + i*20, wy - 20);
|
||||||
|
/* a couple of buggies */
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
unit_spawn(g, (f==FACTION_SURVIVOR?UT_BUGGY:UT_BUGGY), f,
|
||||||
|
wx - 30 + i*30, wy - 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
void engine_setup_match(Game *g, int mission_id) {
|
||||||
|
const Mission *m = mission_get(mission_id);
|
||||||
|
g->mission_id = m->id;
|
||||||
|
g->objective = m->objective;
|
||||||
|
g->diff = (Difficulty)m->diff;
|
||||||
|
g->obj_timer = 0;
|
||||||
|
|
||||||
|
/* resolve the enemy faction (never the player's own side) */
|
||||||
|
Faction enemy = m->enemy;
|
||||||
|
if (enemy == g->human)
|
||||||
|
enemy = (g->human == FACTION_SURVIVOR) ? FACTION_EVOLVED : FACTION_SURVIVOR;
|
||||||
|
g->enemy = enemy;
|
||||||
|
|
||||||
|
RNG rng;
|
||||||
|
rng_seed(&rng, (uint64_t)m->seed ^ 0x4B4B4E44ULL);
|
||||||
|
g->map.style = m->map_style;
|
||||||
|
map_init(&g->map, &rng);
|
||||||
|
|
||||||
|
g->unit_count = 0; g->unit_next_id = 1;
|
||||||
|
g->bld_count = 0; g->bld_next_id = 1;
|
||||||
|
g->bullet_count = 0; g->particle_count = 0;
|
||||||
|
g->sel_count = 0; g->placing = 0;
|
||||||
|
g->time = 0; g->frames = 0; g->winner = 0;
|
||||||
|
g->ai_timer = 5.0f;
|
||||||
|
g->log_count = 0; g->log_next = 0;
|
||||||
|
|
||||||
|
for (int f = 1; f < MAX_FACTIONS; f++) {
|
||||||
|
g->scrap[f] = START_SCRAP;
|
||||||
|
g->power[f] = 0; g->pop[f] = 0; g->pop_cap[f] = 0;
|
||||||
|
for (int u = 0; u < UP_COUNT; u++) g->upg[f].lvl[u] = 0;
|
||||||
|
g->research_lab[f] = 0;
|
||||||
|
g->research_cur[f] = -1;
|
||||||
|
g->research_prog[f] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
place_start(g, g->human);
|
||||||
|
place_start(g, enemy);
|
||||||
|
|
||||||
|
/* fortress theme: wall the enemy base (leave a gate on the far edge) */
|
||||||
|
if (m->map_style == 3) {
|
||||||
|
int sx = g->map.spawn_x[enemy], sy = g->map.spawn_y[enemy];
|
||||||
|
for (int dx = -2; dx <= 4; dx++)
|
||||||
|
for (int dy = -2; dy <= 4; dy++) {
|
||||||
|
int perimeter = (dx == -2 || dx == 4 || dy == -2 || dy == 4);
|
||||||
|
if (!perimeter) continue;
|
||||||
|
if (dy == 4) continue; /* gate on the far edge */
|
||||||
|
bld_spawn(g, BT_WALL, enemy, sx + dx, sy + dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* centre camera on player base */
|
||||||
|
int hx = g->map.spawn_x[g->human], hy = g->map.spawn_y[g->human];
|
||||||
|
g->cam.x = (float)(hx*TILE - VIEW_W/2);
|
||||||
|
g->cam.y = (float)(hy*TILE - VIEW_H/2);
|
||||||
|
if (g->cam.x < 0) g->cam.x = 0;
|
||||||
|
if (g->cam.y < 0) g->cam.y = 0;
|
||||||
|
|
||||||
|
g->state = GAME_PLAYING;
|
||||||
|
engine_log("Mission %d '%s': %s vs %s (%s)",
|
||||||
|
m->id, m->name, faction_name(g->human), faction_name(enemy),
|
||||||
|
m->diff==DIFF_EASY?"Easy":m->diff==DIFF_HARD?"Hard":"Normal");
|
||||||
|
}
|
||||||
|
|
||||||
|
void engine_check_win(Game *g) {
|
||||||
|
Faction human = g->human;
|
||||||
|
Faction enemy = g->enemy;
|
||||||
|
int human_base = 0, enemy_base = 0;
|
||||||
|
int human_bld = 0, enemy_bld = 0;
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead) continue;
|
||||||
|
if (b->faction == human) { human_bld++; if (b->type==BT_BASE) human_base=1; }
|
||||||
|
if (b->faction == enemy) { enemy_bld++; if (b->type==BT_BASE) enemy_base=1; }
|
||||||
|
}
|
||||||
|
if (!human_base || human_bld == 0) { g->state = GAME_LOST; g->winner = enemy; return; }
|
||||||
|
|
||||||
|
switch (g->objective) {
|
||||||
|
case OBJ_SURVIVE:
|
||||||
|
if (g->time >= ((const Mission*)mission_get(g->mission_id))->survive_time)
|
||||||
|
{ g->state = GAME_WON; g->winner = human; }
|
||||||
|
break;
|
||||||
|
case OBJ_GATHER:
|
||||||
|
if (g->scrap[human] >= ((const Mission*)mission_get(g->mission_id))->scrap_goal)
|
||||||
|
{ g->state = GAME_WON; g->winner = human; }
|
||||||
|
break;
|
||||||
|
default: /* OBJ_DESTROY_ALL */
|
||||||
|
if (!enemy_base || enemy_bld == 0) { g->state = GAME_WON; g->winner = human; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void engine_update(Game *g, float dt) {
|
||||||
|
if (g->state != GAME_PLAYING) return;
|
||||||
|
dt *= g->speed_mul;
|
||||||
|
g->time += dt;
|
||||||
|
g->frames++;
|
||||||
|
|
||||||
|
unit_update(g, dt);
|
||||||
|
bld_update(g, dt);
|
||||||
|
bullet_update(g, dt);
|
||||||
|
particle_update(g, dt);
|
||||||
|
ai_update(g, dt);
|
||||||
|
map_update_fog(g, g->human);
|
||||||
|
audio_music_tick(dt);
|
||||||
|
if (g->cam.shake > 0) { g->cam.shake -= dt * 24.0f; if (g->cam.shake < 0) g->cam.shake = 0; }
|
||||||
|
engine_check_win(g);
|
||||||
|
}
|
||||||
292
engine-c/src/input.c
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* input.c - Mouse / keyboard handling and camera control.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
static int g_keys[SDL_NUM_SCANCODES];
|
||||||
|
|
||||||
|
int input_screen_to_world(Game *g, int sx, int sy, float *wx, float *wy) {
|
||||||
|
*wx = (float)sx + g->cam.x;
|
||||||
|
*wy = (float)sy + g->cam.y;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pick_unit(Game *g, int sx, int sy, float *wx, float *wy) {
|
||||||
|
input_screen_to_world(g, sx, sy, wx, wy);
|
||||||
|
int best = -1; float bestd = 18*18;
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead) continue;
|
||||||
|
float d = kknd_dist2(*wx, *wy, u->x, u->y);
|
||||||
|
if (d < bestd) { bestd = d; best = i; }
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pick_building(Game *g, float wx, float wy) {
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead) continue;
|
||||||
|
float bx = (float)(b->tx * TILE), by = (float)(b->ty * TILE);
|
||||||
|
if (wx >= bx && wx <= bx + b->w*TILE && wy >= by && wy <= by + b->h*TILE)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void issue_orders(Game *g, float wx, float wy) {
|
||||||
|
/* attack if enemy unit under cursor */
|
||||||
|
int ei = -1; float bestd = 22*22;
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead || u->faction == g->human) continue;
|
||||||
|
float d = kknd_dist2(wx, wy, u->x, u->y);
|
||||||
|
if (d < bestd) { bestd = d; ei = i; }
|
||||||
|
}
|
||||||
|
int bi = pick_building(g, wx, wy);
|
||||||
|
if (bi >= 0 && g->buildings[bi].faction != g->human) ei = -2;
|
||||||
|
|
||||||
|
int issued = 0;
|
||||||
|
if (ei >= 0 || ei == -2) {
|
||||||
|
for (int s = 0; s < g->sel_count; s++) {
|
||||||
|
Unit *u = unit_by_id(g, g->selection[s]);
|
||||||
|
if (!u) continue;
|
||||||
|
if (ei >= 0) unit_issue_attack_unit(g, (int)(u - g->units), ei);
|
||||||
|
else unit_issue_attack_bld(g, (int)(u - g->units), g->buildings[bi].id);
|
||||||
|
issued++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* gather selection and issue a formation move */
|
||||||
|
int sel[MAX_SELECTION], sn = 0;
|
||||||
|
for (int s = 0; s < g->sel_count; s++) {
|
||||||
|
Unit *u = unit_by_id(g, g->selection[s]);
|
||||||
|
if (!u) continue;
|
||||||
|
if (u->type == UT_HARVESTER) {
|
||||||
|
int tx, ty; map_world_to_tile(wx, wy, &tx, &ty);
|
||||||
|
Tile *t = map_tile(&g->map, tx, ty);
|
||||||
|
if (t && t->terrain == T_SCRAP) unit_issue_gather(g, (int)(u - g->units), wx, wy);
|
||||||
|
else unit_issue_move(g, (int)(u - g->units), wx, wy, 0);
|
||||||
|
} else {
|
||||||
|
sel[sn++] = (int)(u - g->units);
|
||||||
|
}
|
||||||
|
issued++;
|
||||||
|
}
|
||||||
|
if (sn > 0) unit_issue_move_group(g, sel, sn, wx, wy, 1);
|
||||||
|
}
|
||||||
|
if (issued) audio_sfx(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_hud_click(Game *g, int sx, int sy) {
|
||||||
|
HudButton btns[64]; int n = 0;
|
||||||
|
hud_layout(g, btns, &n);
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (sx >= btns[i].x && sx <= btns[i].x + btns[i].w &&
|
||||||
|
sy >= btns[i].y && sy <= btns[i].y + btns[i].h) {
|
||||||
|
if (!btns[i].enabled) { audio_sfx(5); return; }
|
||||||
|
if (btns[i].kind == 0) {
|
||||||
|
g->placing = 1; g->place_type = (BuildingType)btns[i].id;
|
||||||
|
audio_sfx(4);
|
||||||
|
} else if (btns[i].kind == 2) {
|
||||||
|
bld_research(g, g->human, btns[i].id);
|
||||||
|
} else if (btns[i].kind == 3) {
|
||||||
|
Faction enemy = g->enemy;
|
||||||
|
int ex = g->map.spawn_x[enemy], ey = g->map.spawn_y[enemy];
|
||||||
|
float tx, ty; map_tile_to_world(ex, ey, &tx, &ty);
|
||||||
|
int sel[MAX_SELECTION], sn = 0;
|
||||||
|
for (int s = 0; s < g->sel_count; s++) {
|
||||||
|
Unit *u = unit_by_id(g, g->selection[s]);
|
||||||
|
if (!u || u->faction != g->human) continue;
|
||||||
|
int idx = (int)(u - g->units);
|
||||||
|
if (btns[i].id == 0) unit_issue_stop(g, idx);
|
||||||
|
else if (btns[i].id == 4) unit_issue_hold(g, idx);
|
||||||
|
else if (btns[i].id == 2 && u->type == UT_HARVESTER)
|
||||||
|
unit_issue_gather_nearest(g, idx);
|
||||||
|
else if (btns[i].id == 3 && u->type == UT_HARVESTER)
|
||||||
|
unit_issue_return(g, idx);
|
||||||
|
else if (btns[i].id == 1 && u->type != UT_HARVESTER)
|
||||||
|
sel[sn++] = idx;
|
||||||
|
}
|
||||||
|
if (sn > 0 && btns[i].id == 1) unit_issue_move_group(g, sel, sn, tx, ty, 1);
|
||||||
|
audio_sfx(0);
|
||||||
|
} else if (btns[i].kind == 4) {
|
||||||
|
for (int s = 0; s < g->sel_count; s++) {
|
||||||
|
Unit *u = unit_by_id(g, g->selection[s]);
|
||||||
|
if (u && u->faction == g->human && u->ability == (AbilityType)btns[i].id)
|
||||||
|
unit_ability_use(g, (int)(u - g->units));
|
||||||
|
}
|
||||||
|
audio_sfx(4);
|
||||||
|
} else {
|
||||||
|
/* queue unit on selected production building */
|
||||||
|
for (int j = 0; j < g->bld_count; j++) {
|
||||||
|
Building *b = &g->buildings[j];
|
||||||
|
if (b->dead || !b->selected || b->faction != g->human) continue;
|
||||||
|
const BldDef *d = bld_def(b->type);
|
||||||
|
for (int k = 0; k < d->produces_count; k++)
|
||||||
|
if (d->produces[k] == btns[i].id)
|
||||||
|
bld_queue_unit(g, (int)(b - g->buildings), (UnitType)btns[i].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void try_place(Game *g, int sx, int sy) {
|
||||||
|
float wx, wy; input_screen_to_world(g, sx, sy, &wx, &wy);
|
||||||
|
int tx, ty; map_world_to_tile(wx, wy, &tx, &ty);
|
||||||
|
const BldDef *d = bld_def(g->place_type);
|
||||||
|
if (g->scrap[g->human] < d->cost) { audio_sfx(5); return; }
|
||||||
|
int ret = bld_spawn(g, g->place_type, g->human, tx, ty);
|
||||||
|
if (ret == -2) { audio_sfx(5); return; }
|
||||||
|
if (ret >= 0) {
|
||||||
|
g->scrap[g->human] -= d->cost;
|
||||||
|
audio_sfx(3);
|
||||||
|
}
|
||||||
|
g->placing = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void input_handle(Game *g, SDL_Event *e) {
|
||||||
|
switch (e->type) {
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
g_keys[e->key.keysym.scancode] = 1;
|
||||||
|
if (g->state == GAME_MENU) {
|
||||||
|
int n = mission_count();
|
||||||
|
if (e->key.keysym.sym == SDLK_f) {
|
||||||
|
g->human = (g->human == FACTION_SURVIVOR) ? FACTION_EVOLVED
|
||||||
|
: (g->human == FACTION_EVOLVED) ? FACTION_SERIES9
|
||||||
|
: FACTION_SURVIVOR;
|
||||||
|
audio_sfx(4);
|
||||||
|
} else if (e->key.keysym.sym == SDLK_UP) {
|
||||||
|
g->menu_sel = (g->menu_sel > 0) ? g->menu_sel - 1 : n - 1;
|
||||||
|
audio_sfx(6);
|
||||||
|
} else if (e->key.keysym.sym == SDLK_DOWN) {
|
||||||
|
g->menu_sel = (g->menu_sel < n - 1) ? g->menu_sel + 1 : 0;
|
||||||
|
audio_sfx(6);
|
||||||
|
} else if (e->key.keysym.sym == SDLK_RETURN
|
||||||
|
|| e->key.keysym.sym == SDLK_KP_ENTER) {
|
||||||
|
if (g->menu_sel < g->unlocked) { engine_setup_match(g, g->menu_sel); audio_sfx(3); }
|
||||||
|
} else if (e->key.keysym.sym >= SDLK_0 && e->key.keysym.sym <= SDLK_9) {
|
||||||
|
int mid = e->key.keysym.sym - SDLK_0;
|
||||||
|
if (mid < g->unlocked) { engine_setup_match(g, mid); audio_sfx(3); }
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((g->state == GAME_WON || g->state == GAME_LOST) &&
|
||||||
|
e->key.keysym.sym == SDLK_m) {
|
||||||
|
g->state = GAME_MENU;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (e->key.keysym.sym == SDLK_a && g->state == GAME_PLAYING) {
|
||||||
|
unit_select_all(g, g->human);
|
||||||
|
audio_sfx(4);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (e->key.keysym.sym >= SDLK_1 && e->key.keysym.sym <= SDLK_9 &&
|
||||||
|
g->state == GAME_PLAYING) {
|
||||||
|
int gi = e->key.keysym.sym - SDLK_1;
|
||||||
|
if (SDL_GetModState() & KMOD_CTRL) {
|
||||||
|
g->group_len[gi] = g->sel_count;
|
||||||
|
for (int s = 0; s < g->sel_count; s++) g->groups[gi][s] = g->selection[s];
|
||||||
|
audio_sfx(4);
|
||||||
|
} else if (g->group_len[gi] > 0) {
|
||||||
|
unit_clear_selection(g);
|
||||||
|
for (int s = 0; s < g->group_len[gi]; s++) {
|
||||||
|
Unit *u = unit_by_id(g, g->groups[gi][s]);
|
||||||
|
if (u && !u->dead) { g->selection[g->sel_count++] = g->groups[gi][s]; u->selected = 1; }
|
||||||
|
}
|
||||||
|
if (g->sel_count > 0) audio_sfx(4);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (e->key.keysym.sym == SDLK_SPACE) {
|
||||||
|
if (g->state == GAME_PLAYING) g->state = GAME_PAUSED;
|
||||||
|
else if (g->state == GAME_PAUSED) g->state = GAME_PLAYING;
|
||||||
|
} else if (e->key.keysym.sym == SDLK_F5) {
|
||||||
|
save_game(g, "savegame.kknd");
|
||||||
|
} else if (e->key.keysym.sym == SDLK_ESCAPE) {
|
||||||
|
g->placing = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_KEYUP:
|
||||||
|
g_keys[e->key.keysym.scancode] = 0;
|
||||||
|
break;
|
||||||
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
|
if (e->button.button == SDL_BUTTON_LEFT) {
|
||||||
|
if (g->placing) { try_place(g, e->button.x, e->button.y); break; }
|
||||||
|
if (e->button.y > VIEW_H - 140) { handle_hud_click(g, e->button.x, e->button.y); break; }
|
||||||
|
/* minimap click */
|
||||||
|
if (e->button.x > VIEW_W - 212 && e->button.x < VIEW_W - 8 &&
|
||||||
|
e->button.y > 34 && e->button.y < 238) {
|
||||||
|
int mx = VIEW_W - 210, my = 36;
|
||||||
|
g->cam.x = (e->button.x - mx) / 200.0f * MAP_PIX_W - VIEW_W/2;
|
||||||
|
g->cam.y = (e->button.y - my) / 200.0f * MAP_PIX_H - VIEW_H/2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
g->dragging = 1;
|
||||||
|
g->drag_x0 = g->drag_x1 = e->button.x;
|
||||||
|
g->drag_y0 = g->drag_y1 = e->button.y;
|
||||||
|
} else if (e->button.button == SDL_BUTTON_RIGHT) {
|
||||||
|
if (g->placing) { g->placing = 0; break; }
|
||||||
|
if (g->sel_count > 0) {
|
||||||
|
float wx, wy; input_screen_to_world(g, e->button.x, e->button.y, &wx, &wy);
|
||||||
|
issue_orders(g, wx, wy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_MOUSEBUTTONUP:
|
||||||
|
if (e->button.button == SDL_BUTTON_LEFT && g->dragging) {
|
||||||
|
g->dragging = 0;
|
||||||
|
int dx = abs(e->button.x - g->drag_x0);
|
||||||
|
int dy = abs(e->button.y - g->drag_y0);
|
||||||
|
if (dx < 6 && dy < 6) {
|
||||||
|
/* click select */
|
||||||
|
float wx, wy;
|
||||||
|
int ui = pick_unit(g, e->button.x, e->button.y, &wx, &wy);
|
||||||
|
if (ui >= 0 && g->units[ui].faction == g->human) {
|
||||||
|
unit_select_single(g, ui, g_keys[SDL_SCANCODE_LSHIFT]);
|
||||||
|
audio_sfx(4);
|
||||||
|
} else {
|
||||||
|
int bi = pick_building(g, wx, wy);
|
||||||
|
if (bi >= 0 && g->buildings[bi].faction == g->human) {
|
||||||
|
if (!g_keys[SDL_SCANCODE_LSHIFT]) unit_clear_selection(g);
|
||||||
|
g->buildings[bi].selected = 1;
|
||||||
|
g->selection[g->sel_count++] = g->buildings[bi].id;
|
||||||
|
audio_sfx(4);
|
||||||
|
} else if (!g_keys[SDL_SCANCODE_LSHIFT]) {
|
||||||
|
unit_clear_selection(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unit_select_in_rect(g, g->drag_x0, g->drag_y0,
|
||||||
|
e->button.x, e->button.y,
|
||||||
|
g_keys[SDL_SCANCODE_LSHIFT]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_MOUSEMOTION:
|
||||||
|
if (g->dragging) { g->drag_x1 = e->motion.x; g->drag_y1 = e->motion.y; }
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void input_camera(Game *g, float dt) {
|
||||||
|
float sp = 600 * dt;
|
||||||
|
if (g_keys[SDL_SCANCODE_W] || g_keys[SDL_SCANCODE_UP]) g->cam.y -= sp;
|
||||||
|
if (g_keys[SDL_SCANCODE_S] || g_keys[SDL_SCANCODE_DOWN]) g->cam.y += sp;
|
||||||
|
if (g_keys[SDL_SCANCODE_A] || g_keys[SDL_SCANCODE_LEFT]) g->cam.x -= sp;
|
||||||
|
if (g_keys[SDL_SCANCODE_D] || g_keys[SDL_SCANCODE_RIGHT])g->cam.x += sp;
|
||||||
|
/* edge scroll */
|
||||||
|
int mx, my; SDL_GetMouseState(&mx, &my);
|
||||||
|
if (mx < 8) g->cam.x -= sp;
|
||||||
|
if (mx > VIEW_W - 8) g->cam.x += sp;
|
||||||
|
if (my < 8) g->cam.y -= sp;
|
||||||
|
if (my > VIEW_H - 80 && my < VIEW_H - 8) g->cam.y += sp;
|
||||||
|
|
||||||
|
if (g->cam.x < 0) g->cam.x = 0;
|
||||||
|
if (g->cam.y < 0) g->cam.y = 0;
|
||||||
|
if (g->cam.x > MAP_PIX_W - VIEW_W) g->cam.x = MAP_PIX_W - VIEW_W;
|
||||||
|
if (g->cam.y > MAP_PIX_H - VIEW_H + 72) g->cam.y = MAP_PIX_H - VIEW_H + 72;
|
||||||
|
}
|
||||||
167
engine-c/src/main.c
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* main.c - Entry point, headless sim mode and the real-time game loop.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
static void print_stats(Game *g) {
|
||||||
|
Faction human = g->human, enemy = g->enemy;
|
||||||
|
int hu = 0, eu = 0, hb = 0, eb = 0;
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
if (g->units[i].dead) continue;
|
||||||
|
if (g->units[i].faction == human) hu++;
|
||||||
|
else if (g->units[i].faction == enemy) eu++;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
if (g->buildings[i].dead) continue;
|
||||||
|
if (g->buildings[i].faction == human) hb++;
|
||||||
|
else if (g->buildings[i].faction == enemy) eb++;
|
||||||
|
}
|
||||||
|
const char *st = (g->state == GAME_WON) ? "WON" :
|
||||||
|
(g->state == GAME_LOST) ? "LOST" :
|
||||||
|
(g->state == GAME_PAUSED) ? "PAUSED" : "PLAY";
|
||||||
|
printf("[t=%6.1f] state=%-6s %s u=%3d b=%2d scrap=%5d | %s u=%3d b=%2d scrap=%5d | bullets=%d parts=%d obj=%s\n",
|
||||||
|
g->time, st, faction_name(human), hu, hb, g->scrap[human],
|
||||||
|
faction_name(enemy), eu, eb, g->scrap[enemy], g->bullet_count, g->particle_count,
|
||||||
|
g->objective == OBJ_SURVIVE ? "survive" :
|
||||||
|
g->objective == OBJ_GATHER ? "scrap" : "destroy");
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void save_snapshot(Game *g, int n) {
|
||||||
|
char path[64];
|
||||||
|
snprintf(path, sizeof path, "shot_%04d.png", n);
|
||||||
|
void *pixels = malloc((size_t)VIEW_W * VIEW_H * 4);
|
||||||
|
if (!pixels) return;
|
||||||
|
if (SDL_RenderReadPixels(g->ren, NULL, SDL_PIXELFORMAT_RGBA32,
|
||||||
|
pixels, VIEW_W * 4) == 0) {
|
||||||
|
if (png_save(path, VIEW_W, VIEW_H, (const uint8_t *)pixels) == 0)
|
||||||
|
engine_log("snapshot -> %s", path);
|
||||||
|
}
|
||||||
|
free(pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
Game *g = (Game *)malloc(sizeof(Game));
|
||||||
|
if (!g) { fprintf(stderr, "Out of memory\n"); return 1; }
|
||||||
|
|
||||||
|
int headless = 0; float sim_seconds = 0;
|
||||||
|
Difficulty diff = DIFF_NORMAL;
|
||||||
|
int mission = 0;
|
||||||
|
int loaded = 0;
|
||||||
|
float speed = 1.0f;
|
||||||
|
Faction faction_override = FACTION_NONE;
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
if (strcmp(argv[i], "--speed") == 0 && i + 1 < argc)
|
||||||
|
speed = (float)atof(argv[++i]);
|
||||||
|
}
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
if (strcmp(argv[i], "--easy") == 0) diff = DIFF_EASY;
|
||||||
|
else if (strcmp(argv[i], "--hard") == 0) diff = DIFF_HARD;
|
||||||
|
else if (strcmp(argv[i], "--mission") == 0 && i + 1 < argc)
|
||||||
|
mission = atoi(argv[++i]);
|
||||||
|
else if (strcmp(argv[i], "--headless") == 0 && i + 1 < argc)
|
||||||
|
{ headless = 1; sim_seconds = (float)atof(argv[++i]); }
|
||||||
|
else if (strcmp(argv[i], "--auto") == 0)
|
||||||
|
{ g_auto = 1; }
|
||||||
|
else if (strcmp(argv[i], "--faction") == 0 && i + 1 < argc) {
|
||||||
|
i++;
|
||||||
|
if (!strcmp(argv[i], "evolved")) faction_override = FACTION_EVOLVED;
|
||||||
|
else if (!strcmp(argv[i], "series9")) faction_override = FACTION_SERIES9;
|
||||||
|
else faction_override = FACTION_SURVIVOR;
|
||||||
|
}
|
||||||
|
else if (strcmp(argv[i], "--load") == 0 && i + 1 < argc) {
|
||||||
|
g_headless = headless;
|
||||||
|
engine_init(g, KKND_NAME);
|
||||||
|
engine_setup_match(g, mission);
|
||||||
|
load_game(g, argv[++i]);
|
||||||
|
loaded = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_headless = headless;
|
||||||
|
if (getenv("KKND_SHOT")) g_headless = 1; /* snapshot mode = pure software, no SDL */
|
||||||
|
if (!loaded) engine_init(g, KKND_NAME);
|
||||||
|
if (faction_override != FACTION_NONE) g->human = faction_override;
|
||||||
|
if (getenv("KKND_SPEED")) speed = (float)atof(getenv("KKND_SPEED"));
|
||||||
|
g->speed_mul = speed;
|
||||||
|
/* load campaign progress (highest unlocked mission id + 1) */
|
||||||
|
{
|
||||||
|
FILE *pf = fopen("progress.kknd", "r");
|
||||||
|
if (pf) { int u = 0; if (fscanf(pf, "%d", &u) == 1 && u > 0) g->unlocked = u; fclose(pf); }
|
||||||
|
}
|
||||||
|
int shot_mode = (!headless && getenv("KKND_SHOT")) ? 1 : 0;
|
||||||
|
if (shot_mode) engine_setup_match(g, mission);
|
||||||
|
|
||||||
|
if (headless) {
|
||||||
|
if (!loaded) engine_setup_match(g, mission);
|
||||||
|
engine_log("Headless simulation for %.0f s (mission %d)", sim_seconds, mission);
|
||||||
|
float t = 0, step = 1.0f/30.0f, stat = 0;
|
||||||
|
print_stats(g);
|
||||||
|
while (t < sim_seconds && g->state == GAME_PLAYING) {
|
||||||
|
engine_update(g, step);
|
||||||
|
t += step; stat += step;
|
||||||
|
if (stat >= 1.0f) { print_stats(g); stat -= 1.0f; }
|
||||||
|
}
|
||||||
|
print_stats(g);
|
||||||
|
engine_log("Simulation complete (winner=%s)",
|
||||||
|
g->winner ? faction_name(g->winner) : "none");
|
||||||
|
engine_shutdown(g);
|
||||||
|
free(g);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_log("%s %s - choose a mission, SPACE pause, F5 save, RMB command", KKND_NAME, KKND_VERSION);
|
||||||
|
|
||||||
|
/* Snapshot mode: fast-forward the simulation headlessly (no SDL), then
|
||||||
|
* draw the world to an RGBA buffer with the software renderer and save a
|
||||||
|
* PNG. Avoids the broken GPU readback on this box entirely. */
|
||||||
|
if (shot_mode) {
|
||||||
|
float pre = 8.0f;
|
||||||
|
if (getenv("KKND_SHOT_T")) pre = (float)atof(getenv("KKND_SHOT_T"));
|
||||||
|
float hdt = 1.0f / 30.0f;
|
||||||
|
int steps = (int)(pre / hdt);
|
||||||
|
for (int i = 0; i < steps; i++) engine_update(g, hdt);
|
||||||
|
char path[64]; snprintf(path, sizeof path, "shot_%04d.png", 1);
|
||||||
|
snapshot_capture(g, path);
|
||||||
|
engine_log("snapshot -> %s", path);
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 last = SDL_GetTicks();
|
||||||
|
int running = 1;
|
||||||
|
while (running) {
|
||||||
|
SDL_Event e;
|
||||||
|
while (SDL_PollEvent(&e)) {
|
||||||
|
if (e.type == SDL_QUIT) { running = 0; break; }
|
||||||
|
input_handle(g, &e);
|
||||||
|
}
|
||||||
|
if (!running) break;
|
||||||
|
Uint32 now = SDL_GetTicks();
|
||||||
|
float dt = (float)(now - last) / 1000.0f;
|
||||||
|
last = now;
|
||||||
|
if (dt > 0.05f) dt = 0.05f;
|
||||||
|
if (g->state != GAME_MENU) {
|
||||||
|
int prev_state = g->state;
|
||||||
|
input_camera(g, dt);
|
||||||
|
engine_update(g, dt);
|
||||||
|
if (g->state == GAME_WON && prev_state != GAME_WON) {
|
||||||
|
int next = g->mission_id + 2; /* unlock the following mission */
|
||||||
|
if (next > g->unlocked) {
|
||||||
|
g->unlocked = next;
|
||||||
|
FILE *pf = fopen("progress.kknd", "w");
|
||||||
|
if (pf) { fprintf(pf, "%d\n", g->unlocked); fclose(pf); }
|
||||||
|
engine_log("Unlocked next mission (%d)", g->unlocked - 1);
|
||||||
|
}
|
||||||
|
g->menu_sel = (g->mission_id + 1 < g->unlocked) ? g->mission_id + 1 : g->mission_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render_frame(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
save_game(g, "savegame.kknd");
|
||||||
|
engine_shutdown(g);
|
||||||
|
free(g);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
407
engine-c/src/map.c
Normal file
|
|
@ -0,0 +1,407 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* map.c - Wasteland terrain generation and A* pathfinding.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
static Tile *g_tiles = NULL; /* owned by GameMap but accessed via map_tile */
|
||||||
|
|
||||||
|
void map_init(GameMap *m, RNG *rng) {
|
||||||
|
m->w = MAP_W;
|
||||||
|
m->h = MAP_H;
|
||||||
|
m->tiles = (Tile *)calloc((size_t)m->w * m->h, sizeof(Tile));
|
||||||
|
g_tiles = m->tiles;
|
||||||
|
m->fog = (uint8_t *)calloc((size_t)m->w * m->h, 1);
|
||||||
|
m->scrap_fields = 0;
|
||||||
|
|
||||||
|
/* base terrain: wasteland with scattered rubble */
|
||||||
|
for (int y = 0; y < m->h; y++) {
|
||||||
|
for (int x = 0; x < m->w; x++) {
|
||||||
|
Tile *t = &m->tiles[y * m->w + x];
|
||||||
|
t->terrain = T_WASTELAND;
|
||||||
|
t->variant = (uint8_t)rng_range(rng, 0, 7);
|
||||||
|
t->scrap = 0;
|
||||||
|
t->occupied = 0;
|
||||||
|
t->walkable = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* terrain theme knobs */
|
||||||
|
int rubble_n = 40, grass_n = 14, water_n = 8;
|
||||||
|
if (m->style == 1) { rubble_n = 22; grass_n = 20; water_n = 4; } /* open */
|
||||||
|
else if (m->style == 2) { rubble_n = 30; grass_n = 10; water_n = 16; } /* islands */
|
||||||
|
else if (m->style == 3) { rubble_n = 55; grass_n = 8; water_n = 8; } /* fortress */
|
||||||
|
|
||||||
|
/* rubble patches */
|
||||||
|
for (int i = 0; i < rubble_n; i++) {
|
||||||
|
int cx = rng_range(rng, 4, m->w - 5);
|
||||||
|
int cy = rng_range(rng, 4, m->h - 5);
|
||||||
|
int r = rng_range(rng, 3, 9);
|
||||||
|
for (int y = cy - r; y <= cy + r; y++)
|
||||||
|
for (int x = cx - r; x <= cx + r; x++) {
|
||||||
|
if (!map_in_bounds(m, x, y)) continue;
|
||||||
|
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||||
|
Tile *t = &m->tiles[y*m->w + x];
|
||||||
|
if (rng_f32(rng) < 0.7f) t->terrain = T_RUBBLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* grass oases */
|
||||||
|
for (int i = 0; i < grass_n; i++) {
|
||||||
|
int cx = rng_range(rng, 4, m->w - 5);
|
||||||
|
int cy = rng_range(rng, 4, m->h - 5);
|
||||||
|
int r = rng_range(rng, 3, 7);
|
||||||
|
for (int y = cy - r; y <= cy + r; y++)
|
||||||
|
for (int x = cx - r; x <= cx + r; x++) {
|
||||||
|
if (!map_in_bounds(m, x, y)) continue;
|
||||||
|
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r)
|
||||||
|
m->tiles[y*m->w + x].terrain = T_GRASS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* roads: a few straight-ish lines */
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
int horiz = rng_range(rng, 0, 1);
|
||||||
|
int pos = rng_range(rng, 8, m->w - 9);
|
||||||
|
int len = rng_range(rng, 20, 60);
|
||||||
|
int x = rng_range(rng, 2, m->w - 3);
|
||||||
|
int y = rng_range(rng, 2, m->h - 3);
|
||||||
|
for (int s = 0; s < len; s++) {
|
||||||
|
if (map_in_bounds(m, x, y)) m->tiles[y*m->w + x].terrain = T_ROAD;
|
||||||
|
if (horiz) x += (rng_f32(rng) < 0.85f) ? 1 : 0, y += rng_range(rng,-1,1);
|
||||||
|
else y += (rng_f32(rng) < 0.85f) ? 1 : 0, x += rng_range(rng,-1,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* water lakes (blocked) */
|
||||||
|
for (int i = 0; i < water_n; i++) {
|
||||||
|
int cx = rng_range(rng, 6, m->w - 7);
|
||||||
|
int cy = rng_range(rng, 6, m->h - 7);
|
||||||
|
int r = rng_range(rng, 3, 8);
|
||||||
|
for (int y = cy - r; y <= cy + r; y++)
|
||||||
|
for (int x = cx - r; x <= cx + r; x++) {
|
||||||
|
if (!map_in_bounds(m, x, y)) continue;
|
||||||
|
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||||
|
Tile *t = &m->tiles[y*m->w + x];
|
||||||
|
t->terrain = T_WATER;
|
||||||
|
t->walkable = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rock mountains (blocked) */
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
int cx = rng_range(rng, 6, m->w - 7);
|
||||||
|
int cy = rng_range(rng, 6, m->h - 7);
|
||||||
|
int r = rng_range(rng, 3, 9);
|
||||||
|
for (int y = cy - r; y <= cy + r; y++)
|
||||||
|
for (int x = cx - r; x <= cx + r; x++) {
|
||||||
|
if (!map_in_bounds(m, x, y)) continue;
|
||||||
|
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||||
|
Tile *t = &m->tiles[y*m->w + x];
|
||||||
|
if (t->terrain != T_WATER) {
|
||||||
|
t->terrain = T_ROCK;
|
||||||
|
t->walkable = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scrap fields (resource) scattered, avoid spawn corners */
|
||||||
|
int attempts = 0;
|
||||||
|
while (m->scrap_fields < 22 && attempts < 4000) {
|
||||||
|
attempts++;
|
||||||
|
int cx = rng_range(rng, 6, m->w - 7);
|
||||||
|
int cy = rng_range(rng, 6, m->h - 7);
|
||||||
|
int r = rng_range(rng, 2, 4);
|
||||||
|
if (kknd_dist2((float)cx,(float)cy, 10, m->h-10) < 20*20) continue;
|
||||||
|
if (kknd_dist2((float)cx,(float)cy, m->w-10, 10) < 20*20) continue;
|
||||||
|
for (int y = cy - r; y <= cy + r; y++)
|
||||||
|
for (int x = cx - r; x <= cx + r; x++) {
|
||||||
|
if (!map_in_bounds(m, x, y)) continue;
|
||||||
|
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||||
|
Tile *t = &m->tiles[y*m->w + x];
|
||||||
|
if (t->walkable && t->terrain != T_WATER) {
|
||||||
|
t->terrain = T_SCRAP;
|
||||||
|
t->scrap = 255;
|
||||||
|
m->scrap_fields++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* spawn points: Survivor bottom-left, Evolved top-right */
|
||||||
|
m->spawn_x[FACTION_SURVIVOR] = 10;
|
||||||
|
m->spawn_y[FACTION_SURVIVOR] = m->h - 10;
|
||||||
|
m->spawn_x[FACTION_EVOLVED] = m->w - 10;
|
||||||
|
m->spawn_y[FACTION_EVOLVED] = 10;
|
||||||
|
m->spawn_x[FACTION_SERIES9] = 10;
|
||||||
|
m->spawn_y[FACTION_SERIES9] = 10;
|
||||||
|
|
||||||
|
/* clear spawn areas to wasteland & walkable */
|
||||||
|
for (int f = 1; f <= 2; f++) {
|
||||||
|
int sx = m->spawn_x[f], sy = m->spawn_y[f];
|
||||||
|
for (int y = sy - 8; y <= sy + 8; y++)
|
||||||
|
for (int x = sx - 8; x <= sx + 8; x++) {
|
||||||
|
if (!map_in_bounds(m, x, y)) continue;
|
||||||
|
Tile *t = &m->tiles[y*m->w + x];
|
||||||
|
t->terrain = T_WASTELAND;
|
||||||
|
t->walkable = 1;
|
||||||
|
t->occupied = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_free(GameMap *m) {
|
||||||
|
free(m->tiles);
|
||||||
|
free(m->fog);
|
||||||
|
m->tiles = NULL;
|
||||||
|
m->fog = NULL;
|
||||||
|
g_tiles = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_update_fog(Game *g, Faction f) {
|
||||||
|
uint8_t *vis = g->map.fog;
|
||||||
|
int n = g->map.w * g->map.h;
|
||||||
|
for (int i = 0; i < n; i++) if (vis[i] == 2) vis[i] = 1;
|
||||||
|
|
||||||
|
/* reveal around human units */
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead || u->faction != f) continue;
|
||||||
|
int cx = (int)(u->x / TILE), cy = (int)(u->y / TILE);
|
||||||
|
int r = (int)(u->sight / TILE) + 1;
|
||||||
|
if (r < 4) r = 4;
|
||||||
|
for (int y = cy - r; y <= cy + r; y++)
|
||||||
|
for (int x = cx - r; x <= cx + r; x++) {
|
||||||
|
if (!map_in_bounds(&g->map, x, y)) continue;
|
||||||
|
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||||
|
int idx = y*g->map.w + x;
|
||||||
|
vis[idx] = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* reveal around human buildings */
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead || b->faction != f) continue;
|
||||||
|
int cx = b->tx + b->w/2, cy = b->ty + b->h/2;
|
||||||
|
int r = 8 + b->w;
|
||||||
|
for (int y = cy - r; y <= cy + r; y++)
|
||||||
|
for (int x = cx - r; x <= cx + r; x++) {
|
||||||
|
if (!map_in_bounds(&g->map, x, y)) continue;
|
||||||
|
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||||
|
int idx = y*g->map.w + x;
|
||||||
|
vis[idx] = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tile *map_tile(GameMap *m, int tx, int ty) {
|
||||||
|
if (!map_in_bounds(m, tx, ty)) return NULL;
|
||||||
|
return &m->tiles[ty * m->w + tx];
|
||||||
|
}
|
||||||
|
|
||||||
|
int map_in_bounds(GameMap *m, int tx, int ty) {
|
||||||
|
return tx >= 0 && ty >= 0 && tx < m->w && ty < m->h;
|
||||||
|
}
|
||||||
|
|
||||||
|
int map_walkable(GameMap *m, int tx, int ty) {
|
||||||
|
if (!map_in_bounds(m, tx, ty)) return 0;
|
||||||
|
Tile *t = &m->tiles[ty * m->w + tx];
|
||||||
|
return t->walkable && !t->occupied;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_world_to_tile(float wx, float wy, int *tx, int *ty) {
|
||||||
|
*tx = (int)(wx / TILE);
|
||||||
|
*ty = (int)(wy / TILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_tile_to_world(int tx, int ty, float *wx, float *wy) {
|
||||||
|
*wx = (float)(tx * TILE + TILE / 2);
|
||||||
|
*wy = (float)(ty * TILE + TILE / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int map_find_nearest_scrap(GameMap *m, float wx, float wy, float *outx, float *outy) {
|
||||||
|
int best_tx = -1, best_ty = -1;
|
||||||
|
float best = 1e18f;
|
||||||
|
for (int y = 0; y < m->h; y++) {
|
||||||
|
for (int x = 0; x < m->w; x++) {
|
||||||
|
Tile *t = &m->tiles[y*m->w + x];
|
||||||
|
if (t->terrain == T_SCRAP && t->scrap > 0) {
|
||||||
|
float dx = (float)(x*TILE) - wx, dy = (float)(y*TILE) - wy;
|
||||||
|
float d = dx*dx + dy*dy;
|
||||||
|
if (d < best) { best = d; best_tx = x; best_ty = y; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best_tx < 0) return 0;
|
||||||
|
map_tile_to_world(best_tx, best_ty, outx, outy);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_deplete_scrap(GameMap *m, int tx, int ty, int amount) {
|
||||||
|
Tile *t = map_tile(m, tx, ty);
|
||||||
|
if (!t || t->terrain != T_SCRAP) return;
|
||||||
|
int s = (int)t->scrap - amount;
|
||||||
|
if (s <= 0) {
|
||||||
|
t->scrap = 0;
|
||||||
|
t->terrain = T_RUBBLE; /* depleted field becomes rubble */
|
||||||
|
} else {
|
||||||
|
t->scrap = (uint8_t)s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------- A* ----------------------------- */
|
||||||
|
/* Binary heap of tile indices keyed by f-score in a parallel array. */
|
||||||
|
#define ASTAR_NODES (MAP_W * MAP_H)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int g[ASTAR_NODES];
|
||||||
|
int f[ASTAR_NODES];
|
||||||
|
int came[ASTAR_NODES];
|
||||||
|
int closed[ASTAR_NODES];
|
||||||
|
int open[ASTAR_NODES];
|
||||||
|
int heap[ASTAR_NODES];
|
||||||
|
int heap_n;
|
||||||
|
} AStar;
|
||||||
|
|
||||||
|
static AStar *g_astar = NULL;
|
||||||
|
|
||||||
|
static void heap_push(AStar *a, int idx) {
|
||||||
|
int i = a->heap_n++;
|
||||||
|
a->heap[i] = idx;
|
||||||
|
while (i > 0) {
|
||||||
|
int p = (i - 1) / 2;
|
||||||
|
if (a->f[a->heap[p]] <= a->f[a->heap[i]]) break;
|
||||||
|
int tmp = a->heap[p]; a->heap[p] = a->heap[i]; a->heap[i] = tmp;
|
||||||
|
i = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int heap_pop(AStar *a) {
|
||||||
|
int top = a->heap[0];
|
||||||
|
a->heap[0] = a->heap[--a->heap_n];
|
||||||
|
int i = 0;
|
||||||
|
for (;;) {
|
||||||
|
int l = 2*i+1, r = 2*i+2, m = i;
|
||||||
|
if (l < a->heap_n && a->f[a->heap[l]] < a->f[a->heap[m]]) m = l;
|
||||||
|
if (r < a->heap_n && a->f[a->heap[r]] < a->f[a->heap[m]]) m = r;
|
||||||
|
if (m == i) break;
|
||||||
|
int tmp = a->heap[m]; a->heap[m] = a->heap[i]; a->heap[i] = tmp;
|
||||||
|
i = m;
|
||||||
|
}
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int heuristic(int ax, int ay, int bx, int by) {
|
||||||
|
int dx = abs(ax - bx), dy = abs(ay - by);
|
||||||
|
return (dx + dy) + (14 - 20) * (dx < dy ? dx : dy); /* octile-ish */
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int NX[8] = { 1,-1,0,0,1,1,-1,-1 };
|
||||||
|
static const int NY[8] = { 0,0,1,-1,1,-1,1,-1 };
|
||||||
|
|
||||||
|
int path_find(GameMap *m, int sx, int sy, int gx, int gy, Path *out) {
|
||||||
|
out->len = 0;
|
||||||
|
if (!map_in_bounds(m, sx, sy) || !map_in_bounds(m, gx, gy)) return 0;
|
||||||
|
|
||||||
|
/* if goal blocked, retarget to nearest walkable neighbour */
|
||||||
|
if (!map_walkable(m, gx, gy)) {
|
||||||
|
int best = -1, bestd = 1e9;
|
||||||
|
for (int d = 0; d < 8; d++) {
|
||||||
|
int nx = gx + NX[d], ny = gy + NY[d];
|
||||||
|
if (map_walkable(m, nx, ny)) {
|
||||||
|
int dist = abs(nx-sx)+abs(ny-sy);
|
||||||
|
if (dist < bestd) { bestd = dist; best = ny*m->w+nx; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best < 0) return 0;
|
||||||
|
gx = best % m->w; gy = best / m->w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_astar == NULL) g_astar = (AStar *)calloc(1, sizeof(AStar));
|
||||||
|
AStar *a = g_astar;
|
||||||
|
for (int i = 0; i < m->w*m->h; i++) { a->closed[i]=0; a->came[i]=-1; a->g[i]=1e9; }
|
||||||
|
a->heap_n = 0;
|
||||||
|
|
||||||
|
int start = sy*m->w + sx;
|
||||||
|
int goal = gy*m->w + gx;
|
||||||
|
a->g[start] = 0;
|
||||||
|
a->f[start] = heuristic(sx, sy, gx, gy);
|
||||||
|
heap_push(a, start);
|
||||||
|
a->open[start] = 1;
|
||||||
|
|
||||||
|
int found = 0;
|
||||||
|
int iter = 0;
|
||||||
|
while (a->heap_n > 0 && iter++ < ASTAR_NODES * 4) {
|
||||||
|
int cur = heap_pop(a);
|
||||||
|
if (a->closed[cur]) continue;
|
||||||
|
a->closed[cur] = 1;
|
||||||
|
if (cur == goal) { found = 1; break; }
|
||||||
|
int cx = cur % m->w, cy = cur / m->w;
|
||||||
|
for (int d = 0; d < 8; d++) {
|
||||||
|
int nx = cx + NX[d], ny = cy + NY[d];
|
||||||
|
if (!map_in_bounds(m, nx, ny)) continue;
|
||||||
|
int ni = ny*m->w + nx;
|
||||||
|
if (a->closed[ni]) continue;
|
||||||
|
int step = (d < 4) ? 10 : 14;
|
||||||
|
if (!map_walkable(m, nx, ny) && ni != goal) continue;
|
||||||
|
int ng = a->g[cur] + step;
|
||||||
|
if (ng < a->g[ni]) {
|
||||||
|
a->g[ni] = ng;
|
||||||
|
a->came[ni] = cur;
|
||||||
|
a->f[ni] = ng + heuristic(nx, ny, gx, gy);
|
||||||
|
heap_push(a, ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) return 0;
|
||||||
|
|
||||||
|
/* reconstruct */
|
||||||
|
int buf[ASTAR_NODES];
|
||||||
|
int n = 0;
|
||||||
|
for (int c = goal; c != -1; c = a->came[c]) buf[n++] = c;
|
||||||
|
/* reverse */
|
||||||
|
int len = 0;
|
||||||
|
for (int i = n - 1; i >= 0 && len < MAX_PATH; i--) {
|
||||||
|
out->nodes[len].tx = buf[i] % m->w;
|
||||||
|
out->nodes[len].ty = buf[i] / m->w;
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
out->len = len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void path_smooth(GameMap *m, Path *p) {
|
||||||
|
if (p->len < 3) return;
|
||||||
|
/* simple line-of-sight smoothing: skip waypoints when straight clear */
|
||||||
|
int out_i = 0;
|
||||||
|
int i = 0;
|
||||||
|
while (i < p->len - 1) {
|
||||||
|
int j = p->len - 1;
|
||||||
|
while (j > i + 1) {
|
||||||
|
/* check tiles between i and j */
|
||||||
|
int x0 = p->nodes[i].tx, y0 = p->nodes[i].ty;
|
||||||
|
int x1 = p->nodes[j].tx, y1 = p->nodes[j].ty;
|
||||||
|
int steps = abs(x1-x0) > abs(y1-y0) ? abs(x1-x0) : abs(y1-y0);
|
||||||
|
int ok = 1;
|
||||||
|
for (int s = 1; s < steps; s++) {
|
||||||
|
int tx = x0 + (x1-x0)*s/steps;
|
||||||
|
int ty = y0 + (y1-y0)*s/steps;
|
||||||
|
if (!map_walkable(m, tx, ty)) { ok = 0; break; }
|
||||||
|
}
|
||||||
|
if (ok) break;
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
p->nodes[out_i] = p->nodes[i];
|
||||||
|
out_i++;
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
p->nodes[out_i] = p->nodes[p->len-1];
|
||||||
|
out_i++;
|
||||||
|
p->len = out_i;
|
||||||
|
}
|
||||||
83
engine-c/src/png.c
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Minimal dependency-free PNG encoder (RGBA) using zlib.
|
||||||
|
* Avoids linking SDL2_image (which drags in an incompatible SDL3 at runtime).
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "kknd.h"
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
static uint32_t crc_table[256];
|
||||||
|
static int crc_done = 0;
|
||||||
|
|
||||||
|
static void crc_init(void) {
|
||||||
|
for (uint32_t n = 0; n < 256; n++) {
|
||||||
|
uint32_t c = n;
|
||||||
|
for (int k = 0; k < 8; k++)
|
||||||
|
c = (c & 1) ? (0xedb88320U ^ (c >> 1)) : (c >> 1);
|
||||||
|
crc_table[n] = c;
|
||||||
|
}
|
||||||
|
crc_done = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t crc_update(uint32_t c, const uint8_t *buf, size_t len) {
|
||||||
|
c = ~c;
|
||||||
|
for (size_t i = 0; i < len; i++)
|
||||||
|
c = crc_table[(c ^ buf[i]) & 0xff] ^ (c >> 8);
|
||||||
|
return ~c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chunk(FILE *f, const char *type, const uint8_t *data, uint32_t len) {
|
||||||
|
uint8_t hdr[4];
|
||||||
|
hdr[0] = (len >> 24) & 0xff; hdr[1] = (len >> 16) & 0xff;
|
||||||
|
hdr[2] = (len >> 8) & 0xff; hdr[3] = len & 0xff;
|
||||||
|
fwrite(hdr, 1, 4, f);
|
||||||
|
fwrite(type, 1, 4, f);
|
||||||
|
if (len) fwrite(data, 1, len, f);
|
||||||
|
uint32_t crc = crc_update(0, (const uint8_t *)type, 4);
|
||||||
|
crc = crc_update(crc, data, len);
|
||||||
|
uint8_t cr[4];
|
||||||
|
cr[0] = (crc >> 24) & 0xff; cr[1] = (crc >> 16) & 0xff;
|
||||||
|
cr[2] = (crc >> 8) & 0xff; cr[3] = crc & 0xff;
|
||||||
|
fwrite(cr, 1, 4, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
int png_save(const char *path, int w, int h, const uint8_t *rgba) {
|
||||||
|
if (!crc_done) crc_init();
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
|
||||||
|
static const uint8_t sig[8] = {137,80,78,71,13,10,26,10};
|
||||||
|
fwrite(sig, 1, 8, f);
|
||||||
|
|
||||||
|
uint8_t ihdr[13];
|
||||||
|
ihdr[0] = (w >> 24) & 0xff; ihdr[1] = (w >> 16) & 0xff;
|
||||||
|
ihdr[2] = (w >> 8) & 0xff; ihdr[3] = w & 0xff;
|
||||||
|
ihdr[4] = (h >> 24) & 0xff; ihdr[5] = (h >> 16) & 0xff;
|
||||||
|
ihdr[6] = (h >> 8) & 0xff; ihdr[7] = h & 0xff;
|
||||||
|
ihdr[8] = 8; /* bit depth */
|
||||||
|
ihdr[9] = 6; /* colour type RGBA */
|
||||||
|
ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
|
||||||
|
chunk(f, "IHDR", ihdr, 13);
|
||||||
|
|
||||||
|
/* raw scanlines with filter byte 0 */
|
||||||
|
uint8_t *raw = (uint8_t *)malloc((size_t)(w * 4 + 1) * h);
|
||||||
|
if (!raw) { fclose(f); return -1; }
|
||||||
|
for (int y = 0; y < h; y++) {
|
||||||
|
raw[(size_t)y * (w * 4 + 1)] = 0;
|
||||||
|
memcpy(raw + (size_t)y * (w * 4 + 1) + 1, rgba + (size_t)y * w * 4, (size_t)w * 4);
|
||||||
|
}
|
||||||
|
uLongf cmp_cap = compressBound((uLong)(w * 4 + 1) * h);
|
||||||
|
uint8_t *cmp = (uint8_t *)malloc(cmp_cap);
|
||||||
|
if (!cmp) { free(raw); fclose(f); return -1; }
|
||||||
|
uLongf cmp_len = cmp_cap;
|
||||||
|
compress2((Bytef *)cmp, &cmp_len, (const Bytef *)raw,
|
||||||
|
(uLong)(w * 4 + 1) * h, Z_BEST_COMPRESSION);
|
||||||
|
chunk(f, "IDAT", cmp, (uint32_t)cmp_len);
|
||||||
|
chunk(f, "IEND", NULL, 0);
|
||||||
|
|
||||||
|
free(cmp); free(raw);
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
106
engine-c/src/projectiles.c
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* projectiles.c - Bullets, missiles and particle effects.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
static RNG g_prng;
|
||||||
|
static int g_prng_init = 0;
|
||||||
|
|
||||||
|
void bullet_spawn(Game *g, float x, float y, int target_unit, int target_bld,
|
||||||
|
float dmg, Faction f, int kind) {
|
||||||
|
if (g->bullet_count >= MAX_BULLETS) return;
|
||||||
|
Bullet *b = &g->bullets[g->bullet_count++];
|
||||||
|
b->x = x; b->y = y;
|
||||||
|
b->target_unit = target_unit;
|
||||||
|
b->target_bld = target_bld;
|
||||||
|
b->damage = dmg;
|
||||||
|
b->faction = f;
|
||||||
|
b->kind = kind;
|
||||||
|
b->speed = (kind == 3) ? 320 : (kind == 1 ? 420 : 520);
|
||||||
|
b->life = 3.0f;
|
||||||
|
if (target_unit >= 0) {
|
||||||
|
Unit *t = unit_by_id(g, target_unit);
|
||||||
|
if (t) { b->tx = t->x; b->ty = t->y; }
|
||||||
|
} else if (target_bld >= 0) {
|
||||||
|
Building *t = bld_by_id(g, target_bld);
|
||||||
|
if (t) { b->tx = (float)(t->tx*TILE + t->w*TILE/2);
|
||||||
|
b->ty = (float)(t->ty*TILE + t->h*TILE/2); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bullet_update(Game *g, float dt) {
|
||||||
|
for (int i = g->bullet_count - 1; i >= 0; i--) {
|
||||||
|
Bullet *b = &g->bullets[i];
|
||||||
|
b->life -= dt;
|
||||||
|
/* home onto target if still alive */
|
||||||
|
if (b->target_unit >= 0) {
|
||||||
|
Unit *t = unit_by_id(g, b->target_unit);
|
||||||
|
if (t) { b->tx = t->x; b->ty = t->y; }
|
||||||
|
else b->target_unit = -1;
|
||||||
|
} else if (b->target_bld >= 0) {
|
||||||
|
Building *t = bld_by_id(g, b->target_bld);
|
||||||
|
if (t) { b->tx = (float)(t->tx*TILE+t->w*TILE/2);
|
||||||
|
b->ty = (float)(t->ty*TILE+t->h*TILE/2); }
|
||||||
|
else b->target_bld = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dx = b->tx - b->x, dy = b->ty - b->y;
|
||||||
|
float d = (float)sqrtf(dx*dx + dy*dy);
|
||||||
|
float step = b->speed * dt;
|
||||||
|
if (d <= step + 4 || b->life <= 0) {
|
||||||
|
/* impact */
|
||||||
|
if (b->target_unit >= 0) {
|
||||||
|
Unit *t = unit_by_id(g, b->target_unit);
|
||||||
|
if (t) unit_apply_damage(g, t, b->damage);
|
||||||
|
} else if (b->target_bld >= 0) {
|
||||||
|
Building *t = bld_by_id(g, b->target_bld);
|
||||||
|
if (t) bld_apply_damage(g, t, b->damage);
|
||||||
|
}
|
||||||
|
particle_burst(g, b->tx, b->ty, 4,
|
||||||
|
(b->kind==2)?2:1, 255, 200, 80);
|
||||||
|
/* swap-remove */
|
||||||
|
g->bullets[i] = g->bullets[g->bullet_count - 1];
|
||||||
|
g->bullet_count--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
b->x += dx/d * step;
|
||||||
|
b->y += dy/d * step;
|
||||||
|
/* trail */
|
||||||
|
if (b->kind != 0)
|
||||||
|
particle_spawn(g, b->x, b->y, 2, 255, 180, 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void particle_spawn(Game *g, float x, float y, int kind, uint8_t cr, uint8_t cg, uint8_t cb) {
|
||||||
|
if (g->particle_count >= MAX_PARTICLES) return;
|
||||||
|
if (!g_prng_init) { rng_seed(&g_prng, 0x1234ABCDULL); g_prng_init = 1; }
|
||||||
|
Particle *p = &g->particles[g->particle_count++];
|
||||||
|
p->x = x; p->y = y;
|
||||||
|
float ang = rng_frange(&g_prng, 0, 6.283f);
|
||||||
|
float sp = rng_frange(&g_prng, 10, 60);
|
||||||
|
p->vx = (float)cosf(ang) * sp;
|
||||||
|
p->vy = (float)sinf(ang) * sp;
|
||||||
|
p->life = p->max_life = (kind == 0) ? 1.2f : 0.5f;
|
||||||
|
p->r = cr; p->g = cg; p->b = cb;
|
||||||
|
p->kind = kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
void particle_burst(Game *g, float x, float y, int n, int kind, uint8_t cr, uint8_t cg, uint8_t cb) {
|
||||||
|
for (int i = 0; i < n; i++) particle_spawn(g, x, y, kind, cr, cg, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void particle_update(Game *g, float dt) {
|
||||||
|
for (int i = g->particle_count - 1; i >= 0; i--) {
|
||||||
|
Particle *p = &g->particles[i];
|
||||||
|
p->life -= dt;
|
||||||
|
p->x += p->vx * dt;
|
||||||
|
p->y += p->vy * dt;
|
||||||
|
p->vx *= 0.92f; p->vy *= 0.92f;
|
||||||
|
if (p->kind == 1) p->vy += 40 * dt; /* fire rises/slows */
|
||||||
|
if (p->life <= 0) {
|
||||||
|
g->particles[i] = g->particles[g->particle_count - 1];
|
||||||
|
g->particle_count--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
631
engine-c/src/render.c
Normal file
|
|
@ -0,0 +1,631 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
static void render_message_log(Game *g);
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* render.c - All 2D drawing: terrain, units, buildings, HUD and minimap.
|
||||||
|
* Recreates the chunky overhead KKND battlefield presentation.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
int render_init(Game *g) {
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
|
||||||
|
fprintf(stderr, "SDL_Init: %s\n", SDL_GetError());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (TTF_Init() != 0) {
|
||||||
|
fprintf(stderr, "TTF_Init: %s\n", TTF_GetError());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
g->win = SDL_CreateWindow(KKND_NAME " - Krush, Kill 'n' Destroy",
|
||||||
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, VIEW_W, VIEW_H,
|
||||||
|
SDL_WINDOW_SHOWN);
|
||||||
|
if (!g->win) return 0;
|
||||||
|
g->ren = SDL_CreateRenderer(g->win, -1,
|
||||||
|
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||||
|
if (!g->ren) return 0;
|
||||||
|
SDL_SetRenderDrawBlendMode(g->ren, SDL_BLENDMODE_BLEND);
|
||||||
|
sprites_init(&g->spr, g->ren);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_text(Sprites *s, SDL_Renderer *r, TTF_Font *f,
|
||||||
|
const char *txt, int x, int y, SDL_Color c) {
|
||||||
|
if (!f || !txt || !*txt) return;
|
||||||
|
SDL_Surface *surf = TTF_RenderText_Blended(f, txt, c);
|
||||||
|
if (!surf) return;
|
||||||
|
SDL_Texture *tex = SDL_CreateTextureFromSurface(r, surf);
|
||||||
|
if (tex) {
|
||||||
|
SDL_Rect dr = { x, y, surf->w, surf->h };
|
||||||
|
SDL_RenderCopy(r, tex, NULL, &dr);
|
||||||
|
SDL_DestroyTexture(tex);
|
||||||
|
}
|
||||||
|
SDL_FreeSurface(surf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_tex_world(Game *g, SDL_Texture *tex, float wx, float wy,
|
||||||
|
int w, int h, float angle, Uint8 alpha) {
|
||||||
|
SDL_Rect r;
|
||||||
|
r.w = w; r.h = h;
|
||||||
|
r.x = (int)(wx - w/2 - g->cam.x);
|
||||||
|
r.y = (int)(wy - h/2 - g->cam.y);
|
||||||
|
SDL_SetTextureAlphaMod(tex, alpha);
|
||||||
|
SDL_RenderCopyEx(g->ren, tex, NULL, &r, angle * 180.0f / 3.14159f,
|
||||||
|
NULL, SDL_FLIP_NONE);
|
||||||
|
SDL_SetTextureAlphaMod(tex, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_world(Game *g) {
|
||||||
|
int tx0 = (int)(g->cam.x / TILE) - 1;
|
||||||
|
int ty0 = (int)(g->cam.y / TILE) - 1;
|
||||||
|
int tx1 = (int)((g->cam.x + VIEW_W) / TILE) + 1;
|
||||||
|
int ty1 = (int)((g->cam.y + VIEW_H) / TILE) + 1;
|
||||||
|
for (int ty = ty0; ty <= ty1; ty++) {
|
||||||
|
for (int tx = tx0; tx <= tx1; tx++) {
|
||||||
|
if (!map_in_bounds(&g->map, tx, ty)) continue;
|
||||||
|
uint8_t fv = g->map.fog[ty*g->map.w + tx];
|
||||||
|
if (fv == 0) continue; /* unexplored: leave black */
|
||||||
|
Tile *t = map_tile(&g->map, tx, ty);
|
||||||
|
SDL_Rect r = { (int)(tx*TILE - g->cam.x), (int)(ty*TILE - g->cam.y), TILE, TILE };
|
||||||
|
SDL_RenderCopy(g->ren, g->spr.terrain[t->terrain], NULL, &r);
|
||||||
|
if (fv == 1) {
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 120);
|
||||||
|
SDL_RenderFillRect(g->ren, &r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* subtle grid for the tactical look (only on explored tiles) */
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 22);
|
||||||
|
for (int tx = tx0; tx <= tx1; tx++) {
|
||||||
|
int x = (int)(tx*TILE - g->cam.x);
|
||||||
|
SDL_RenderDrawLine(g->ren, x, (int)(ty0*TILE-g->cam.y), x, (int)(ty1*TILE-g->cam.y));
|
||||||
|
}
|
||||||
|
for (int ty = ty0; ty <= ty1; ty++) {
|
||||||
|
int y = (int)(ty*TILE - g->cam.y);
|
||||||
|
SDL_RenderDrawLine(g->ren, (int)(tx0*TILE-g->cam.x), y, (int)(tx1*TILE-g->cam.x), y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_buildings(Game *g) {
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead) continue;
|
||||||
|
if (b->faction != g->human) {
|
||||||
|
int tx = b->tx + b->w/2, ty = b->ty + b->h/2;
|
||||||
|
if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*g->map.w + tx] != 2) continue;
|
||||||
|
}
|
||||||
|
int W = b->w * TILE, H = b->h * TILE;
|
||||||
|
int sx = (int)(b->tx*TILE - g->cam.x);
|
||||||
|
int sy = (int)(b->ty*TILE - g->cam.y);
|
||||||
|
SDL_Rect r = { sx, sy, W, H };
|
||||||
|
SDL_Texture *tex = g->spr.bld[b->type][b->faction];
|
||||||
|
if (b->build < 1.0f) {
|
||||||
|
/* construction: draw then scaffold overlay */
|
||||||
|
SDL_SetTextureAlphaMod(tex, 160);
|
||||||
|
SDL_RenderCopy(g->ren, tex, NULL, &r);
|
||||||
|
SDL_SetTextureAlphaMod(tex, 255);
|
||||||
|
int pct = (int)(b->build * 100);
|
||||||
|
SDL_Rect bar = { sx, sy - 8, W, 5 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 200, 200, 200, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &bar);
|
||||||
|
SDL_Rect fill = { sx, sy - 8, (int)(W * b->build), 5 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 120, 220, 120, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &fill);
|
||||||
|
} else {
|
||||||
|
SDL_RenderCopy(g->ren, tex, NULL, &r);
|
||||||
|
}
|
||||||
|
/* health bar if damaged or selected */
|
||||||
|
if (b->hp < b->max_hp || b->selected) {
|
||||||
|
SDL_Rect bar = { sx, sy - 6, W, 4 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 60, 60, 60, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &bar);
|
||||||
|
float f = b->hp / b->max_hp;
|
||||||
|
SDL_Color c = (b->faction == FACTION_SURVIVOR) ? (SDL_Color){90,160,70,255}
|
||||||
|
: (SDL_Color){170,70,180,255};
|
||||||
|
SDL_Rect fill = { sx, sy - 6, (int)(W * f), 4 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &fill);
|
||||||
|
}
|
||||||
|
if (b->selected) {
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 255, 255, 255, 200);
|
||||||
|
SDL_RenderDrawRect(g->ren, &r);
|
||||||
|
}
|
||||||
|
/* production progress */
|
||||||
|
if (b->producing != 0) {
|
||||||
|
const UnitDef *ud = unit_def((UnitType)b->producing, b->faction);
|
||||||
|
float f = b->prod_timer / (float)ud->build_time;
|
||||||
|
SDL_Rect bar = { sx, sy + H + 2, W, 4 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 60, 60, 60, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &bar);
|
||||||
|
SDL_Rect fill = { sx, sy + H + 2, (int)(W * f), 4 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 255, 220, 120, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_wrecks(Game *g) {
|
||||||
|
for (int i = 0; i < g->wreck_count; i++) {
|
||||||
|
Wreck *w = &g->wrecks[i];
|
||||||
|
int tx = (int)(w->x / TILE), ty = (int)(w->y / TILE);
|
||||||
|
if (!map_in_bounds(&g->map, tx, ty)) continue;
|
||||||
|
if (g->map.fog[ty*g->map.w + tx] == 0) continue;
|
||||||
|
SDL_Color c = faction_color(w->faction);
|
||||||
|
int sx = (int)(w->x - g->cam.x), sy = (int)(w->y - g->cam.y);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, c.r/2, c.g/2, c.b/2, 200);
|
||||||
|
SDL_RenderFillRect(g->ren, &(SDL_Rect){ sx-12, sy-9, 24, 18 });
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 30, 28, 26, 200);
|
||||||
|
SDL_RenderFillRect(g->ren, &(SDL_Rect){ sx-8, sy-5, 16, 10 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_units(Game *g) {
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead) continue;
|
||||||
|
if (u->faction != g->human) {
|
||||||
|
int tx = (int)(u->x / TILE), ty = (int)(u->y / TILE);
|
||||||
|
if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*g->map.w + tx] != 2) continue;
|
||||||
|
}
|
||||||
|
int sz = 28;
|
||||||
|
SDL_Texture *tex = g->spr.unit[u->type][u->faction];
|
||||||
|
draw_tex_world(g, tex, u->x, u->y, sz, sz, u->heading, 255);
|
||||||
|
/* selection ring */
|
||||||
|
if (u->selected) {
|
||||||
|
int cx = (int)(u->x - g->cam.x), cy = (int)(u->y - g->cam.y);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 120, 255, 120, 220);
|
||||||
|
for (int a = 0; a < 360; a += 12) {
|
||||||
|
int px = cx + (int)(cosf(a*3.14159f/180)*16);
|
||||||
|
int py = cy + (int)(sinf(a*3.14159f/180)*16);
|
||||||
|
SDL_RenderDrawPoint(g->ren, px, py);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* health bar */
|
||||||
|
if (u->hp < u->max_hp || u->selected) {
|
||||||
|
int cx = (int)(u->x - g->cam.x), by = (int)(u->y - g->cam.y) - 18;
|
||||||
|
int bw = 24;
|
||||||
|
SDL_Rect bg = { cx - bw/2, by, bw, 4 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 50, 50, 50, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &bg);
|
||||||
|
float f = u->hp / u->max_hp;
|
||||||
|
SDL_Color c = (u->faction == FACTION_SURVIVOR) ? (SDL_Color){90,200,90,255}
|
||||||
|
: (SDL_Color){220,110,220,255};
|
||||||
|
SDL_Rect fg = { cx - bw/2, by, (int)(bw * f), 4 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &fg);
|
||||||
|
/* cargo indicator for harvesters */
|
||||||
|
if (u->type == UT_HARVESTER && u->cargo > 0) {
|
||||||
|
SDL_Rect cg = { cx - bw/2, by - 4, (int)(bw * u->cargo / 1000), 2 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 230, 190, 70, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &cg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_bullets(Game *g) {
|
||||||
|
for (int i = 0; i < g->bullet_count; i++) {
|
||||||
|
Bullet *b = &g->bullets[i];
|
||||||
|
int sx = (int)(b->x - g->cam.x), sy = (int)(b->y - g->cam.y);
|
||||||
|
SDL_Texture *tex = g->spr.bullet[b->kind < 4 ? b->kind : 0];
|
||||||
|
SDL_Rect r = { sx - 5, sy - 5, 10, 10 };
|
||||||
|
SDL_RenderCopy(g->ren, tex, NULL, &r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_particles(Game *g) {
|
||||||
|
for (int i = 0; i < g->particle_count; i++) {
|
||||||
|
Particle *p = &g->particles[i];
|
||||||
|
int sx = (int)(p->x - g->cam.x), sy = (int)(p->y - g->cam.y);
|
||||||
|
Uint8 a = (Uint8)(255 * (p->life / p->max_life));
|
||||||
|
SDL_SetRenderDrawColor(g->ren, p->r, p->g, p->b, a);
|
||||||
|
int sz = (p->kind == 1) ? 3 : 2;
|
||||||
|
SDL_RenderFillRect(g->ren, &(SDL_Rect){ sx-sz/2, sy-sz/2, sz, sz });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_selection(Game *g) {
|
||||||
|
if (g->dragging) {
|
||||||
|
int x = g->drag_x0 < g->drag_x1 ? g->drag_x0 : g->drag_x1;
|
||||||
|
int y = g->drag_y0 < g->drag_y1 ? g->drag_y0 : g->drag_y1;
|
||||||
|
int w = abs(g->drag_x1 - g->drag_x0);
|
||||||
|
int h = abs(g->drag_y1 - g->drag_y0);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 120, 255, 120, 120);
|
||||||
|
SDL_RenderDrawRect(g->ren, &(SDL_Rect){x, y, w, h});
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 120, 255, 120, 30);
|
||||||
|
SDL_RenderFillRect(g->ren, &(SDL_Rect){x, y, w, h});
|
||||||
|
}
|
||||||
|
/* placement ghost */
|
||||||
|
if (g->placing) {
|
||||||
|
int mx, my; SDL_GetMouseState(&mx, &my);
|
||||||
|
float wx, wy; input_screen_to_world(g, mx, my, &wx, &wy);
|
||||||
|
int tx, ty; map_world_to_tile(wx, wy, &tx, &ty);
|
||||||
|
int ok = bld_can_place(g, g->place_type, tx, ty);
|
||||||
|
const BldDef *d = bld_def(g->place_type);
|
||||||
|
int sx = (int)(tx*TILE - g->cam.x), sy = (int)(ty*TILE - g->cam.y);
|
||||||
|
SDL_Rect r = { sx, sy, d->w*TILE, d->h*TILE };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, ok ? 120 : 220, ok ? 220 : 80, 80, 140);
|
||||||
|
SDL_RenderFillRect(g->ren, &r);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, ok ? 120 : 220, ok ? 220 : 80, 80, 255);
|
||||||
|
SDL_RenderDrawRect(g->ren, &r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hud_layout(Game *g, HudButton *out, int *n) {
|
||||||
|
int count = 0;
|
||||||
|
int bx = 12, by = VIEW_H - 64, bw = 92, bh = 52, gap = 6;
|
||||||
|
|
||||||
|
/* always-available structures (player faction) */
|
||||||
|
BuildingType structs[] = { BT_WARFACTORY, BT_REFINERY, BT_POWER,
|
||||||
|
BT_RESEARCH, BT_REPAIR, BT_TURRET, BT_WALL };
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
const BldDef *d = bld_def(structs[i]);
|
||||||
|
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh;
|
||||||
|
out[count].kind = 0; out[count].id = structs[i];
|
||||||
|
out[count].label = d->name; out[count].cost = d->cost;
|
||||||
|
out[count].enabled = (g->scrap[g->human] >= d->cost);
|
||||||
|
bx += bw + gap; count++;
|
||||||
|
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if a production building is selected, show its units */
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead || !b->selected || b->faction != g->human) continue;
|
||||||
|
const BldDef *d = bld_def(b->type);
|
||||||
|
for (int k = 0; k < d->produces_count; k++) {
|
||||||
|
const UnitDef *ud = unit_def((UnitType)d->produces[k], g->human);
|
||||||
|
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh;
|
||||||
|
out[count].kind = 1; out[count].id = d->produces[k];
|
||||||
|
out[count].label = ud->name; out[count].cost = ud->cost;
|
||||||
|
out[count].enabled = (g->scrap[g->human] >= ud->cost);
|
||||||
|
bx += bw + gap; count++;
|
||||||
|
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* if a research lab is selected, show upgrade buttons */
|
||||||
|
{
|
||||||
|
int has_lab = 0;
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead || !b->selected || b->faction != g->human) continue;
|
||||||
|
const BldDef *d = bld_def(b->type);
|
||||||
|
if (d->is_research) { has_lab = 1; break; }
|
||||||
|
}
|
||||||
|
if (has_lab) {
|
||||||
|
for (int u = 0; u < UP_COUNT; u++) {
|
||||||
|
int lvl = g->upg[g->human].lvl[u];
|
||||||
|
int maxed = (lvl >= UP_MAX_LVL);
|
||||||
|
int cost = maxed ? 0 : research_cost(u, lvl);
|
||||||
|
int busy = (g->research_cur[g->human] == u);
|
||||||
|
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh;
|
||||||
|
out[count].kind = 2; out[count].id = u;
|
||||||
|
out[count].label = research_name(u);
|
||||||
|
out[count].cost = cost;
|
||||||
|
out[count].enabled = !maxed && !busy && g->scrap[g->human] >= cost;
|
||||||
|
bx += bw + gap; count++;
|
||||||
|
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* command buttons for the current unit selection (appended to build row) */
|
||||||
|
{
|
||||||
|
int sel_units = 0, sel_harv = 0;
|
||||||
|
for (int i = 0; i < g->sel_count; i++) {
|
||||||
|
Unit *u = unit_by_id(g, g->selection[i]);
|
||||||
|
if (u && u->faction == g->human) { sel_units++; if (u->type == UT_HARVESTER) sel_harv++; }
|
||||||
|
}
|
||||||
|
if (sel_units > 0) {
|
||||||
|
#define ADDCMD(cid, lab) do { \
|
||||||
|
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh; \
|
||||||
|
out[count].kind = 3; out[count].id = (cid); out[count].label = (lab); \
|
||||||
|
out[count].enabled = 1; out[count].cost = 0; count++; \
|
||||||
|
bx += bw + gap; \
|
||||||
|
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); } \
|
||||||
|
} while (0)
|
||||||
|
ADDCMD(0, "Stop");
|
||||||
|
ADDCMD(1, "Attack");
|
||||||
|
ADDCMD(4, "Hold");
|
||||||
|
if (sel_harv > 0) {
|
||||||
|
ADDCMD(2, "Gather");
|
||||||
|
ADDCMD(3, "Return");
|
||||||
|
}
|
||||||
|
#undef ADDCMD
|
||||||
|
/* special-ability button (siege / heal) when applicable */
|
||||||
|
int ab = AB_NONE;
|
||||||
|
for (int i = 0; i < g->sel_count; i++) {
|
||||||
|
Unit *uu = unit_by_id(g, g->selection[i]);
|
||||||
|
if (uu && uu->faction == g->human && uu->ability != AB_NONE) { ab = uu->ability; break; }
|
||||||
|
}
|
||||||
|
if (ab != AB_NONE) {
|
||||||
|
const char *lab = (ab == AB_SIEGE) ? "Siege" : "Heal";
|
||||||
|
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh;
|
||||||
|
out[count].kind = 4; out[count].id = ab; out[count].label = lab;
|
||||||
|
out[count].enabled = 1; out[count].cost = 0; count++;
|
||||||
|
bx += bw + gap;
|
||||||
|
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*n = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char g_btn_label[256][64];
|
||||||
|
void render_build_menu(Game *g) {
|
||||||
|
HudButton btns[64]; int n = 0;
|
||||||
|
hud_layout(g, btns, &n);
|
||||||
|
/* panel background (covers build bar + command row) */
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 20, 20, 24, 230);
|
||||||
|
SDL_RenderFillRect(g->ren, &(SDL_Rect){0, VIEW_H - 138, VIEW_W, 138});
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 90, 160, 70, 255);
|
||||||
|
SDL_RenderDrawLine(g->ren, 0, VIEW_H - 138, VIEW_W, VIEW_H - 138);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 60, 90, 50, 255);
|
||||||
|
SDL_RenderDrawLine(g->ren, 0, VIEW_H - 72, VIEW_W, VIEW_H - 72);
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
SDL_Rect r = { btns[i].x, btns[i].y, btns[i].w, btns[i].h };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, btns[i].enabled ? 50 : 35,
|
||||||
|
btns[i].enabled ? 70 : 40,
|
||||||
|
btns[i].enabled ? 55 : 38, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &r);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, btns[i].enabled ? 120 : 70,
|
||||||
|
btns[i].enabled ? 200 : 90,
|
||||||
|
btns[i].enabled ? 120 : 70, 255);
|
||||||
|
SDL_RenderDrawRect(g->ren, &r);
|
||||||
|
SDL_Color txt = btns[i].enabled ? (SDL_Color){230,230,230,255}
|
||||||
|
: (SDL_Color){120,120,120,255};
|
||||||
|
snprintf(g_btn_label[i], 64, "%s", btns[i].label);
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_small, g_btn_label[i],
|
||||||
|
btns[i].x + 4, btns[i].y + 4, txt);
|
||||||
|
char cost[32]; snprintf(cost, 32, "%d scrap", btns[i].cost);
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_small, cost,
|
||||||
|
btns[i].x + 4, btns[i].y + btns[i].h - 18,
|
||||||
|
(SDL_Color){230,190,70,255});
|
||||||
|
/* level marker for research buttons */
|
||||||
|
if (btns[i].kind == 2) {
|
||||||
|
int lvl = g->upg[g->human].lvl[btns[i].id];
|
||||||
|
char lvs[32];
|
||||||
|
if (lvl >= UP_MAX_LVL) snprintf(lvs, 32, "MAX");
|
||||||
|
else if (g->research_cur[g->human] == btns[i].id)
|
||||||
|
snprintf(lvs, 32, "L%d..%d%%", lvl, (int)(g->research_prog[g->human]*100));
|
||||||
|
else snprintf(lvs, 32, "L%d", lvl);
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_small, lvs,
|
||||||
|
btns[i].x + 4, btns[i].y + btns[i].h - 34,
|
||||||
|
(SDL_Color){150,210,255,255});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_hud(Game *g) {
|
||||||
|
/* top resource bar */
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 16, 16, 20, 220);
|
||||||
|
SDL_RenderFillRect(g->ren, &(SDL_Rect){0, 0, VIEW_W, 28});
|
||||||
|
char buf[128];
|
||||||
|
snprintf(buf, sizeof buf, "%s - Scrap: %d", faction_name(g->human),
|
||||||
|
g->scrap[g->human]);
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_med, buf, 10, 4,
|
||||||
|
(SDL_Color){230,230,230,255});
|
||||||
|
|
||||||
|
/* selection summary */
|
||||||
|
int n_units = 0, n_bld = 0;
|
||||||
|
for (int i = 0; i < g->sel_count; i++) {
|
||||||
|
int id = g->selection[i];
|
||||||
|
if (unit_by_id(g, id)) n_units++;
|
||||||
|
else if (bld_by_id(g, id)) n_bld++;
|
||||||
|
}
|
||||||
|
if (n_units > 0 || n_bld > 0) {
|
||||||
|
snprintf(buf, sizeof buf, "Selected: %d units, %d buildings",
|
||||||
|
n_units, n_bld);
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_small, buf,
|
||||||
|
VIEW_W - 260, 6, (SDL_Color){200,220,200,255});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hints */
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_small,
|
||||||
|
"LMB select/drag RMB move/attack A select-all [B] build Space pause F5 save",
|
||||||
|
10, VIEW_H - 90, (SDL_Color){150,150,150,255});
|
||||||
|
|
||||||
|
/* objective */
|
||||||
|
const Mission *m = mission_get(g->mission_id);
|
||||||
|
const char *obj = (g->objective == OBJ_SURVIVE) ? "Survive the assault"
|
||||||
|
: (g->objective == OBJ_GATHER) ? "Stockpile scrap"
|
||||||
|
: "Destroy all enemies";
|
||||||
|
char obuf[128];
|
||||||
|
if (g->objective == OBJ_SURVIVE)
|
||||||
|
snprintf(obuf, sizeof obuf, "Objective: %s (%.0f/%.0f s)", obj,
|
||||||
|
g->time, m->survive_time);
|
||||||
|
else if (g->objective == OBJ_GATHER)
|
||||||
|
snprintf(obuf, sizeof obuf, "Objective: %s (%d/%d)", obj,
|
||||||
|
g->scrap[g->human], m->scrap_goal);
|
||||||
|
else
|
||||||
|
snprintf(obuf, sizeof obuf, "Objective: %s", obj);
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_small, obuf,
|
||||||
|
VIEW_W/2 - 90, 6, (SDL_Color){230,230,160,255});
|
||||||
|
|
||||||
|
if (g->state == GAME_PAUSED)
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_big, "PAUSED",
|
||||||
|
VIEW_W/2 - 70, VIEW_H/2 - 20, (SDL_Color){255,255,255,255});
|
||||||
|
if (g->state == GAME_WON)
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_big, "VICTORY",
|
||||||
|
VIEW_W/2 - 80, VIEW_H/2 - 20, (SDL_Color){120,255,120,255});
|
||||||
|
if (g->state == GAME_LOST)
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_big, "DEFEAT",
|
||||||
|
VIEW_W/2 - 70, VIEW_H/2 - 20, (SDL_Color){255,120,120,255});
|
||||||
|
if (g->state == GAME_WON || g->state == GAME_LOST)
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_med, "Press M for mission menu",
|
||||||
|
VIEW_W/2 - 120, VIEW_H/2 + 20, (SDL_Color){220,220,220,255});
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_minimap(Game *g) {
|
||||||
|
int mw = 200, mh = 200;
|
||||||
|
int mx = VIEW_W - mw - 10, my = 36;
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 220);
|
||||||
|
SDL_RenderFillRect(g->ren, &(SDL_Rect){mx-2, my-2, mw+4, mh+4});
|
||||||
|
float sx = (float)mw / MAP_W, sy = (float)mh / MAP_H;
|
||||||
|
for (int ty = 0; ty < MAP_H; ty++) {
|
||||||
|
for (int tx = 0; tx < MAP_W; tx++) {
|
||||||
|
uint8_t fv = g->map.fog[ty*MAP_W + tx];
|
||||||
|
if (fv == 0) continue;
|
||||||
|
Tile *t = map_tile(&g->map, tx, ty);
|
||||||
|
Uint8 cr=120,cg=104,cb=78;
|
||||||
|
switch (t->terrain) {
|
||||||
|
case T_WATER: cr=44;cg=78;cb=120; break;
|
||||||
|
case T_ROCK: cr=110;cg=104;cb=98; break;
|
||||||
|
case T_SCRAP: cr=200;cg=170;cb=70; break;
|
||||||
|
case T_GRASS: cr=86;cg=120;cb=64; break;
|
||||||
|
}
|
||||||
|
if (fv == 1) { cr/=2; cg/=2; cb/=2; }
|
||||||
|
SDL_SetRenderDrawColor(g->ren, cr, cg, cb, 255);
|
||||||
|
SDL_RenderDrawPoint(g->ren, mx + (int)(tx*sx), my + (int)(ty*sy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead) continue;
|
||||||
|
if (u->faction != g->human) {
|
||||||
|
int tx = (int)(u->x/TILE), ty = (int)(u->y/TILE);
|
||||||
|
if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*MAP_W+tx] != 2) continue;
|
||||||
|
}
|
||||||
|
SDL_Color c = faction_color(u->faction);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255);
|
||||||
|
SDL_RenderDrawPoint(g->ren, mx + (int)(u->x/MAP_PIX_W*mw),
|
||||||
|
my + (int)(u->y/MAP_PIX_H*mh));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead) continue;
|
||||||
|
if (b->faction != g->human) {
|
||||||
|
int tx = b->tx + b->w/2, ty = b->ty + b->h/2;
|
||||||
|
if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*MAP_W+tx] != 2) continue;
|
||||||
|
}
|
||||||
|
SDL_Color c = faction_color(b->faction);
|
||||||
|
int px = mx + (int)((b->tx*TILE)/MAP_PIX_W*mw);
|
||||||
|
int py = my + (int)((b->ty*TILE)/MAP_PIX_H*mh);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &(SDL_Rect){px, py, 2, 2});
|
||||||
|
}
|
||||||
|
/* viewport box */
|
||||||
|
int vx = mx + (int)(g->cam.x / MAP_PIX_W * mw);
|
||||||
|
int vy = my + (int)(g->cam.y / MAP_PIX_H * mh);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 255, 255, 255, 200);
|
||||||
|
SDL_RenderDrawRect(g->ren, &(SDL_Rect){vx, vy,
|
||||||
|
(int)(VIEW_W/MAP_PIX_W*mw), (int)(VIEW_H/MAP_PIX_H*mh)});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void render_wrapped(Game *g, TTF_Font *f, const char *txt,
|
||||||
|
int x, int y, int maxw, SDL_Color c) {
|
||||||
|
/* greedy word wrap */
|
||||||
|
char buf[1024];
|
||||||
|
strncpy(buf, txt, sizeof buf - 1); buf[sizeof buf - 1] = 0;
|
||||||
|
char *tok = strtok(buf, " ");
|
||||||
|
char line[256]; line[0] = 0;
|
||||||
|
int ly = y;
|
||||||
|
while (tok) {
|
||||||
|
int cur = (int)strlen(line);
|
||||||
|
int add = (int)strlen(tok) + (cur ? 1 : 0);
|
||||||
|
if (cur + add >= (int)sizeof line || cur + add > maxw/8) {
|
||||||
|
render_text(&g->spr, g->ren, f, line, x, ly, c);
|
||||||
|
ly += 22; line[0] = 0; cur = 0;
|
||||||
|
}
|
||||||
|
if (cur) { strcat(line, " "); strcat(line, tok); }
|
||||||
|
else strcpy(line, tok);
|
||||||
|
tok = strtok(NULL, " ");
|
||||||
|
}
|
||||||
|
if (line[0]) render_text(&g->spr, g->ren, f, line, x, ly, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_menu(Game *g) {
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 18, 16, 14, 255);
|
||||||
|
SDL_RenderClear(g->ren);
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_big,
|
||||||
|
"KRUSH, KILL 'N' DESTROY", VIEW_W/2 - 260, 36, (SDL_Color){230,200,90,255});
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_med,
|
||||||
|
"Recreated post-apocalyptic RTS - choose a mission:", VIEW_W/2 - 230, 84,
|
||||||
|
(SDL_Color){220,220,220,255});
|
||||||
|
|
||||||
|
int n = mission_count();
|
||||||
|
int lx = 40, lw = 470, rh = 34;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
const Mission *m = mission_get(i);
|
||||||
|
int y = 124 + i * rh;
|
||||||
|
int locked = (m->id >= g->unlocked);
|
||||||
|
int sel = (i == g->menu_sel);
|
||||||
|
SDL_Rect r = { lx, y, lw, rh - 4 };
|
||||||
|
SDL_SetRenderDrawColor(g->ren, sel ? 54 : 40, sel ? 60 : 44, sel ? 48 : 38, 255);
|
||||||
|
SDL_RenderFillRect(g->ren, &r);
|
||||||
|
SDL_SetRenderDrawColor(g->ren, sel ? 230 : 120, sel ? 200 : 200, sel ? 90 : 110, 255);
|
||||||
|
SDL_RenderDrawRect(g->ren, &r);
|
||||||
|
char line[200];
|
||||||
|
const char *obj = (m->objective == OBJ_SURVIVE) ? "Survive"
|
||||||
|
: (m->objective == OBJ_GATHER) ? "Gather"
|
||||||
|
: "Destroy";
|
||||||
|
snprintf(line, sizeof line, locked ? "%d. %s [LOCKED]"
|
||||||
|
: "%d. %s (%s, %s)",
|
||||||
|
m->id, m->name, obj, m->diff==DIFF_HARD ? "Hard" : "Normal");
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_med, line, lx + 10, y + 4,
|
||||||
|
locked ? (SDL_Color){120,120,120,255}
|
||||||
|
: (SDL_Color){230,230,230,255});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* briefing panel for the highlighted mission */
|
||||||
|
const Mission *m = mission_get(g->menu_sel);
|
||||||
|
int px = lx + lw + 30, pw = VIEW_W - px - 30;
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_big, m->name, px, 124,
|
||||||
|
(SDL_Color){230,200,90,255});
|
||||||
|
char meta[256];
|
||||||
|
snprintf(meta, sizeof meta,
|
||||||
|
"Enemy: %s Difficulty: %s Objective: %s",
|
||||||
|
faction_name(m->enemy),
|
||||||
|
m->diff==DIFF_HARD ? "Hard" : "Normal",
|
||||||
|
m->objective==OBJ_SURVIVE ? "Survive the assault"
|
||||||
|
: m->objective==OBJ_GATHER ? "Stockpile scrap" : "Destroy all enemies");
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_small, meta, px, 158,
|
||||||
|
(SDL_Color){180,210,255,255});
|
||||||
|
render_wrapped(g, g->spr.font_med, m->brief, px, 188, pw,
|
||||||
|
(SDL_Color){220,220,220,255});
|
||||||
|
|
||||||
|
char hint[256];
|
||||||
|
snprintf(hint, sizeof hint,
|
||||||
|
"Up/Down select Enter start [F] side: %s 0-9 jump. Factions: Survivors/Evolved/Series 9.",
|
||||||
|
faction_name(g->human));
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_small, hint,
|
||||||
|
lx, VIEW_H - 28, (SDL_Color){150,150,150,255});
|
||||||
|
SDL_RenderPresent(g->ren);
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_message_log(Game *g) {
|
||||||
|
if (g->log_count <= 0) return;
|
||||||
|
int x = 12;
|
||||||
|
int y = VIEW_H - 200; /* above the build bar */
|
||||||
|
int shown = (g->log_count < 6) ? g->log_count : 6;
|
||||||
|
for (int i = 0; i < shown; i++) {
|
||||||
|
int idx = (g->log_next - shown + i + 32) % 32;
|
||||||
|
render_text(&g->spr, g->ren, g->spr.font_small, g->log_msg[idx],
|
||||||
|
x, y + i * 18, (SDL_Color){210, 230, 180, 255});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_frame(Game *g) {
|
||||||
|
if (g->state == GAME_MENU) { render_menu(g); return; }
|
||||||
|
/* camera shake */
|
||||||
|
float shx = 0, shy = 0;
|
||||||
|
if (g->cam.shake > 0) {
|
||||||
|
shx = ((float)(rand() % 21) - 10) / 10.0f * g->cam.shake;
|
||||||
|
shy = ((float)(rand() % 21) - 10) / 10.0f * g->cam.shake;
|
||||||
|
g->cam.x += shx; g->cam.y += shy;
|
||||||
|
}
|
||||||
|
SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 255);
|
||||||
|
SDL_RenderClear(g->ren);
|
||||||
|
render_world(g);
|
||||||
|
render_wrecks(g);
|
||||||
|
render_buildings(g);
|
||||||
|
render_units(g);
|
||||||
|
render_bullets(g);
|
||||||
|
render_particles(g);
|
||||||
|
render_selection(g);
|
||||||
|
render_build_menu(g);
|
||||||
|
render_hud(g);
|
||||||
|
render_minimap(g);
|
||||||
|
render_message_log(g);
|
||||||
|
SDL_RenderPresent(g->ren);
|
||||||
|
if (g->cam.shake > 0) { g->cam.x -= shx; g->cam.y -= shy; }
|
||||||
|
}
|
||||||
42
engine-c/src/rng.c
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* xoroshiro128** deterministic RNG */
|
||||||
|
|
||||||
|
void rng_seed(RNG *r, uint64_t seed) {
|
||||||
|
if (seed == 0) seed = 0x9E3779B97F4A7C15ULL;
|
||||||
|
r->s[0] = seed;
|
||||||
|
r->s[1] = seed ^ 0xBF58476D1CE4E5B9ULL;
|
||||||
|
if (r->s[0] == 0) r->s[0] = 1;
|
||||||
|
if (r->s[1] == 0) r->s[1] = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t rotl(uint64_t x, int k) {
|
||||||
|
return (x << k) | (x >> (64 - k));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t rng_u64(RNG *r) {
|
||||||
|
uint64_t s0 = r->s[0];
|
||||||
|
uint64_t s1 = r->s[1];
|
||||||
|
uint64_t result = rotl(s0 * 5, 7) * 9;
|
||||||
|
s1 ^= s0;
|
||||||
|
r->s[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16);
|
||||||
|
r->s[1] = rotl(s1, 37);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rng_u32(RNG *r) {
|
||||||
|
return (uint32_t)(rng_u64(r) >> 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rng_range(RNG *r, int lo, int hi) {
|
||||||
|
if (hi <= lo) return lo;
|
||||||
|
return lo + (int)(rng_u32(r) % (uint32_t)(hi - lo + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
float rng_f32(RNG *r) {
|
||||||
|
return (float)(rng_u32(r) >> 8) / (float)(1 << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
float rng_frange(RNG *r, float lo, float hi) {
|
||||||
|
return lo + rng_f32(r) * (hi - lo);
|
||||||
|
}
|
||||||
87
engine-c/src/save.c
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* save.c - Binary save / load of skirmish state.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
static void w32(FILE *f, uint32_t v) { fwrite(&v, 4, 1, f); }
|
||||||
|
static void wf(FILE *f, float v) { fwrite(&v, 4, 1, f); }
|
||||||
|
static void wi(FILE *f, int v) { fwrite(&v, 4, 1, f); }
|
||||||
|
static uint32_t r32(FILE *f) { uint32_t v = 0; fread(&v, 4, 1, f); return v; }
|
||||||
|
static float rf(FILE *f) { float v = 0; fread(&v, 4, 1, f); return v; }
|
||||||
|
static int ri(FILE *f) { int v = 0; fread(&v, 4, 1, f); return v; }
|
||||||
|
|
||||||
|
int save_game(Game *g, const char *path) {
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
if (!f) return 0;
|
||||||
|
w32(f, SAVE_MAGIC);
|
||||||
|
w32(f, SAVE_VERSION);
|
||||||
|
wi(f, g->map.w); wi(f, g->map.h);
|
||||||
|
size_t n = (size_t)g->map.w * g->map.h;
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
Tile *t = &g->map.tiles[i];
|
||||||
|
uint8_t buf[4] = { t->terrain, t->walkable, t->scrap, t->occupied };
|
||||||
|
fwrite(buf, 1, 4, f);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < MAX_FACTIONS; i++) wf(f, (float)g->scrap[i]);
|
||||||
|
wf(f, g->time);
|
||||||
|
wi(f, g->unit_count); wi(f, g->unit_next_id);
|
||||||
|
fwrite(g->units, sizeof(Unit), (size_t)g->unit_count, f);
|
||||||
|
wi(f, g->bld_count); wi(f, g->bld_next_id);
|
||||||
|
fwrite(g->buildings, sizeof(Building), (size_t)g->bld_count, f);
|
||||||
|
wf(f, g->cam.x); wf(f, g->cam.y);
|
||||||
|
/* upgrades / research / mission */
|
||||||
|
wi(f, g->mission_id); wi(f, g->objective);
|
||||||
|
wi(f, (int)g->human); wi(f, (int)g->enemy);
|
||||||
|
for (int i = 0; i < MAX_FACTIONS; i++) {
|
||||||
|
for (int u = 0; u < UP_COUNT; u++) wi(f, g->upg[i].lvl[u]);
|
||||||
|
wi(f, g->research_lab[i]);
|
||||||
|
wi(f, g->research_cur[i]);
|
||||||
|
wf(f, g->research_prog[i]);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
engine_log("Game saved to %s", path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int load_game(Game *g, const char *path) {
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return 0;
|
||||||
|
if (r32(f) != SAVE_MAGIC) { fclose(f); return 0; }
|
||||||
|
if (r32(f) != SAVE_VERSION) { fclose(f); return 0; }
|
||||||
|
int w = ri(f), h = ri(f);
|
||||||
|
map_free(&g->map);
|
||||||
|
g->map.w = w; g->map.h = h;
|
||||||
|
g->map.tiles = (Tile *)calloc((size_t)w*h, sizeof(Tile));
|
||||||
|
size_t n = (size_t)w*h;
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
uint8_t buf[4];
|
||||||
|
if (fread(buf, 1, 4, f) != 4) break;
|
||||||
|
g->map.tiles[i].terrain = buf[0];
|
||||||
|
g->map.tiles[i].walkable = buf[1];
|
||||||
|
g->map.tiles[i].scrap = buf[2];
|
||||||
|
g->map.tiles[i].occupied = buf[3];
|
||||||
|
}
|
||||||
|
for (int i = 0; i < MAX_FACTIONS; i++) g->scrap[i] = (int)rf(f);
|
||||||
|
g->time = rf(f);
|
||||||
|
g->unit_count = ri(f); g->unit_next_id = ri(f);
|
||||||
|
if (g->unit_count > MAX_UNITS) g->unit_count = MAX_UNITS;
|
||||||
|
fread(g->units, sizeof(Unit), (size_t)g->unit_count, f);
|
||||||
|
g->bld_count = ri(f); g->bld_next_id = ri(f);
|
||||||
|
if (g->bld_count > MAX_BUILDINGS) g->bld_count = MAX_BUILDINGS;
|
||||||
|
fread(g->buildings, sizeof(Building), (size_t)g->bld_count, f);
|
||||||
|
g->cam.x = rf(f); g->cam.y = rf(f);
|
||||||
|
g->mission_id = ri(f); g->objective = ri(f);
|
||||||
|
g->human = (Faction)ri(f); g->enemy = (Faction)ri(f);
|
||||||
|
for (int i = 0; i < MAX_FACTIONS; i++) {
|
||||||
|
for (int u = 0; u < UP_COUNT; u++) g->upg[i].lvl[u] = ri(f);
|
||||||
|
g->research_lab[i] = ri(f);
|
||||||
|
g->research_cur[i] = ri(f);
|
||||||
|
g->research_prog[i] = rf(f);
|
||||||
|
}
|
||||||
|
g->sel_count = 0; g->placing = 0;
|
||||||
|
g->state = GAME_PLAYING;
|
||||||
|
fclose(f);
|
||||||
|
engine_log("Game loaded from %s", path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
148
engine-c/src/snapshot.c
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* Software snapshot renderer - draws the game world into an RGBA buffer using
|
||||||
|
* only primitive shapes (no SDL gpu readback, which is broken on this box due
|
||||||
|
* to an SDL2/SDL3 conflict). Output is saved as PNG via png_save().
|
||||||
|
*/
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
#define SW VIEW_W
|
||||||
|
#define SH VIEW_H
|
||||||
|
|
||||||
|
static uint8_t *g_buf;
|
||||||
|
|
||||||
|
static void px(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
if (x < 0 || y < 0 || x >= SW || y >= SH) return;
|
||||||
|
int i = (y * SW + x) * 4;
|
||||||
|
g_buf[i] = r; g_buf[i+1] = g; g_buf[i+2] = b; g_buf[i+3] = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rectf(int x0, int y0, int x1, int y1, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
if (x0 > x1) { int t = x0; x0 = x1; x1 = t; }
|
||||||
|
if (y0 > y1) { int t = y0; y0 = y1; y1 = t; }
|
||||||
|
for (int y = y0; y <= y1; y++)
|
||||||
|
for (int x = x0; x <= x1; x++)
|
||||||
|
px(x, y, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void disc(int cx, int cy, int rad, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
for (int y = -rad; y <= rad; y++)
|
||||||
|
for (int x = -rad; x <= rad; x++)
|
||||||
|
if (x*x + y*y <= rad*rad) px(cx+x, cy+y, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t faction_r(Faction f){ return f==FACTION_SURVIVOR?70 : f==FACTION_EVOLVED?200 : 230; }
|
||||||
|
static uint8_t faction_g(Faction f){ return f==FACTION_SURVIVOR?180: f==FACTION_EVOLVED?70 : 60; }
|
||||||
|
static uint8_t faction_b(Faction f){ return f==FACTION_SURVIVOR?90 : f==FACTION_EVOLVED?210 : 230; }
|
||||||
|
|
||||||
|
static void terrain_color(TerrainType t, uint8_t *r, uint8_t *g, uint8_t *b) {
|
||||||
|
switch (t) {
|
||||||
|
case T_GRASS: *r=46; *g=92; *b=46; break;
|
||||||
|
case T_ROAD: *r=90; *g=90; *b=80; break;
|
||||||
|
case T_RUBBLE:*r=80; *g=72; *b=64; break;
|
||||||
|
case T_ROCK: *r=80; *g=80; *b=86; break;
|
||||||
|
case T_WATER: *r=40; *g=60; *b=120; break;
|
||||||
|
case T_SCRAP: *r=70; *g=64; *b=40; break;
|
||||||
|
default: *r=50; *g=50; *b=50; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void snapshot_capture(Game *g, const char *path) {
|
||||||
|
uint8_t *buf = (uint8_t *)malloc((size_t)SW * SH * 4);
|
||||||
|
if (!buf) return;
|
||||||
|
g_buf = buf;
|
||||||
|
for (int i = 0; i < SW*SH; i++) { buf[i*4]=0; buf[i*4+1]=0; buf[i*4+2]=0; buf[i*4+3]=255; }
|
||||||
|
|
||||||
|
/* camera: centre on centroid of human units, else map centre */
|
||||||
|
float cx = MAP_PIX_W/2, cy = MAP_PIX_H/2;
|
||||||
|
int nc = 0;
|
||||||
|
for (int i = 0; i < g->unit_count; i++)
|
||||||
|
if (!g->units[i].dead && g->units[i].faction == g->human) { cx += g->units[i].x; cy += g->units[i].y; nc++; }
|
||||||
|
if (nc) { cx /= nc; cy /= nc; }
|
||||||
|
float camx = cx - SW/2, camy = cy - SH/2;
|
||||||
|
if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||||||
|
if (camx > MAP_PIX_W - SW) camx = MAP_PIX_W - SW;
|
||||||
|
if (camy > MAP_PIX_H - SH) camy = MAP_PIX_H - SH;
|
||||||
|
|
||||||
|
/* terrain */
|
||||||
|
int tx0 = (int)camx / TILE, ty0 = (int)camy / TILE;
|
||||||
|
int tx1 = (int)(camx + SW) / TILE, ty1 = (int)(camy + SH) / TILE;
|
||||||
|
for (int ty = ty0; ty <= ty1; ty++) {
|
||||||
|
for (int tx = tx0; tx <= tx1; tx++) {
|
||||||
|
if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_W) continue;
|
||||||
|
Tile *t = map_tile(&g->map, tx, ty);
|
||||||
|
uint8_t r,g,b; terrain_color(t->terrain, &r,&g,&b);
|
||||||
|
int sx = tx*TILE - (int)camx, sy = ty*TILE - (int)camy;
|
||||||
|
rectf(sx, sy, sx+TILE-1, sy+TILE-1, r, g, b);
|
||||||
|
if (t->terrain == T_SCRAP && t->scrap > 0) {
|
||||||
|
int cc = (sx+TILE/2), cr = (sy+TILE/2);
|
||||||
|
for (int k=0;k<t->scrap/20;k++)
|
||||||
|
disc(cc + ((k*7)%10)-5, cr + ((k*5)%10)-5, 2, 150,140,90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fog overlay */
|
||||||
|
for (int ty = ty0; ty <= ty1; ty++)
|
||||||
|
for (int tx = tx0; tx <= tx1; tx++) {
|
||||||
|
if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_W) continue;
|
||||||
|
int f = g->map.fog[ty*MAP_W + tx];
|
||||||
|
if (f == 0) { int sx=tx*TILE-(int)camx, sy=ty*TILE-(int)camy; rectf(sx,sy,sx+TILE-1,sy+TILE-1,0,0,0); }
|
||||||
|
else if (f == 1) { int sx=tx*TILE-(int)camx, sy=ty*TILE-(int)camy; rectf(sx,sy,sx+TILE-1,sy+TILE-1,0,0,30); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buildings */
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead) continue;
|
||||||
|
int sx = (int)(b->tx*TILE - camx), sy = (int)(b->ty*TILE - camy);
|
||||||
|
int w = b->w*TILE, h = b->h*TILE;
|
||||||
|
if (sx+w < 0 || sy+h < 0 || sx > SW || sy > SH) continue;
|
||||||
|
uint8_t r=faction_r(b->faction),gg=faction_g(b->faction),bb=faction_b(b->faction);
|
||||||
|
rectf(sx, sy, sx+w-1, sy+h-1, r/2+30, gg/2+30, bb/2+30);
|
||||||
|
rectf(sx+2, sy+2, sx+w-3, sy+h-3, r, gg, bb);
|
||||||
|
if (b->build < 1.0f) rectf(sx, sy, sx+w-1, sy+(int)(h*b->build)-1, 255,255,255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* units */
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead) continue;
|
||||||
|
int sx = (int)(u->x - camx), sy = (int)(u->y - camy);
|
||||||
|
if (sx < -10 || sy < -10 || sx > SW+10 || sy > SH+10) continue;
|
||||||
|
uint8_t r=faction_r(u->faction),gg=faction_g(u->faction),bb=faction_b(u->faction);
|
||||||
|
int rad = u->type==UT_TANK||u->type==UT_BEAST?9:u->type==UT_HARVESTER?8:6;
|
||||||
|
disc(sx, sy, rad, r, gg, bb);
|
||||||
|
disc(sx, sy, 2, 255,255,255);
|
||||||
|
int hx = sx + (int)(cosf(u->heading)*rad), hy = sy + (int)(sinf(u->heading)*rad);
|
||||||
|
for (int k=0;k<rad;k++){ int xx=sx+(hx-sx)*k/rad, yy=sy+(hy-sy)*k/rad; px(xx,yy,230,230,230); }
|
||||||
|
if (u->selected) { rectf(sx-rad-2,sy-rad-2,sx+rad+2,sy+rad+2,255,255,120); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* projectiles */
|
||||||
|
for (int i = 0; i < g->bullet_count; i++) {
|
||||||
|
Bullet *p = &g->bullets[i];
|
||||||
|
int sx=(int)(p->x-camx), sy=(int)(p->y-camy);
|
||||||
|
px(sx,sy,255,230,120); px(sx+1,sy,255,230,120);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* minimap */
|
||||||
|
int mmx=SW-210, mmy=10, mms=200;
|
||||||
|
rectf(mmx-2,mmy-2,mmx+mms+1,mmy+mms+1,0,0,0);
|
||||||
|
for (int ty=0;ty<MAP_W;ty+=2) for (int tx=0;tx<MAP_W;tx+=2) {
|
||||||
|
int f=g->map.fog[ty*MAP_W+tx];
|
||||||
|
if (f==0) continue;
|
||||||
|
Tile *t=map_tile(&g->map,tx,ty);
|
||||||
|
uint8_t r,g,b; terrain_color(t->terrain,&r,&g,&b);
|
||||||
|
int dx=mmx + tx*mms/MAP_W, dy=mmy + ty*mms/MAP_W;
|
||||||
|
px(dx,dy,r,g,b);
|
||||||
|
}
|
||||||
|
for (int i=0;i<g->bld_count;i++){ Building*b=&g->buildings[i]; if(b->dead)continue;
|
||||||
|
int dx=mmx+b->tx*TILE*mms/(MAP_W*TILE), dy=mmy+b->ty*TILE*mms/(MAP_W*TILE);
|
||||||
|
px(dx,dy,faction_r(b->faction),faction_g(b->faction),faction_b(b->faction)); }
|
||||||
|
for (int i=0;i<g->unit_count;i++){ Unit*u=&g->units[i]; if(u->dead)continue;
|
||||||
|
int dx=mmx+(int)(u->x*mms/MAP_PIX_W), dy=mmy+(int)(u->y*mms/MAP_PIX_H);
|
||||||
|
px(dx,dy,faction_r(u->faction),faction_g(u->faction),faction_b(u->faction)); }
|
||||||
|
|
||||||
|
png_save(path, SW, SH, buf);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
341
engine-c/src/sprites.c
Normal file
|
|
@ -0,0 +1,341 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* sprites.c - Procedural 2D sprite/terrain generation.
|
||||||
|
* Recreates the chunky post-apocalyptic KKND look without using any
|
||||||
|
* original game assets. All art is drawn at runtime into SDL textures.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
#define SURF_FMT SDL_PIXELFORMAT_RGBA32
|
||||||
|
|
||||||
|
static SDL_Surface *new_surf(int w, int h) {
|
||||||
|
return SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SURF_FMT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void px(SDL_Surface *s, int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
|
||||||
|
if (x < 0 || y < 0 || x >= s->w || y >= s->h) return;
|
||||||
|
Uint32 *p = (Uint32 *)((Uint8 *)s->pixels + y * s->pitch);
|
||||||
|
Uint32 pix = (a << 24) | (b << 16) | (g << 8) | r;
|
||||||
|
p[x] = pix;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill_rect(SDL_Surface *s, int x, int y, int w, int h,
|
||||||
|
Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
|
||||||
|
for (int j = y; j < y + h; j++)
|
||||||
|
for (int i = x; i < x + w; i++)
|
||||||
|
px(s, i, j, r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void disc(SDL_Surface *s, int cx, int cy, int rad,
|
||||||
|
Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
|
||||||
|
for (int j = -rad; j <= rad; j++)
|
||||||
|
for (int i = -rad; i <= rad; i++)
|
||||||
|
if (i*i + j*j <= rad*rad)
|
||||||
|
px(s, cx + i, cy + j, r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ring(SDL_Surface *s, int cx, int cy, int rad, int t,
|
||||||
|
Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
|
||||||
|
for (int j = -rad; j <= rad; j++)
|
||||||
|
for (int i = -rad; i <= rad; i++) {
|
||||||
|
int d = i*i + j*j;
|
||||||
|
if (d <= rad*rad && d >= (rad - t)*(rad - t))
|
||||||
|
px(s, cx + i, cy + j, r, g, b, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void line(SDL_Surface *s, int x0, int y0, int x1, int y1,
|
||||||
|
Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
|
||||||
|
int dx = abs(x1 - x0), dy = -abs(y1 - y0);
|
||||||
|
int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1;
|
||||||
|
int err = dx + dy;
|
||||||
|
for (;;) {
|
||||||
|
px(s, x0, y0, r, g, b, a);
|
||||||
|
if (x0 == x1 && y0 == y1) break;
|
||||||
|
int e2 = 2 * err;
|
||||||
|
if (e2 >= dy) { err += dy; x0 += sx; }
|
||||||
|
if (e2 <= dx) { err += dx; y0 += sy; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_Color darken(SDL_Color c, float f) {
|
||||||
|
return (SDL_Color){
|
||||||
|
(Uint8)(c.r*f), (Uint8)(c.g*f), (Uint8)(c.b*f), c.a };
|
||||||
|
}
|
||||||
|
static SDL_Color lighten(SDL_Color c, float f) {
|
||||||
|
return (SDL_Color){
|
||||||
|
(Uint8)(c.r + (255-c.r)*f), (Uint8)(c.g + (255-c.g)*f),
|
||||||
|
(Uint8)(c.b + (255-c.b)*f), c.a };
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- unit sprites ---------------- */
|
||||||
|
SDL_Texture *sprites_make_unit(SDL_Renderer *r, UnitType t, Faction f) {
|
||||||
|
int S = 30;
|
||||||
|
SDL_Surface *s = new_surf(S, S);
|
||||||
|
SDL_Color base = faction_color(f);
|
||||||
|
SDL_Color dark = darken(base, 0.55f);
|
||||||
|
SDL_Color lite = lighten(base, 0.45f);
|
||||||
|
SDL_Color metal = { 60, 60, 70, 255 };
|
||||||
|
|
||||||
|
switch (t) {
|
||||||
|
case UT_INFANTRY:
|
||||||
|
disc(s, 15, 15, 8, dark.r, dark.g, dark.b, 255);
|
||||||
|
disc(s, 15, 15, 6, base.r, base.g, base.b, 255);
|
||||||
|
disc(s, 15, 12, 3, lite.r, lite.g, lite.b, 255);
|
||||||
|
line(s, 15, 15, 15, 2, 30, 30, 30, 255); /* rifle */
|
||||||
|
break;
|
||||||
|
case UT_BUGGY:
|
||||||
|
fill_rect(s, 6, 9, 18, 12, dark.r, dark.g, dark.b, 255);
|
||||||
|
fill_rect(s, 8, 11, 14, 8, base.r, base.g, base.b, 255);
|
||||||
|
disc(s, 10, 8, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
disc(s, 20, 8, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
disc(s, 10, 22, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
disc(s, 20, 22, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
line(s, 15, 15, 28, 15, 30, 30, 30, 255);
|
||||||
|
break;
|
||||||
|
case UT_TANK:
|
||||||
|
fill_rect(s, 5, 10, 20, 11, dark.r, dark.g, dark.b, 255);
|
||||||
|
fill_rect(s, 7, 12, 16, 7, base.r, base.g, base.b, 255);
|
||||||
|
disc(s, 15, 15, 6, lite.r, lite.g, lite.b, 255);
|
||||||
|
line(s, 15, 15, 30, 15, 40, 40, 45, 255);
|
||||||
|
disc(s, 9, 22, 3, 30, 30, 30, 255);
|
||||||
|
disc(s, 21, 22, 3, 30, 30, 30, 255);
|
||||||
|
break;
|
||||||
|
case UT_ARTILLERY:
|
||||||
|
fill_rect(s, 5, 11, 20, 9, dark.r, dark.g, dark.b, 255);
|
||||||
|
disc(s, 15, 15, 5, lite.r, lite.g, lite.b, 255);
|
||||||
|
line(s, 15, 15, 4, 4, 40, 40, 45, 255);
|
||||||
|
line(s, 15, 15, 26, 26, 40, 40, 45, 255);
|
||||||
|
break;
|
||||||
|
case UT_HARVESTER:
|
||||||
|
fill_rect(s, 4, 8, 22, 14, dark.r, dark.g, dark.b, 255);
|
||||||
|
fill_rect(s, 6, 10, 18, 10, 110, 90, 50, 255); /* scrap bed */
|
||||||
|
disc(s, 9, 22, 3, 30, 30, 30, 255);
|
||||||
|
disc(s, 21, 22, 3, 30, 30, 30, 255);
|
||||||
|
line(s, 6, 8, 6, 4, base.r, base.g, base.b, 255);
|
||||||
|
break;
|
||||||
|
case UT_JEEP:
|
||||||
|
fill_rect(s, 7, 11, 16, 9, dark.r, dark.g, dark.b, 255);
|
||||||
|
disc(s, 10, 21, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
disc(s, 20, 21, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
line(s, 15, 12, 15, 5, base.r, base.g, base.b, 255); /* roll bar */
|
||||||
|
line(s, 15, 15, 27, 15, 40, 40, 45, 255);
|
||||||
|
break;
|
||||||
|
case UT_MUTANT:
|
||||||
|
disc(s, 15, 16, 9, dark.r, dark.g, dark.b, 255);
|
||||||
|
disc(s, 15, 14, 6, base.r, base.g, base.b, 255);
|
||||||
|
disc(s, 15, 11, 3, lite.r, lite.g, lite.b, 255);
|
||||||
|
line(s, 15, 16, 23, 10, 30, 30, 30, 255); /* arm/club */
|
||||||
|
break;
|
||||||
|
case UT_BEAST:
|
||||||
|
fill_rect(s, 8, 11, 14, 9, dark.r, dark.g, dark.b, 255);
|
||||||
|
disc(s, 10, 20, 2, 30, 30, 30, 255);
|
||||||
|
disc(s, 14, 21, 2, 30, 30, 30, 255);
|
||||||
|
disc(s, 18, 20, 2, 30, 30, 30, 255);
|
||||||
|
disc(s, 22, 21, 2, 30, 30, 30, 255);
|
||||||
|
disc(s, 22, 13, 4, base.r, base.g, base.b, 255);
|
||||||
|
break;
|
||||||
|
case UT_RAVAGER:
|
||||||
|
fill_rect(s, 3, 9, 24, 13, dark.r, dark.g, dark.b, 255);
|
||||||
|
fill_rect(s, 6, 12, 18, 7, base.r, base.g, base.b, 255);
|
||||||
|
disc(s, 15, 15, 7, lite.r, lite.g, lite.b, 255);
|
||||||
|
line(s, 15, 15, 29, 15, 40, 40, 45, 255); /* big barrel */
|
||||||
|
disc(s, 8, 22, 3, 30, 30, 30, 255);
|
||||||
|
disc(s, 22, 22, 3, 30, 30, 30, 255);
|
||||||
|
break;
|
||||||
|
case UT_FLAME:
|
||||||
|
fill_rect(s, 8, 12, 14, 12, dark.r, dark.g, dark.b, 255);
|
||||||
|
disc(s, 15, 11, 4, base.r, base.g, base.b, 255);
|
||||||
|
line(s, 15, 15, 27, 19, 220, 120, 40, 255); /* flame nozzle */
|
||||||
|
break;
|
||||||
|
case UT_MISSILE:
|
||||||
|
fill_rect(s, 7, 12, 16, 11, dark.r, dark.g, dark.b, 255);
|
||||||
|
disc(s, 10, 22, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
disc(s, 20, 22, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
line(s, 15, 15, 28, 8, 200, 60, 60, 255); /* launcher */
|
||||||
|
break;
|
||||||
|
case UT_MEDIC:
|
||||||
|
fill_rect(s, 8, 11, 14, 12, 235, 235, 235, 255); /* white ambulance body */
|
||||||
|
disc(s, 11, 23, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
disc(s, 19, 23, 3, metal.r, metal.g, metal.b, 255);
|
||||||
|
disc(s, 15, 10, 4, 220, 60, 60, 255); /* red cross */
|
||||||
|
fill_rect(s, 14, 7, 2, 6, 255, 255, 255, 255);
|
||||||
|
fill_rect(s, 12, 9, 6, 2, 255, 255, 255, 255);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
disc(s, 15, 15, 9, base.r, base.g, base.b, 255);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SDL_Texture *tex = SDL_CreateTextureFromSurface(r, s);
|
||||||
|
SDL_FreeSurface(s);
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- building sprites ---------------- */
|
||||||
|
SDL_Texture *sprites_make_bld(SDL_Renderer *r, BuildingType t, Faction f) {
|
||||||
|
int w = bld_def(t)->w, h = bld_def(t)->h;
|
||||||
|
int W = w * TILE, H = h * TILE;
|
||||||
|
SDL_Surface *s = new_surf(W, H);
|
||||||
|
SDL_Color base = faction_color(f);
|
||||||
|
SDL_Color dark = darken(base, 0.5f);
|
||||||
|
SDL_Color lite = lighten(base, 0.4f);
|
||||||
|
SDL_Color wall = { 90, 84, 78, 255 };
|
||||||
|
SDL_Color wall2 = { 64, 60, 56, 255 };
|
||||||
|
|
||||||
|
fill_rect(s, 0, 0, W, H, wall2.r, wall2.g, wall2.b, 255);
|
||||||
|
fill_rect(s, 3, 3, W - 6, H - 6, wall.r, wall.g, wall.b, 255);
|
||||||
|
ring(s, W/2, H/2, (W<H?W:H)/2 - 3, 2, dark.r, dark.g, dark.b, 255);
|
||||||
|
|
||||||
|
switch (t) {
|
||||||
|
case BT_BASE:
|
||||||
|
disc(s, W/2, H/2, W/4, base.r, base.g, base.b, 255);
|
||||||
|
disc(s, W/2, H/2, W/6, lite.r, lite.g, lite.b, 255);
|
||||||
|
line(s, W/2, H/2, W/2, 4, 200, 200, 60, 255);
|
||||||
|
break;
|
||||||
|
case BT_WARFACTORY:
|
||||||
|
fill_rect(s, 8, 8, W-16, H-16, dark.r, dark.g, dark.b, 255);
|
||||||
|
fill_rect(s, 12, 12, W-24, 10, base.r, base.g, base.b, 255);
|
||||||
|
fill_rect(s, 12, H-24, W-24, 10, base.r, base.g, base.b, 255);
|
||||||
|
break;
|
||||||
|
case BT_REFINERY:
|
||||||
|
disc(s, W/2, H/2, W/4, 120, 100, 60, 255);
|
||||||
|
ring(s, W/2, H/2, W/4 - 3, 3, 60, 50, 40, 255);
|
||||||
|
break;
|
||||||
|
case BT_POWER:
|
||||||
|
disc(s, W/2, H/2, W/3, 80, 120, 160, 255);
|
||||||
|
line(s, W/2, 4, W/2, H-4, 220, 240, 255, 255);
|
||||||
|
line(s, 4, H/2, W-4, H/2, 220, 240, 255, 255);
|
||||||
|
break;
|
||||||
|
case BT_TURRET:
|
||||||
|
disc(s, W/2, H/2, W/3, dark.r, dark.g, dark.b, 255);
|
||||||
|
disc(s, W/2, H/2, W/5, lite.r, lite.g, lite.b, 255);
|
||||||
|
line(s, W/2, H/2, W/2 + 12, H/2 - 12, 40, 40, 45, 255);
|
||||||
|
break;
|
||||||
|
case BT_WALL:
|
||||||
|
fill_rect(s, 2, 2, W-4, H-4, 70, 66, 60, 255);
|
||||||
|
line(s, 2, 2, W-2, H-2, 40, 38, 34, 255);
|
||||||
|
break;
|
||||||
|
case BT_REPAIR:
|
||||||
|
fill_rect(s, 8, 8, W-16, H-16, dark.r, dark.g, dark.b, 255);
|
||||||
|
ring(s, W/2, H/2, W/4, 3, 120, 220, 120, 255);
|
||||||
|
line(s, W/2 - 12, H/2, W/2 + 12, H/2, 120, 220, 120, 255);
|
||||||
|
line(s, W/2, H/2 - 12, W/2, H/2 + 12, 120, 220, 120, 255);
|
||||||
|
break;
|
||||||
|
case BT_RESEARCH:
|
||||||
|
fill_rect(s, 8, 8, W-16, H-16, dark.r, dark.g, dark.b, 255);
|
||||||
|
disc(s, W/2, H/2, W/5, 180, 150, 60, 255);
|
||||||
|
line(s, W/2, 6, W/2, H-6, 220, 220, 150, 255);
|
||||||
|
line(s, 6, H/2, W-6, H/2, 220, 220, 150, 255);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SDL_Texture *tex = SDL_CreateTextureFromSurface(r, s);
|
||||||
|
SDL_FreeSurface(s);
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- terrain tiles ---------------- */
|
||||||
|
SDL_Texture *sprites_make_terrain(SDL_Renderer *r, TerrainType t) {
|
||||||
|
SDL_Surface *s = new_surf(TILE, TILE);
|
||||||
|
Uint8 tr=0,tg=0,tb=0;
|
||||||
|
switch (t) {
|
||||||
|
case T_WASTELAND: tr=120; tg=104; tb=78; break;
|
||||||
|
case T_RUBBLE: tr=96; tg=86; tb=74; break;
|
||||||
|
case T_ROAD: tr=70; tg=68; tb=64; break;
|
||||||
|
case T_GRASS: tr=86; tg=120; tb=64; break;
|
||||||
|
case T_WATER: tr=44; tg=78; tb=120; break;
|
||||||
|
case T_ROCK: tr=110; tg=104; tb=98; break;
|
||||||
|
case T_SCRAP: tr=110; tg=96; tb=70; break;
|
||||||
|
default: tr=120; tg=104; tb=78; break;
|
||||||
|
}
|
||||||
|
fill_rect(s, 0, 0, TILE, TILE, tr, tg, tb, 255);
|
||||||
|
/* speckle for texture */
|
||||||
|
for (int i = 0; i < 40; i++) {
|
||||||
|
int x = (i*73) % TILE, y = (i*131) % TILE;
|
||||||
|
int v = ((i*53) % 24) - 12;
|
||||||
|
px(s, x, y,
|
||||||
|
(Uint8)kknd_clampi(tr+v,0,255),
|
||||||
|
(Uint8)kknd_clampi(tg+v,0,255),
|
||||||
|
(Uint8)kknd_clampi(tb+v,0,255), 255);
|
||||||
|
}
|
||||||
|
if (t == T_SCRAP) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
int x = (i*97) % TILE, y = (i*61) % TILE;
|
||||||
|
disc(s, x, y, 2, 180, 150, 60, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t == T_WATER) {
|
||||||
|
for (int i = 0; i < TILE; i += 4)
|
||||||
|
line(s, 0, i, TILE, i, 60, 100, 150, 255);
|
||||||
|
}
|
||||||
|
if (t == T_ROCK) {
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
int x = (i*89) % TILE, y = (i*149) % TILE;
|
||||||
|
disc(s, x, y, 3, 130, 124, 118, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDL_Texture *tex = SDL_CreateTextureFromSurface(r, s);
|
||||||
|
SDL_FreeSurface(s);
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- bullets ---------------- */
|
||||||
|
static SDL_Texture *make_bullet(SDL_Renderer *r, int kind) {
|
||||||
|
int S = 10;
|
||||||
|
SDL_Surface *s = new_surf(S, S);
|
||||||
|
switch (kind) {
|
||||||
|
case 0: disc(s, 5, 5, 3, 255, 230, 120, 255); break; /* MG tracer */
|
||||||
|
case 1: disc(s, 5, 5, 4, 255, 160, 60, 255); break; /* shell */
|
||||||
|
case 2: disc(s, 5, 5, 3, 120, 255, 160, 255); break; /* laser */
|
||||||
|
case 3: disc(s, 5, 5, 4, 255, 90, 90, 255); break; /* missile */
|
||||||
|
default: disc(s, 5, 5, 3, 255, 255, 255, 255);
|
||||||
|
}
|
||||||
|
SDL_Texture *tex = SDL_CreateTextureFromSurface(r, s);
|
||||||
|
SDL_FreeSurface(s);
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sprites_init(Sprites *sp, SDL_Renderer *r) {
|
||||||
|
memset(sp, 0, sizeof(Sprites));
|
||||||
|
for (int t = 0; t < T_COUNT; t++)
|
||||||
|
sp->terrain[t] = sprites_make_terrain(r, (TerrainType)t);
|
||||||
|
for (int t = 1; t < UT_COUNT; t++) {
|
||||||
|
sp->unit[t][FACTION_SURVIVOR] = sprites_make_unit(r, (UnitType)t, FACTION_SURVIVOR);
|
||||||
|
sp->unit[t][FACTION_EVOLVED] = sprites_make_unit(r, (UnitType)t, FACTION_EVOLVED);
|
||||||
|
sp->unit[t][FACTION_SERIES9] = sprites_make_unit(r, (UnitType)t, FACTION_SERIES9);
|
||||||
|
}
|
||||||
|
for (int t = 1; t < BT_COUNT; t++) {
|
||||||
|
sp->bld[t][FACTION_SURVIVOR] = sprites_make_bld(r, (BuildingType)t, FACTION_SURVIVOR);
|
||||||
|
sp->bld[t][FACTION_EVOLVED] = sprites_make_bld(r, (BuildingType)t, FACTION_EVOLVED);
|
||||||
|
sp->bld[t][FACTION_SERIES9] = sprites_make_bld(r, (BuildingType)t, FACTION_SERIES9);
|
||||||
|
}
|
||||||
|
for (int k = 0; k < 4; k++)
|
||||||
|
sp->bullet[k] = make_bullet(r, k);
|
||||||
|
|
||||||
|
sp->font_small = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 14);
|
||||||
|
if (!sp->font_small)
|
||||||
|
sp->font_small = TTF_OpenFont("/usr/share/fonts/gnu-free/FreeSans.ttf", 14);
|
||||||
|
sp->font_med = sp->font_small ? TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 20) : NULL;
|
||||||
|
sp->font_big = sp->font_small ? TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 34) : NULL;
|
||||||
|
if (!sp->font_small) {
|
||||||
|
sp->font_small = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", 14);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sprites_free(Sprites *sp) {
|
||||||
|
for (int t = 0; t < T_COUNT; t++)
|
||||||
|
if (sp->terrain[t]) SDL_DestroyTexture(sp->terrain[t]);
|
||||||
|
for (int t = 1; t < UT_COUNT; t++)
|
||||||
|
for (int f = 0; f < MAX_FACTIONS; f++)
|
||||||
|
if (sp->unit[t][f]) SDL_DestroyTexture(sp->unit[t][f]);
|
||||||
|
for (int t = 1; t < BT_COUNT; t++)
|
||||||
|
for (int f = 0; f < MAX_FACTIONS; f++)
|
||||||
|
if (sp->bld[t][f]) SDL_DestroyTexture(sp->bld[t][f]);
|
||||||
|
for (int k = 0; k < 4; k++)
|
||||||
|
if (sp->bullet[k]) SDL_DestroyTexture(sp->bullet[k]);
|
||||||
|
if (sp->font_small) TTF_CloseFont(sp->font_small);
|
||||||
|
if (sp->font_med) TTF_CloseFont(sp->font_med);
|
||||||
|
if (sp->font_big) TTF_CloseFont(sp->font_big);
|
||||||
|
}
|
||||||
334
engine-c/src/unit_card.c
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
#include "unit_card.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <immintrin.h>
|
||||||
|
|
||||||
|
// Create a new unit card
|
||||||
|
UnitCard* create_unit_card(const char* name, const char* faction,
|
||||||
|
uint32_t cost, uint32_t hp, uint32_t attack,
|
||||||
|
uint32_t defense, uint32_t speed, uint32_t range,
|
||||||
|
const char* description, const char* image,
|
||||||
|
uint8_t unit_type) {
|
||||||
|
UnitCard* unit = (UnitCard*)malloc(sizeof(UnitCard));
|
||||||
|
if (!unit) {
|
||||||
|
fprintf(stderr, "Memory allocation failed for unit card\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize all fields with defaults
|
||||||
|
memset(unit, 0, sizeof(UnitCard));
|
||||||
|
|
||||||
|
// Copy strings
|
||||||
|
strncpy(unit->name, name, sizeof(unit->name) - 1);
|
||||||
|
unit->name[sizeof(unit->name) - 1] = '\0';
|
||||||
|
|
||||||
|
strncpy(unit->faction, faction, sizeof(unit->faction) - 1);
|
||||||
|
unit->faction[sizeof(unit->faction) - 1] = '\0';
|
||||||
|
|
||||||
|
strncpy(unit->description, description, sizeof(unit->description) - 1);
|
||||||
|
unit->description[sizeof(unit->description) - 1] = '\0';
|
||||||
|
|
||||||
|
strncpy(unit->image_path, image, sizeof(unit->image_path) - 1);
|
||||||
|
unit->image_path[sizeof(unit->image_path) - 1] = '\0';
|
||||||
|
|
||||||
|
// Copy numeric values
|
||||||
|
unit->cost = cost;
|
||||||
|
unit->hp = hp;
|
||||||
|
unit->max_hp = hp;
|
||||||
|
unit->attack = attack;
|
||||||
|
unit->defense = defense;
|
||||||
|
unit->speed = speed;
|
||||||
|
unit->range = range;
|
||||||
|
unit->unit_type = unit_type;
|
||||||
|
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free a unit card
|
||||||
|
void free_unit_card(UnitCard* unit) {
|
||||||
|
if (unit) {
|
||||||
|
free(unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new unit registry
|
||||||
|
UnitRegistry* create_registry() {
|
||||||
|
UnitRegistry* registry = (UnitRegistry*)malloc(sizeof(UnitRegistry));
|
||||||
|
if (!registry) {
|
||||||
|
fprintf(stderr, "Memory allocation failed for registry\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
registry->units = NULL;
|
||||||
|
registry->unit_count = 0;
|
||||||
|
registry->capacity = 0;
|
||||||
|
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy a unit registry
|
||||||
|
void destroy_registry(UnitRegistry* registry) {
|
||||||
|
if (!registry) return;
|
||||||
|
|
||||||
|
if (registry->units) {
|
||||||
|
for (size_t i = 0; i < registry->unit_count; i++) {
|
||||||
|
free_unit_card(registry->units[i]);
|
||||||
|
}
|
||||||
|
free(registry->units);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a unit to the registry
|
||||||
|
int add_unit(UnitRegistry* registry, UnitCard* unit) {
|
||||||
|
if (!registry || !unit) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize if necessary
|
||||||
|
if (registry->unit_count >= registry->capacity) {
|
||||||
|
size_t new_capacity = registry->capacity ? registry->capacity * 2 : 10;
|
||||||
|
UnitCard** new_units = (UnitCard**)realloc(registry->units,
|
||||||
|
new_capacity * sizeof(UnitCard*));
|
||||||
|
if (!new_units) {
|
||||||
|
fprintf(stderr, "Memory reallocation failed\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
registry->units = new_units;
|
||||||
|
registry->capacity = new_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the unit
|
||||||
|
registry->units[registry->unit_count++] = unit;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get unit by name
|
||||||
|
UnitCard* get_unit_by_name(UnitRegistry* registry, const char* name) {
|
||||||
|
if (!registry || !name) return NULL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < registry->unit_count; i++) {
|
||||||
|
if (strcmp(registry->units[i]->name, name) == 0) {
|
||||||
|
return registry->units[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get units by faction
|
||||||
|
UnitCard** get_units_by_faction(UnitRegistry* registry, const char* faction,
|
||||||
|
size_t* out_count) {
|
||||||
|
if (!registry || !faction || !out_count) return NULL;
|
||||||
|
|
||||||
|
// Count units of this faction
|
||||||
|
size_t count = 0;
|
||||||
|
for (size_t i = 0; i < registry->unit_count; i++) {
|
||||||
|
if (strcmp(registry->units[i]->faction, faction) == 0) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
*out_count = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate and fill array
|
||||||
|
UnitCard** result = (UnitCard**)calloc(count, sizeof(UnitCard*));
|
||||||
|
if (!result) {
|
||||||
|
*out_count = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t idx = 0;
|
||||||
|
for (size_t i = 0; i < registry->unit_count; i++) {
|
||||||
|
if (strcmp(registry->units[i]->faction, faction) == 0) {
|
||||||
|
result[idx++] = registry->units[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_count = count;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get units by type
|
||||||
|
UnitCard** get_units_by_type(UnitRegistry* registry, uint8_t unit_type,
|
||||||
|
size_t* out_count) {
|
||||||
|
if (!registry || !out_count) return NULL;
|
||||||
|
|
||||||
|
// Count units of this type
|
||||||
|
size_t count = 0;
|
||||||
|
for (size_t i = 0; i < registry->unit_count; i++) {
|
||||||
|
if (registry->units[i]->unit_type == unit_type) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
*out_count = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate and fill array
|
||||||
|
UnitCard** result = (UnitCard**)calloc(count, sizeof(UnitCard*));
|
||||||
|
if (!result) {
|
||||||
|
*out_count = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t idx = 0;
|
||||||
|
for (size_t i = 0; i < registry->unit_count; i++) {
|
||||||
|
if (registry->units[i]->unit_type == unit_type) {
|
||||||
|
result[idx++] = registry->units[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_count = count;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIMD optimized damage spreading
|
||||||
|
void spread_damage_SIMD(const UnitCard* units[], size_t unit_count,
|
||||||
|
uint32_t damage, uint32_t* remaining_hp) {
|
||||||
|
if (!units || unit_count == 0 || !remaining_hp) return;
|
||||||
|
|
||||||
|
// SIMD-accelerated damage calculation
|
||||||
|
// Process units in SIMD chunks where possible
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
// Process 4 units at a time with AVX2 if available
|
||||||
|
for (; i + 4 <= unit_count; i += 4) {
|
||||||
|
uint32_t hp_values[4];
|
||||||
|
uint32_t def_values[4];
|
||||||
|
|
||||||
|
hp_values[0] = units[i]->hp;
|
||||||
|
hp_values[1] = units[i+1]->hp;
|
||||||
|
hp_values[2] = units[i+2]->hp;
|
||||||
|
hp_values[3] = units[i+3]->hp;
|
||||||
|
|
||||||
|
def_values[0] = units[i]->defense;
|
||||||
|
def_values[1] = units[i+1]->defense;
|
||||||
|
def_values[2] = units[i+2]->defense;
|
||||||
|
def_values[3] = units[i+3]->defense;
|
||||||
|
|
||||||
|
for (int j = 0; j < 4; j++) {
|
||||||
|
uint32_t eff_damage = damage > def_values[j] ? damage - def_values[j] : 0;
|
||||||
|
remaining_hp[i+j] = hp_values[j] > eff_damage ? hp_values[j] - eff_damage : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process remaining units
|
||||||
|
for (; i < unit_count; i++) {
|
||||||
|
uint32_t eff_damage = damage > units[i]->defense ? damage - units[i]->defense : 0;
|
||||||
|
remaining_hp[i] = units[i]->hp > eff_damage ? units[i]->hp - eff_damage : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIMD optimized healing
|
||||||
|
void apply_healing_SIMD(UnitCard** units, size_t unit_count,
|
||||||
|
uint32_t heal_amount) {
|
||||||
|
if (!units || unit_count == 0) return;
|
||||||
|
|
||||||
|
// Process units with SIMD-like approach
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
for (; i + 4 <= unit_count; i += 4) {
|
||||||
|
uint32_t new_hp_0 = units[i]->hp + heal_amount;
|
||||||
|
uint32_t new_hp_1 = units[i+1]->hp + heal_amount;
|
||||||
|
uint32_t new_hp_2 = units[i+2]->hp + heal_amount;
|
||||||
|
uint32_t new_hp_3 = units[i+3]->hp + heal_amount;
|
||||||
|
|
||||||
|
if (new_hp_0 > units[i]->max_hp) new_hp_0 = units[i]->max_hp;
|
||||||
|
if (new_hp_1 > units[i+1]->max_hp) new_hp_1 = units[i+1]->max_hp;
|
||||||
|
if (new_hp_2 > units[i+2]->max_hp) new_hp_2 = units[i+2]->max_hp;
|
||||||
|
if (new_hp_3 > units[i+3]->max_hp) new_hp_3 = units[i+3]->max_hp;
|
||||||
|
|
||||||
|
units[i]->hp = new_hp_0;
|
||||||
|
units[i+1]->hp = new_hp_1;
|
||||||
|
units[i+2]->hp = new_hp_2;
|
||||||
|
units[i+3]->hp = new_hp_3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process remaining units
|
||||||
|
for (; i < unit_count; i++) {
|
||||||
|
uint32_t new_hp = units[i]->hp + heal_amount;
|
||||||
|
if (new_hp > units[i]->max_hp) new_hp = units[i]->max_hp;
|
||||||
|
units[i]->hp = new_hp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save units to JSON (placeholder implementation)
|
||||||
|
int save_units_to_json(UnitRegistry* registry, const char* filepath) {
|
||||||
|
FILE* file = fopen(filepath, "w");
|
||||||
|
if (!file) {
|
||||||
|
fprintf(stderr, "Failed to open file: %s\n", filepath);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(file, "[\n");
|
||||||
|
for (size_t i = 0; i < registry->unit_count; i++) {
|
||||||
|
UnitCard* unit = registry->units[i];
|
||||||
|
fprintf(file, " {\n");
|
||||||
|
fprintf(file, " \"name\": \"%s\",\n", unit->name);
|
||||||
|
fprintf(file, " \"faction\": \"%s\",\n", unit->faction);
|
||||||
|
fprintf(file, " \"hp\": %u,\n", unit->hp);
|
||||||
|
fprintf(file, " \"attack\": %u,\n", unit->attack);
|
||||||
|
fprintf(file, " \"defense\": %u,\n", unit->defense);
|
||||||
|
fprintf(file, " \"speed\": %u,\n", unit->speed);
|
||||||
|
fprintf(file, " \"range\": %u,\n", unit->range);
|
||||||
|
fprintf(file, " \"cost\": %u,\n", unit->cost);
|
||||||
|
fprintf(file, " \"description\": \"%s\"\n", unit->description);
|
||||||
|
if (i < registry->unit_count - 1) {
|
||||||
|
fprintf(file, " },\n");
|
||||||
|
} else {
|
||||||
|
fprintf(file, " }\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(file, "\n]\n");
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load units from JSON (placeholder implementation)
|
||||||
|
int load_units_from_json(UnitRegistry* registry, const char* filepath) {
|
||||||
|
fprintf(stderr, "JSON loading not implemented yet\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert unit card to JSON string
|
||||||
|
void unit_card_to_json(UnitCard* unit, char* buffer, size_t buffer_size) {
|
||||||
|
if (!unit || !buffer) return;
|
||||||
|
|
||||||
|
snprintf(buffer, buffer_size,
|
||||||
|
"{\"name\":\"%s\",\"faction\":\"%s\",\"hp\":%u,\"attack\":%u,\"defense\":%u,\"speed\":%u,\"range\":%u,\"cost\":%u}",
|
||||||
|
unit->name, unit->faction, unit->hp, unit->attack,
|
||||||
|
unit->defense, unit->speed, unit->range, unit->cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print performance statistics
|
||||||
|
void print_performance_stats(UnitRegistry* registry) {
|
||||||
|
if (!registry) {
|
||||||
|
printf("No registry available\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t total_hp = 0;
|
||||||
|
uint32_t total_cost = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < registry->unit_count; i++) {
|
||||||
|
total_hp += registry->units[i]->hp;
|
||||||
|
total_cost += registry->units[i]->cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("=== Unit Registry Statistics ===\n");
|
||||||
|
printf("Total units: %zu\n", registry->unit_count);
|
||||||
|
printf("Total HP: %u\n", total_hp);
|
||||||
|
printf("Total cost: %u\n", total_cost);
|
||||||
|
printf("Average HP: %.2f\n", (double)total_hp / registry->unit_count);
|
||||||
|
printf("Average cost: %.2f\n", (double)total_cost / registry->unit_count);
|
||||||
|
printf("==============================\n");
|
||||||
|
}
|
||||||
585
engine-c/src/units.c
Normal file
|
|
@ -0,0 +1,585 @@
|
||||||
|
#include "kknd.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* units.c - Unit lifecycle, movement, combat and the harvester economy.
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
static int unit_alloc_id(Game *g) { return g->unit_next_id++; }
|
||||||
|
|
||||||
|
Unit *unit_by_id(Game *g, int id) {
|
||||||
|
for (int i = 0; i < g->unit_count; i++)
|
||||||
|
if (!g->units[i].dead && g->units[i].id == id)
|
||||||
|
return &g->units[i];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int unit_spawn(Game *g, UnitType t, Faction f, float x, float y) {
|
||||||
|
if (g->unit_count >= MAX_UNITS) return -1;
|
||||||
|
const UnitDef *d = unit_def(t, f);
|
||||||
|
Unit *u = &g->units[g->unit_count++];
|
||||||
|
memset(u, 0, sizeof(Unit));
|
||||||
|
u->id = unit_alloc_id(g);
|
||||||
|
u->type = t;
|
||||||
|
u->faction = f;
|
||||||
|
u->x = x; u->y = y;
|
||||||
|
/* apply researched upgrades */
|
||||||
|
float dmg_m = 1.0f + 0.15f * (float)g->upg[f].lvl[UP_DAMAGE];
|
||||||
|
float arm_m = 1.0f + 0.15f * (float)g->upg[f].lvl[UP_ARMOR];
|
||||||
|
float spd_m = 1.0f + 0.10f * (float)g->upg[f].lvl[UP_SPEED];
|
||||||
|
float rel_m = 1.0f + 0.12f * (float)g->upg[f].lvl[UP_RELOAD];
|
||||||
|
u->hp = d->hp * arm_m; u->max_hp = d->hp * arm_m;
|
||||||
|
u->attack = d->attack * dmg_m; u->defense = d->defense;
|
||||||
|
u->speed = d->speed * spd_m; u->range = d->range; u->sight = d->sight;
|
||||||
|
u->reload_mul = rel_m;
|
||||||
|
u->heading = 0;
|
||||||
|
u->state = STATE_IDLE;
|
||||||
|
u->order = ORDER_NONE;
|
||||||
|
u->target_unit = -1; u->target_bld = -1;
|
||||||
|
u->atk_cd = 0;
|
||||||
|
u->cargo = 0;
|
||||||
|
u->selected = 0;
|
||||||
|
u->dead = 0;
|
||||||
|
u->anim = 0;
|
||||||
|
u->repath_timer = 0;
|
||||||
|
u->path.len = 0; u->path_idx = 0;
|
||||||
|
return g->unit_count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_remove(Game *g, int idx) {
|
||||||
|
if (idx < 0 || idx >= g->unit_count) return;
|
||||||
|
if (g->units[idx].selected) {
|
||||||
|
for (int i = 0; i < g->sel_count; i++)
|
||||||
|
if (g->selection[i] == g->units[idx].id) {
|
||||||
|
g->selection[i] = g->selection[g->sel_count-1];
|
||||||
|
g->sel_count--; i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g->units[idx] = g->units[g->unit_count - 1];
|
||||||
|
g->unit_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_apply_damage(Game *g, Unit *u, float dmg) {
|
||||||
|
float eff = dmg - u->defense * 0.25f;
|
||||||
|
if (eff < 1) eff = 1;
|
||||||
|
u->hp -= eff;
|
||||||
|
particle_burst(g, u->x, u->y, 3, 2, 255, 200, 80);
|
||||||
|
if (u->hp <= 0 && !u->dead) {
|
||||||
|
u->dead = 1;
|
||||||
|
u->state = STATE_DEAD;
|
||||||
|
particle_burst(g, u->x, u->y, 14, 1, 255, 140, 40);
|
||||||
|
particle_burst(g, u->x, u->y, 8, 3, 90, 80, 70);
|
||||||
|
audio_sfx(2);
|
||||||
|
if (g->cam.shake < 7) g->cam.shake = 7;
|
||||||
|
if (u->faction == g->human)
|
||||||
|
game_log(g, "Unit lost: %s", unit_def(u->type, u->faction)->name);
|
||||||
|
/* leave a wreck decal on the battlefield */
|
||||||
|
if (g->wreck_count < MAX_WRECKS) {
|
||||||
|
Wreck *w = &g->wrecks[g->wreck_next];
|
||||||
|
w->x = u->x; w->y = u->y; w->type = u->type;
|
||||||
|
w->faction = u->faction; w->rot = u->heading;
|
||||||
|
g->wreck_next = (g->wreck_next + 1) % MAX_WRECKS;
|
||||||
|
if (g->wreck_count < MAX_WRECKS) g->wreck_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unit_repath(Game *g, Unit *u, float gx, float gy) {
|
||||||
|
int sx, sy, gx_t, gy_t;
|
||||||
|
map_world_to_tile(u->x, u->y, &sx, &sy);
|
||||||
|
map_world_to_tile(gx, gy, &gx_t, &gy_t);
|
||||||
|
Path p;
|
||||||
|
if (path_find(&g->map, sx, sy, gx_t, gy_t, &p)) {
|
||||||
|
path_smooth(&g->map, &p);
|
||||||
|
u->path = p;
|
||||||
|
u->path_idx = (p.len > 1) ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
u->path.len = 0;
|
||||||
|
}
|
||||||
|
u->repath_timer = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_issue_move(Game *g, int idx, float x, float y, int attackmove) {
|
||||||
|
Unit *u = &g->units[idx];
|
||||||
|
u->order = attackmove ? ORDER_ATTACKMOVE : ORDER_MOVE;
|
||||||
|
u->dest_x = x; u->dest_y = y;
|
||||||
|
u->target_unit = -1; u->target_bld = -1;
|
||||||
|
u->state = STATE_MOVING;
|
||||||
|
unit_repath(g, u, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spread a group of units into a grid formation centred on (cx,cy) so they
|
||||||
|
* don't all path to the same tile and pile up. */
|
||||||
|
void unit_issue_move_group(Game *g, const int *idxs, int n, float cx, float cy, int attackmove) {
|
||||||
|
if (n <= 0) return;
|
||||||
|
int cols = (int)(sqrtf((float)n) + 0.5f);
|
||||||
|
if (cols < 1) cols = 1;
|
||||||
|
float sp = 28.0f;
|
||||||
|
int halfc = (cols - 1) / 2;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
int c = i % cols, r = i / cols;
|
||||||
|
float ox = (c - halfc) * sp;
|
||||||
|
float oy = (r - halfc) * sp;
|
||||||
|
Unit *u = &g->units[idxs[i]];
|
||||||
|
if (u->type == UT_HARVESTER) {
|
||||||
|
unit_issue_move(g, idxs[i], cx, cy, attackmove);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
u->order = attackmove ? ORDER_ATTACKMOVE : ORDER_MOVE;
|
||||||
|
u->dest_x = cx + ox; u->dest_y = cy + oy;
|
||||||
|
u->target_unit = -1; u->target_bld = -1;
|
||||||
|
u->state = STATE_MOVING;
|
||||||
|
unit_repath(g, u, u->dest_x, u->dest_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_issue_attack_unit(Game *g, int idx, int target) {
|
||||||
|
Unit *u = &g->units[idx];
|
||||||
|
u->order = ORDER_ATTACK;
|
||||||
|
u->target_unit = target; u->target_bld = -1;
|
||||||
|
u->state = STATE_ATTACKING;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_issue_attack_bld(Game *g, int idx, int target) {
|
||||||
|
Unit *u = &g->units[idx];
|
||||||
|
u->order = ORDER_ATTACK;
|
||||||
|
u->target_bld = target; u->target_unit = -1;
|
||||||
|
u->state = STATE_ATTACKING;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_issue_gather(Game *g, int idx, float x, float y) {
|
||||||
|
Unit *u = &g->units[idx];
|
||||||
|
if (u->type != UT_HARVESTER) { unit_issue_move(g, idx, x, y, 0); return; }
|
||||||
|
u->order = ORDER_GATHER;
|
||||||
|
u->dest_x = x; u->dest_y = y;
|
||||||
|
u->state = STATE_MOVING;
|
||||||
|
unit_repath(g, u, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_issue_gather_nearest(Game *g, int idx) {
|
||||||
|
Unit *u = &g->units[idx];
|
||||||
|
if (u->type != UT_HARVESTER) return;
|
||||||
|
float sx, sy;
|
||||||
|
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||||
|
u->order = ORDER_GATHER;
|
||||||
|
u->dest_x = sx; u->dest_y = sy;
|
||||||
|
u->state = STATE_MOVING;
|
||||||
|
unit_repath(g, u, sx, sy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_issue_return(Game *g, int idx) {
|
||||||
|
Unit *u = &g->units[idx];
|
||||||
|
if (u->type != UT_HARVESTER) return;
|
||||||
|
int rid = -1; float rbest = 1e18f;
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead || b->faction != u->faction || b->type != BT_REFINERY) continue;
|
||||||
|
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||||
|
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||||
|
float dd = kknd_dist2(u->x, u->y, bx, by);
|
||||||
|
if (dd < rbest) { rbest = dd; rid = b->id; }
|
||||||
|
}
|
||||||
|
if (rid < 0) return;
|
||||||
|
Building *b = bld_by_id(g, rid);
|
||||||
|
u->dest_x = (float)(b->tx*TILE + b->w*TILE/2);
|
||||||
|
u->dest_y = (float)(b->ty*TILE + b->h*TILE/2);
|
||||||
|
u->order = ORDER_RETURN;
|
||||||
|
u->state = STATE_MOVING;
|
||||||
|
unit_repath(g, u, u->dest_x, u->dest_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_issue_stop(Game *g, int idx) {
|
||||||
|
Unit *u = &g->units[idx];
|
||||||
|
u->order = ORDER_STOP;
|
||||||
|
u->target_unit = -1; u->target_bld = -1;
|
||||||
|
u->state = STATE_IDLE;
|
||||||
|
u->hold = 0; u->attack_move = 0;
|
||||||
|
u->path.len = 0; u->path_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_issue_hold(Game *g, int idx) {
|
||||||
|
Unit *u = &g->units[idx];
|
||||||
|
u->order = ORDER_STOP;
|
||||||
|
u->target_unit = -1; u->target_bld = -1;
|
||||||
|
u->state = STATE_IDLE;
|
||||||
|
u->hold = 1;
|
||||||
|
u->path.len = 0; u->path_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_ability_use(Game *g, int idx) {
|
||||||
|
Unit *u = &g->units[idx];
|
||||||
|
if (u->ability == AB_SIEGE) {
|
||||||
|
u->ability_on = !u->ability_on;
|
||||||
|
audio_sfx(4);
|
||||||
|
} else if (u->ability == AB_HEAL) {
|
||||||
|
if (u->ability_cd <= 0) {
|
||||||
|
u->ability_cd = 8.0f;
|
||||||
|
float heal_r = 90.0f;
|
||||||
|
for (int k = 0; k < g->unit_count; k++) {
|
||||||
|
Unit *a = &g->units[k];
|
||||||
|
if (a->dead || a->faction != u->faction) continue;
|
||||||
|
if (kknd_dist2(u->x, u->y, a->x, a->y) < heal_r*heal_r)
|
||||||
|
a->hp = (a->hp + 120 > a->max_hp) ? a->max_hp : a->hp + 120;
|
||||||
|
}
|
||||||
|
particle_burst(g, u->x, u->y, 16, 1, 90, 255, 120);
|
||||||
|
audio_sfx(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_select_single(Game *g, int idx, int add) {
|
||||||
|
if (!add) unit_clear_selection(g);
|
||||||
|
g->units[idx].selected = 1;
|
||||||
|
g->selection[g->sel_count++] = g->units[idx].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_clear_selection(Game *g) {
|
||||||
|
for (int i = 0; i < g->unit_count; i++) g->units[i].selected = 0;
|
||||||
|
g->sel_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_select_all(Game *g, Faction f) {
|
||||||
|
unit_clear_selection(g);
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead || u->faction != f) continue;
|
||||||
|
u->selected = 1;
|
||||||
|
g->selection[g->sel_count++] = u->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_select_in_rect(Game *g, int x0, int y0, int x1, int y1, int add) {
|
||||||
|
int minx = x0<x1?x0:x1, maxx = x0>x1?x0:x1;
|
||||||
|
int miny = y0<y1?y0:y1, maxy = y0>y1?y0:y1;
|
||||||
|
if (!add) unit_clear_selection(g);
|
||||||
|
int added = 0;
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead) continue;
|
||||||
|
int sx = (int)(u->x - g->cam.x);
|
||||||
|
int sy = (int)(u->y - g->cam.y);
|
||||||
|
if (sx >= minx && sx <= maxx && sy >= miny && sy <= maxy) {
|
||||||
|
if (u->faction == g->human && !u->selected) {
|
||||||
|
u->selected = 1;
|
||||||
|
g->selection[g->sel_count++] = u->id;
|
||||||
|
added++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (added) audio_sfx(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unit_find_enemy_unit(Game *g, Unit *u, float maxd) {
|
||||||
|
int best = -1; float bestd = maxd * maxd;
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *e = &g->units[i];
|
||||||
|
if (e->dead || e->faction == u->faction) continue;
|
||||||
|
float d = kknd_dist2(u->x, u->y, e->x, e->y);
|
||||||
|
if (d < bestd) { bestd = d; best = e->id; }
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unit_find_enemy_bld(Game *g, Unit *u, float maxd) {
|
||||||
|
int best = -1; float bestd = maxd * maxd;
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead || b->faction == u->faction) continue;
|
||||||
|
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||||
|
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||||
|
float d = kknd_dist2(u->x, u->y, bx, by);
|
||||||
|
if (d < bestd) { bestd = d; best = b->id; }
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unit_combat_step(Game *g, Unit *u, float dt) {
|
||||||
|
if (u->atk_cd > 0) u->atk_cd -= dt;
|
||||||
|
if (u->attack <= 0) return;
|
||||||
|
|
||||||
|
/* validate current target */
|
||||||
|
Unit *tu = (u->target_unit >= 0) ? unit_by_id(g, u->target_unit) : NULL;
|
||||||
|
Building *tb = NULL;
|
||||||
|
if (!tu && u->target_bld >= 0) {
|
||||||
|
for (int i = 0; i < g->bld_count; i++)
|
||||||
|
if (g->buildings[i].id == u->target_bld && !g->buildings[i].dead)
|
||||||
|
{ tb = &g->buildings[i]; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
float tx, ty;
|
||||||
|
if (tu) { tx = tu->x; ty = tu->y; }
|
||||||
|
else if (tb) { tx = (float)(tb->tx*TILE + tb->w*TILE/2);
|
||||||
|
ty = (float)(tb->ty*TILE + tb->h*TILE/2); }
|
||||||
|
else { tx = u->dest_x; ty = u->dest_y; }
|
||||||
|
|
||||||
|
float d = kknd_dist(u->x, u->y, tx, ty);
|
||||||
|
float rng = u->range * (u->ability_on ? 1.6f : 1.0f);
|
||||||
|
|
||||||
|
if ((tu || tb) && d <= rng) {
|
||||||
|
u->heading = (float)atan2(ty - u->y, tx - u->x);
|
||||||
|
if (u->atk_cd <= 0) {
|
||||||
|
int kind = (u->type == UT_ARTILLERY || u->type == UT_MISSILE || u->type == UT_TANK)
|
||||||
|
? 1 : 0;
|
||||||
|
if (tu) bullet_spawn(g, u->x, u->y, tu->id, -1, u->attack, u->faction, kind);
|
||||||
|
else bullet_spawn(g, u->x, u->y, -1, tb->id, u->attack, u->faction, kind);
|
||||||
|
u->atk_cd = ((u->type == UT_ARTILLERY) ? 2.2f
|
||||||
|
: (u->type == UT_INFANTRY ? 0.8f : 1.0f)) / u->reload_mul;
|
||||||
|
u->anim = 1.0f;
|
||||||
|
audio_sfx(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* need to close distance: keep moving along path toward target */
|
||||||
|
if (u->hold) return; /* hold position: fire but never chase */
|
||||||
|
if ((tu || tb) && u->order == ORDER_ATTACK) {
|
||||||
|
if (u->repath_timer <= 0 ||
|
||||||
|
kknd_dist2(u->dest_x, u->dest_y, tx, ty) > 64*64) {
|
||||||
|
u->dest_x = tx; u->dest_y = ty;
|
||||||
|
unit_repath(g, u, tx, ty);
|
||||||
|
}
|
||||||
|
u->state = STATE_MOVING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int harvester_find_refinery(Game *g, Unit *u) {
|
||||||
|
int rid = -1; float rbest = 1e18f;
|
||||||
|
for (int i = 0; i < g->bld_count; i++) {
|
||||||
|
Building *b = &g->buildings[i];
|
||||||
|
if (b->dead || b->faction != u->faction || b->type != BT_REFINERY) continue;
|
||||||
|
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||||
|
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||||
|
float dd = kknd_dist2(u->x, u->y, bx, by);
|
||||||
|
if (dd < rbest) { rbest = dd; rid = b->id; }
|
||||||
|
}
|
||||||
|
return rid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Harvester behaviour: travels in STATE_MOVING, loads only in
|
||||||
|
* STATE_GATHERING (whilst sitting on a scrap tile). */
|
||||||
|
static void unit_harvester_step(Game *g, Unit *u, float dt) {
|
||||||
|
(void)dt;
|
||||||
|
const UnitDef *d = unit_def(u->type, u->faction);
|
||||||
|
int tx, ty; map_world_to_tile(u->x, u->y, &tx, &ty);
|
||||||
|
Tile *t = map_tile(&g->map, tx, ty);
|
||||||
|
|
||||||
|
if (u->order == ORDER_NONE) u->order = ORDER_GATHER;
|
||||||
|
|
||||||
|
if (u->state == STATE_IDLE) {
|
||||||
|
if (t && t->terrain == T_SCRAP && t->scrap > 0 && u->cargo < d->cargo) {
|
||||||
|
u->state = STATE_GATHERING;
|
||||||
|
} else if (u->cargo >= d->cargo) {
|
||||||
|
int rid = harvester_find_refinery(g, u);
|
||||||
|
Building *b = (rid >= 0) ? bld_by_id(g, rid) : NULL;
|
||||||
|
if (b) {
|
||||||
|
u->dest_x = (float)(b->tx*TILE + b->w*TILE/2);
|
||||||
|
u->dest_y = (float)(b->ty*TILE + b->h*TILE/2);
|
||||||
|
u->state = STATE_MOVING; unit_repath(g, u, u->dest_x, u->dest_y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
float sx, sy;
|
||||||
|
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||||
|
u->order = ORDER_GATHER;
|
||||||
|
u->dest_x = sx; u->dest_y = sy;
|
||||||
|
u->state = STATE_MOVING; unit_repath(g, u, sx, sy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u->state == STATE_GATHERING) {
|
||||||
|
if (t && t->terrain == T_SCRAP && t->scrap > 0 && u->cargo < d->cargo) {
|
||||||
|
int take = ((int)t->scrap > 4) ? 4 : (int)t->scrap;
|
||||||
|
int add = (d->cargo - u->cargo > take) ? take : (int)(d->cargo - u->cargo);
|
||||||
|
u->cargo += add;
|
||||||
|
map_deplete_scrap(&g->map, tx, ty, add);
|
||||||
|
u->anim = 1.0f;
|
||||||
|
}
|
||||||
|
if (u->cargo >= d->cargo) {
|
||||||
|
int rid = harvester_find_refinery(g, u);
|
||||||
|
Building *b = (rid >= 0) ? bld_by_id(g, rid) : NULL;
|
||||||
|
if (b) {
|
||||||
|
u->dest_x = (float)(b->tx*TILE + b->w*TILE/2);
|
||||||
|
u->dest_y = (float)(b->ty*TILE + b->h*TILE/2);
|
||||||
|
u->state = STATE_MOVING; unit_repath(g, u, u->dest_x, u->dest_y);
|
||||||
|
} else u->state = STATE_IDLE;
|
||||||
|
} else if (!t || t->terrain != T_SCRAP || t->scrap == 0) {
|
||||||
|
float sx, sy;
|
||||||
|
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||||
|
u->dest_x = sx; u->dest_y = sy; u->state = STATE_MOVING;
|
||||||
|
unit_repath(g, u, sx, sy);
|
||||||
|
} else u->state = STATE_IDLE;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* travelling: handle arrival at refinery (deposit) or scrap (load) */
|
||||||
|
if (u->state == STATE_MOVING &&
|
||||||
|
(u->order == ORDER_GATHER || u->order == ORDER_RETURN)) {
|
||||||
|
int rid = harvester_find_refinery(g, u);
|
||||||
|
if (rid >= 0) {
|
||||||
|
Building *b = bld_by_id(g, rid);
|
||||||
|
float bx = (float)(b->tx*TILE + b->w*TILE/2);
|
||||||
|
float by = (float)(b->ty*TILE + b->h*TILE/2);
|
||||||
|
if (kknd_dist(u->x, u->y, bx, by) < TILE*1.4f) {
|
||||||
|
g->scrap[u->faction] += (int)u->cargo;
|
||||||
|
u->cargo = 0; audio_sfx(3);
|
||||||
|
float sx, sy;
|
||||||
|
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||||
|
u->dest_x = sx; u->dest_y = sy; u->state = STATE_MOVING;
|
||||||
|
unit_repath(g, u, sx, sy);
|
||||||
|
} else u->state = STATE_IDLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int arrived = (u->path.len == 0 || u->path_idx >= u->path.len);
|
||||||
|
if (arrived) {
|
||||||
|
if (u->cargo < d->cargo && t && t->terrain == T_SCRAP && t->scrap > 0) {
|
||||||
|
u->state = STATE_GATHERING;
|
||||||
|
} else {
|
||||||
|
float sx, sy;
|
||||||
|
if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) {
|
||||||
|
u->dest_x = sx; u->dest_y = sy; u->state = STATE_MOVING;
|
||||||
|
unit_repath(g, u, sx, sy);
|
||||||
|
} else u->state = STATE_IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unit_update(Game *g, float dt) {
|
||||||
|
/* separation + obstacle-avoidance pass (cheap O(n^2) capped) */
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead || u->speed <= 0) continue;
|
||||||
|
float fx = 0, fy = 0;
|
||||||
|
float rad = (u->type == UT_TANK || u->type == UT_BEAST) ? 15.0f : 12.0f;
|
||||||
|
for (int j = 0; j < g->unit_count; j++) {
|
||||||
|
if (i == j) continue;
|
||||||
|
Unit *o = &g->units[j];
|
||||||
|
if (o->dead) continue;
|
||||||
|
float dx = u->x - o->x, dy = u->y - o->y;
|
||||||
|
float d2 = dx*dx + dy*dy;
|
||||||
|
float rr = rad * 2.0f;
|
||||||
|
if (d2 > 0 && d2 < rr*rr) {
|
||||||
|
float d = (float)sqrtf(d2);
|
||||||
|
float push = (rr - d) / rr; /* 0..1, stronger when closer */
|
||||||
|
fx += dx/d * push; fy += dy/d * push;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* push out of building footprints */
|
||||||
|
for (int b = 0; b < g->bld_count; b++) {
|
||||||
|
Building *bd = &g->buildings[b];
|
||||||
|
if (bd->dead) continue;
|
||||||
|
float bx0 = bd->tx*TILE, by0 = bd->ty*TILE;
|
||||||
|
float bx1 = bx0 + bd->w*TILE, by1 = by0 + bd->h*TILE;
|
||||||
|
if (u->x > bx0-rad && u->x < bx1+rad && u->y > by0-rad && u->y < by1+rad) {
|
||||||
|
/* closest point on rect */
|
||||||
|
float cxp = u->x < bx0 ? bx0 : (u->x > bx1 ? bx1 : u->x);
|
||||||
|
float cyp = u->y < by0 ? by0 : (u->y > by1 ? by1 : u->y);
|
||||||
|
float dx = u->x - cxp, dy = u->y - cyp;
|
||||||
|
float d2 = dx*dx + dy*dy;
|
||||||
|
if (d2 < rad*rad) {
|
||||||
|
if (d2 < 0.01f) { dx = u->x - (bx0+bx1)/2; dy = u->y - (by0+by1)/2; d2 = dx*dx+dy*dy; }
|
||||||
|
float d = (float)sqrtf(d2);
|
||||||
|
fx += dx/d * 1.5f; fy += dy/d * 1.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u->x += fx * 26 * dt;
|
||||||
|
u->y += fy * 26 * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < g->unit_count; i++) {
|
||||||
|
Unit *u = &g->units[i];
|
||||||
|
if (u->dead) continue;
|
||||||
|
if (u->anim > 0) u->anim -= dt * 3;
|
||||||
|
if (u->repath_timer > 0) u->repath_timer -= dt;
|
||||||
|
if (u->ability_cd > 0) u->ability_cd -= dt;
|
||||||
|
|
||||||
|
/* siege mode: immobile while active */
|
||||||
|
if (u->ability == AB_SIEGE && u->ability_on && u->state == STATE_MOVING) {
|
||||||
|
u->state = STATE_IDLE; u->path.len = 0; u->path_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* medic / repair aura: heal nearby wounded allies */
|
||||||
|
if (u->ability == AB_HEAL) {
|
||||||
|
float heal_r = 70.0f;
|
||||||
|
float rate = (u->ability_cd > 0) ? 60.0f : 22.0f; /* burst pulses faster */
|
||||||
|
for (int k = 0; k < g->unit_count; k++) {
|
||||||
|
Unit *a = &g->units[k];
|
||||||
|
if (a->dead || a->faction != u->faction || a == u) continue;
|
||||||
|
if (a->hp >= a->max_hp) continue;
|
||||||
|
if (kknd_dist2(u->x, u->y, a->x, a->y) < heal_r*heal_r) {
|
||||||
|
a->hp = (a->hp + rate*dt > a->max_hp) ? a->max_hp : a->hp + rate*dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u->type == UT_HARVESTER) {
|
||||||
|
unit_harvester_step(g, u, dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u->attack > 0) {
|
||||||
|
if (u->order == ORDER_ATTACK) {
|
||||||
|
unit_combat_step(g, u, dt);
|
||||||
|
} else {
|
||||||
|
/* auto-acquire while idle / attack-moving */
|
||||||
|
if (u->state == STATE_IDLE || u->order == ORDER_ATTACKMOVE) {
|
||||||
|
float acq = u->hold ? u->range : u->sight;
|
||||||
|
int e = unit_find_enemy_unit(g, u, acq);
|
||||||
|
if (e < 0) {
|
||||||
|
int b = unit_find_enemy_bld(g, u, acq);
|
||||||
|
if (b >= 0) { u->target_bld = b; u->order = ORDER_ATTACK;
|
||||||
|
u->state = STATE_ATTACKING; }
|
||||||
|
} else {
|
||||||
|
u->target_unit = e; u->order = ORDER_ATTACK;
|
||||||
|
u->state = STATE_ATTACKING;
|
||||||
|
}
|
||||||
|
} else if (u->order == ORDER_ATTACKMOVE) {
|
||||||
|
if (u->atk_cd <= 0) {
|
||||||
|
int e = unit_find_enemy_unit(g, u, u->range + 8);
|
||||||
|
int b = unit_find_enemy_bld(g, u, u->range + 8);
|
||||||
|
if (e >= 0) { u->target_unit = e; unit_combat_step(g, u, dt); }
|
||||||
|
else if (b >= 0) { u->target_bld = b; unit_combat_step(g, u, dt); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* movement along path */
|
||||||
|
if (u->state == STATE_MOVING && u->path.len > 0) {
|
||||||
|
if (u->path_idx >= u->path.len) {
|
||||||
|
u->state = STATE_IDLE;
|
||||||
|
u->path.len = 0;
|
||||||
|
} else {
|
||||||
|
float wx, wy;
|
||||||
|
map_tile_to_world(u->path.nodes[u->path_idx].tx,
|
||||||
|
u->path.nodes[u->path_idx].ty, &wx, &wy);
|
||||||
|
float d = kknd_dist(u->x, u->y, wx, wy);
|
||||||
|
if (d < 4) {
|
||||||
|
u->path_idx++;
|
||||||
|
} else {
|
||||||
|
float sp = u->speed * dt;
|
||||||
|
u->heading = (float)atan2(wy - u->y, wx - u->x);
|
||||||
|
u->x += (wx - u->x) / d * sp;
|
||||||
|
u->y += (wy - u->y) / d * sp;
|
||||||
|
u->anim = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (u->state == STATE_MOVING) {
|
||||||
|
/* no path: move straight */
|
||||||
|
float d = kknd_dist(u->x, u->y, u->dest_x, u->dest_y);
|
||||||
|
if (d < 4) { u->state = STATE_IDLE; }
|
||||||
|
else {
|
||||||
|
float sp = u->speed * dt;
|
||||||
|
u->x += (u->dest_x - u->x)/d * sp;
|
||||||
|
u->y += (u->dest_y - u->y)/d * sp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cull dead */
|
||||||
|
for (int i = g->unit_count - 1; i >= 0; i--)
|
||||||
|
if (g->units[i].dead) unit_remove(g, i);
|
||||||
|
}
|
||||||
42
kitchen/COLLAB-SIGNUP.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# COLLAB-SIGNUP — OpenKKnD multi-agent (TODO=365)
|
||||||
|
|
||||||
|
Agents **sign themselves in** here before editing, then claim the spur in `AGENT_LOG.md`.
|
||||||
|
Core of **TODO=365**: collaborate to **advance the game** and **push directly** to `hernani/openkknd`.
|
||||||
|
|
||||||
|
## Rules (short)
|
||||||
|
|
||||||
|
1. **One free row / spur** — pick an open spur (A/B preferred). Do not steal an ACTIVE claim unless stale **>2h** or owner marked DONE/GIVE_UP.
|
||||||
|
2. **Signup → claim → work → smoke → commit(forced author) → push → release.**
|
||||||
|
3. Forced author: `local-model/<experte>-L<linie>-<modell>` (see Instruct / REF365). Auth ≠ Author. Shared push OK.
|
||||||
|
4. Empty kitchen/hygiene/push-theater ≠ progress. Priorität **A > B > C/D**.
|
||||||
|
5. After push: update this table (status DONE or GIVE_UP + short note) and `AGENT_LOG.md`.
|
||||||
|
|
||||||
|
## Spurs
|
||||||
|
|
||||||
|
| Spur | Focus | Typical paths |
|
||||||
|
|------|--------|----------------|
|
||||||
|
| **A** | engine-c / runtime | `engine-c/`, related smoke |
|
||||||
|
| **B** | sim + game docs | sim/, missions, game-facing docs |
|
||||||
|
| **C** | kitchen triad | `kitchen/` only with ACTIVITY+STATUS+outbox triad |
|
||||||
|
| **D** | hygiene | only if blocking A/B |
|
||||||
|
|
||||||
|
## Active / recent signups
|
||||||
|
|
||||||
|
| When (UTC) | Agent / author slug | Spur | Paths (short) | Status | Note |
|
||||||
|
|------------|---------------------|------|---------------|--------|------|
|
||||||
|
| — | — | — | — | FREE | next agent: add a row, then claim in AGENT_LOG |
|
||||||
|
|
||||||
|
<!-- Example row:
|
||||||
|
| 2026-09-17T12:00Z | local-model/engine-L2-jan-gemma4 | A | engine-c/… | ACTIVE | KEEP landed; unit X |
|
||||||
|
| 2026-09-17T13:10Z | local-model/engine-L2-jan-gemma4 | A | engine-c/… | DONE | pushed abc1234; smoke PASS |
|
||||||
|
-->
|
||||||
|
|
||||||
|
## How to sign up (copy)
|
||||||
|
|
||||||
|
1. Append a row: `ACTIVE`, your forced-author slug, spur, paths.
|
||||||
|
2. Add claim line in `AGENT_LOG.md` (same agent · spur · paths · started).
|
||||||
|
3. Do **one** relevant unit → `./scripts/smoke/run_all.sh` → docs → commit → **`git push origin master`**.
|
||||||
|
4. Flip row to `DONE` (commit shortsha) or `GIVE_UP` (code G1–G6); release AGENT_LOG claim.
|
||||||
|
|
||||||
|
Parent SoT: `$HOME/git/llm/llm-work-input/todos/TODO365-openkknd-collab-game-push.md`
|
||||||
|
Instruct: `$HOME/git/llm/llm-work-input/OSAURUS-OPENKKND-CONTINUE.md`
|
||||||
|
|
@ -8,7 +8,8 @@ Thema: Freude am Spieleentwickeln. Ablauf & Kanäle: [`PLAYBOOK.md`](PLAYBOOK.md
|
||||||
| `PERSONAS.md` | P1–P7 Handles + Modell-Linien |
|
| `PERSONAS.md` | P1–P7 Handles + Modell-Linien |
|
||||||
| `PLAYBOOK.md` | Kanäle, Play-Ablauf, Ton |
|
| `PLAYBOOK.md` | Kanäle, Play-Ablauf, Ton |
|
||||||
| `ACTIVITY.md` | Brodelnder Thread (lesen!) |
|
| `ACTIVITY.md` | Brodelnder Thread (lesen!) |
|
||||||
| `AGENT_LOG.md` | Maschinen-Timeline |
|
| `AGENT_LOG.md` | Maschinen-Timeline + spur claims |
|
||||||
|
| `COLLAB-SIGNUP.md` | TODO=365 Bearbeiter-Liste — sich als Agent einschreiben vor Edit |
|
||||||
| `STATUS.md` | RUNNING / BROILING / PLAYBOOK-EXPANDED / … |
|
| `STATUS.md` | RUNNING / BROILING / PLAYBOOK-EXPANDED / … |
|
||||||
| `outbox/` | Entwürfe → Publish |
|
| `outbox/` | Entwürfe → Publish |
|
||||||
| `raw/` | Rohe Worker-JSON |
|
| `raw/` | Rohe Worker-JSON |
|
||||||
|
|
|
||||||
170
kknd2-runs/20260713-093232/hermes-build.txt
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
openkknd: compiling...
|
||||||
|
In file included from src/rng.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/config.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/png.c:8:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/snapshot.c:6:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/snapshot.c: In Funktion »snapshot_capture«:
|
||||||
|
src/snapshot.c:62:5: warning: diese »if«-Klausel beschützt nicht … [-Wmisleading-indentation]
|
||||||
|
62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||||||
|
| ^~
|
||||||
|
src/snapshot.c:62:29: note: … diese Anweisung, aber letztere ist verwirrend eingerückt, als wäre sie abhängig von »if«
|
||||||
|
62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||||||
|
| ^~
|
||||||
|
In file included from src/map.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/map.c: In Funktion »map_init«:
|
||||||
|
src/map.c:66:13: warning: Variable »pos« wird nicht verwendet [-Wunused-variable]
|
||||||
|
66 | int pos = rng_range(rng, 8, m->w - 9);
|
||||||
|
| ^~~
|
||||||
|
In file included from src/sprites.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/units.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/buildings.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/projectiles.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/render.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/render.c: In Funktion »render_buildings«:
|
||||||
|
src/render.c:106:17: warning: Variable »pct« wird nicht verwendet [-Wunused-variable]
|
||||||
|
106 | int pct = (int)(b->build * 100);
|
||||||
|
| ^~~
|
||||||
|
In file included from src/input.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/ai.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/audio.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/save.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/engine.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/main.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/main.c: In Funktion »main«:
|
||||||
|
src/main.c:50:16: warning: Variable »diff« gesetzt, aber nicht verwendet [-Wunused-but-set-variable=]
|
||||||
|
50 | Difficulty diff = DIFF_NORMAL;
|
||||||
|
| ^~~~
|
||||||
|
src/main.c: Auf höchster Ebene:
|
||||||
|
src/main.c:32:13: warning: »save_snapshot« definiert, aber nicht verwendet [-Wunused-function]
|
||||||
|
32 | static void save_snapshot(Game *g, int n) {
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
openkknd: built -> ./openkknd
|
||||||
|
usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd]
|
||||||
2
kknd2-runs/20260713-093232/hermes.err
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
[hermes3:8b · 12616ms · ok]
|
||||||
2
kknd2-runs/20260713-093232/hermes.out
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<SCRATCHPAD>
|
||||||
|
{"arguments": {"path": "/home/vx/src/openkknd-hermes"}, "name": "read"}
|
||||||
39
kknd2-runs/20260713-093232/master.log
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
==== 2026-07-13T09:32:32+02:00 QWEN start ====
|
||||||
|
qwen exit=0
|
||||||
|
{
|
||||||
|
"name": "exec",
|
||||||
|
"arguments": {
|
||||||
|
"command": "cd /home/vx/src/openkknd-qwen && ./build.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
==== qwen tree ====
|
||||||
|
insgesamt 136
|
||||||
|
drwxr-xr-x 1 vx vx 144 13. Jul 09:33 .
|
||||||
|
drwxr-xr-x 1 vx vx 76 13. Jul 09:32 ..
|
||||||
|
drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets
|
||||||
|
drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks
|
||||||
|
-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh
|
||||||
|
drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery
|
||||||
|
drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include
|
||||||
|
-rw-r--r-- 1 vx vx 1103 13. Jul 09:32 MISSION.md
|
||||||
|
-rwxr-xr-x 1 vx vx 124680 13. Jul 09:33 openkknd
|
||||||
|
-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd
|
||||||
|
drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src
|
||||||
|
==== 2026-07-13T09:33:15+02:00 HERMES start ====
|
||||||
|
hermes exit=0
|
||||||
|
<SCRATCHPAD>
|
||||||
|
{"arguments": {"path": "/home/vx/src/openkknd-hermes"}, "name": "read"}
|
||||||
|
==== hermes tree ====
|
||||||
|
insgesamt 136
|
||||||
|
drwxr-xr-x 1 vx vx 144 13. Jul 09:33 .
|
||||||
|
drwxr-xr-x 1 vx vx 76 13. Jul 09:32 ..
|
||||||
|
drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets
|
||||||
|
drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks
|
||||||
|
-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh
|
||||||
|
drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery
|
||||||
|
drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include
|
||||||
|
-rw-r--r-- 1 vx vx 1103 13. Jul 09:32 MISSION.md
|
||||||
|
-rwxr-xr-x 1 vx vx 124680 13. Jul 09:33 openkknd
|
||||||
|
-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd
|
||||||
|
drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src
|
||||||
|
==== 2026-07-13T09:33:39+02:00 DONE ====
|
||||||
39
kknd2-runs/20260713-093232/nohup.out
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
==== 2026-07-13T09:32:32+02:00 QWEN start ====
|
||||||
|
qwen exit=0
|
||||||
|
{
|
||||||
|
"name": "exec",
|
||||||
|
"arguments": {
|
||||||
|
"command": "cd /home/vx/src/openkknd-qwen && ./build.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
==== qwen tree ====
|
||||||
|
insgesamt 136
|
||||||
|
drwxr-xr-x 1 vx vx 144 13. Jul 09:33 .
|
||||||
|
drwxr-xr-x 1 vx vx 76 13. Jul 09:32 ..
|
||||||
|
drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets
|
||||||
|
drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks
|
||||||
|
-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh
|
||||||
|
drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery
|
||||||
|
drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include
|
||||||
|
-rw-r--r-- 1 vx vx 1103 13. Jul 09:32 MISSION.md
|
||||||
|
-rwxr-xr-x 1 vx vx 124680 13. Jul 09:33 openkknd
|
||||||
|
-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd
|
||||||
|
drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src
|
||||||
|
==== 2026-07-13T09:33:15+02:00 HERMES start ====
|
||||||
|
hermes exit=0
|
||||||
|
<SCRATCHPAD>
|
||||||
|
{"arguments": {"path": "/home/vx/src/openkknd-hermes"}, "name": "read"}
|
||||||
|
==== hermes tree ====
|
||||||
|
insgesamt 136
|
||||||
|
drwxr-xr-x 1 vx vx 144 13. Jul 09:33 .
|
||||||
|
drwxr-xr-x 1 vx vx 76 13. Jul 09:32 ..
|
||||||
|
drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets
|
||||||
|
drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks
|
||||||
|
-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh
|
||||||
|
drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery
|
||||||
|
drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include
|
||||||
|
-rw-r--r-- 1 vx vx 1103 13. Jul 09:32 MISSION.md
|
||||||
|
-rwxr-xr-x 1 vx vx 124680 13. Jul 09:33 openkknd
|
||||||
|
-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd
|
||||||
|
drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src
|
||||||
|
==== 2026-07-13T09:33:39+02:00 DONE ====
|
||||||
13
kknd2-runs/20260713-093232/prompt-hermes.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
KKND2 mission. You are the hermes3 coding agent.
|
||||||
|
|
||||||
|
Work ONLY in /home/vx/src/openkknd-hermes (already created, seeded from openkknd). Read MISSION.md there.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
1) cd /home/vx/src/openkknd-hermes
|
||||||
|
2) Use exec to run: ./build.sh — fix any compile/link errors with real file edits (write/edit tools).
|
||||||
|
3) If build works, git init if needed and commit "kknd2: green build".
|
||||||
|
4) Write/update README.md with build/run instructions.
|
||||||
|
5) Optionally small improvements only after green build.
|
||||||
|
|
||||||
|
Do not touch /home/vx/openkknd or /home/vx/src/openkknd-qwen.
|
||||||
|
Use tools (exec, read, write, edit). Report what you did and build status.
|
||||||
13
kknd2-runs/20260713-093232/prompt-qwen.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
KKND2 mission. You are the qwen coding agent.
|
||||||
|
|
||||||
|
Work ONLY in /home/vx/src/openkknd-qwen (already created, seeded from openkknd). Read MISSION.md there.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
1) cd /home/vx/src/openkknd-qwen
|
||||||
|
2) Use exec to run: ./build.sh — fix any compile/link errors with real file edits (write/edit tools).
|
||||||
|
3) If build works, git init if needed and commit "kknd2: green build".
|
||||||
|
4) Write/update README.md with build/run instructions.
|
||||||
|
5) Optionally small improvements only after green build.
|
||||||
|
|
||||||
|
Do not touch /home/vx/openkknd or /home/vx/src/openkknd-hermes.
|
||||||
|
Use tools (exec, read, write, edit). Report what you did and build status.
|
||||||
170
kknd2-runs/20260713-093232/qwen-build.txt
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
openkknd: compiling...
|
||||||
|
In file included from src/rng.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/config.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/png.c:8:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/snapshot.c:6:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/snapshot.c: In Funktion »snapshot_capture«:
|
||||||
|
src/snapshot.c:62:5: warning: diese »if«-Klausel beschützt nicht … [-Wmisleading-indentation]
|
||||||
|
62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||||||
|
| ^~
|
||||||
|
src/snapshot.c:62:29: note: … diese Anweisung, aber letztere ist verwirrend eingerückt, als wäre sie abhängig von »if«
|
||||||
|
62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||||||
|
| ^~
|
||||||
|
In file included from src/map.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/map.c: In Funktion »map_init«:
|
||||||
|
src/map.c:66:13: warning: Variable »pos« wird nicht verwendet [-Wunused-variable]
|
||||||
|
66 | int pos = rng_range(rng, 8, m->w - 9);
|
||||||
|
| ^~~
|
||||||
|
In file included from src/sprites.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/units.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/buildings.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/projectiles.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/render.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/render.c: In Funktion »render_buildings«:
|
||||||
|
src/render.c:106:17: warning: Variable »pct« wird nicht verwendet [-Wunused-variable]
|
||||||
|
106 | int pct = (int)(b->build * 100);
|
||||||
|
| ^~~
|
||||||
|
In file included from src/input.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/ai.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/audio.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/save.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/engine.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/main.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/main.c: In Funktion »main«:
|
||||||
|
src/main.c:50:16: warning: Variable »diff« gesetzt, aber nicht verwendet [-Wunused-but-set-variable=]
|
||||||
|
50 | Difficulty diff = DIFF_NORMAL;
|
||||||
|
| ^~~~
|
||||||
|
src/main.c: Auf höchster Ebene:
|
||||||
|
src/main.c:32:13: warning: »save_snapshot« definiert, aber nicht verwendet [-Wunused-function]
|
||||||
|
32 | static void save_snapshot(Game *g, int n) {
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
openkknd: built -> ./openkknd
|
||||||
|
usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd]
|
||||||
2
kknd2-runs/20260713-093232/qwen.err
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
[qwen2.5-coder:14b · 32316ms · ok]
|
||||||
6
kknd2-runs/20260713-093232/qwen.out
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "exec",
|
||||||
|
"arguments": {
|
||||||
|
"command": "cd /home/vx/src/openkknd-qwen && ./build.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
37
kknd2-runs/20260713-093232/run.sh
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -uo pipefail
|
||||||
|
export PATH="$HOME/.local/bin:/usr/bin:$PATH"
|
||||||
|
export OLLAMA_API_KEY=ollama-local
|
||||||
|
export OLLAMA_HOST=127.0.0.1
|
||||||
|
export OPENCLAW_TIMEOUT=1200
|
||||||
|
LOGDIR="${1:?}"
|
||||||
|
PROMPT_QWEN=$(cat "$LOGDIR/prompt-qwen.txt")
|
||||||
|
PROMPT_HERMES=$(cat "$LOGDIR/prompt-hermes.txt")
|
||||||
|
|
||||||
|
echo "==== $(date -Is) QWEN start ====" | tee -a "$LOGDIR/master.log"
|
||||||
|
# free VRAM for 14b
|
||||||
|
ollama-gpu stop hermes3:8b 2>/dev/null || true
|
||||||
|
ollama-gpu stop qwen2.5-coder:7b 2>/dev/null || true
|
||||||
|
|
||||||
|
"$HOME/agent-coder" --qwen14 -s openkknd-qwen "$PROMPT_QWEN" \
|
||||||
|
>"$LOGDIR/qwen.out" 2>"$LOGDIR/qwen.err"
|
||||||
|
echo "qwen exit=$?" | tee -a "$LOGDIR/master.log"
|
||||||
|
tail -40 "$LOGDIR/qwen.out" | tee -a "$LOGDIR/master.log"
|
||||||
|
# build proof
|
||||||
|
(cd "$HOME/src/openkknd-qwen" && ./build.sh) >"$LOGDIR/qwen-build.txt" 2>&1 || true
|
||||||
|
echo "==== qwen tree ====" | tee -a "$LOGDIR/master.log"
|
||||||
|
ls -la "$HOME/src/openkknd-qwen" | tee -a "$LOGDIR/master.log"
|
||||||
|
|
||||||
|
echo "==== $(date -Is) HERMES start ====" | tee -a "$LOGDIR/master.log"
|
||||||
|
ollama-gpu stop qwen2.5-coder:14b 2>/dev/null || true
|
||||||
|
ollama-gpu stop qwen2.5-coder:7b 2>/dev/null || true
|
||||||
|
|
||||||
|
"$HOME/agent-coder" --hermes -s openkknd-hermes "$PROMPT_HERMES" \
|
||||||
|
>"$LOGDIR/hermes.out" 2>"$LOGDIR/hermes.err"
|
||||||
|
echo "hermes exit=$?" | tee -a "$LOGDIR/master.log"
|
||||||
|
tail -40 "$LOGDIR/hermes.out" | tee -a "$LOGDIR/master.log"
|
||||||
|
(cd "$HOME/src/openkknd-hermes" && ./build.sh) >"$LOGDIR/hermes-build.txt" 2>&1 || true
|
||||||
|
echo "==== hermes tree ====" | tee -a "$LOGDIR/master.log"
|
||||||
|
ls -la "$HOME/src/openkknd-hermes" | tee -a "$LOGDIR/master.log"
|
||||||
|
|
||||||
|
echo "==== $(date -Is) DONE ====" | tee -a "$LOGDIR/master.log"
|
||||||
170
kknd2-runs/20260713-093614-q14-deepseek/deepseek-build.txt
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
openkknd: compiling...
|
||||||
|
In file included from src/rng.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/config.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/png.c:8:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/snapshot.c:6:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/snapshot.c: In Funktion »snapshot_capture«:
|
||||||
|
src/snapshot.c:62:5: warning: diese »if«-Klausel beschützt nicht … [-Wmisleading-indentation]
|
||||||
|
62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||||||
|
| ^~
|
||||||
|
src/snapshot.c:62:29: note: … diese Anweisung, aber letztere ist verwirrend eingerückt, als wäre sie abhängig von »if«
|
||||||
|
62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||||||
|
| ^~
|
||||||
|
In file included from src/map.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/map.c: In Funktion »map_init«:
|
||||||
|
src/map.c:66:13: warning: Variable »pos« wird nicht verwendet [-Wunused-variable]
|
||||||
|
66 | int pos = rng_range(rng, 8, m->w - 9);
|
||||||
|
| ^~~
|
||||||
|
In file included from src/sprites.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/units.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/buildings.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/projectiles.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/render.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/render.c: In Funktion »render_buildings«:
|
||||||
|
src/render.c:106:17: warning: Variable »pct« wird nicht verwendet [-Wunused-variable]
|
||||||
|
106 | int pct = (int)(b->build * 100);
|
||||||
|
| ^~~
|
||||||
|
In file included from src/input.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/ai.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/audio.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/save.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/engine.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/main.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/main.c: In Funktion »main«:
|
||||||
|
src/main.c:50:16: warning: Variable »diff« gesetzt, aber nicht verwendet [-Wunused-but-set-variable=]
|
||||||
|
50 | Difficulty diff = DIFF_NORMAL;
|
||||||
|
| ^~~~
|
||||||
|
src/main.c: Auf höchster Ebene:
|
||||||
|
src/main.c:32:13: warning: »save_snapshot« definiert, aber nicht verwendet [-Wunused-function]
|
||||||
|
32 | static void save_snapshot(Game *g, int n) {
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
openkknd: built -> ./openkknd
|
||||||
|
usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd]
|
||||||
2
kknd2-runs/20260713-093614-q14-deepseek/deepseek.err
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
[deepseek-coder:6.7b · 1005ms · ok]
|
||||||
1
kknd2-runs/20260713-093614-q14-deepseek/deepseek.out
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Context overflow: prompt too large for the model. Try /reset (or /new) to start a fresh session, or use a larger-context model.
|
||||||
48
kknd2-runs/20260713-093614-q14-deepseek/master.log
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
==== 2026-07-13T09:36:14+02:00 QWEN14 start ====
|
||||||
|
qwen14 exit=0
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "exec",
|
||||||
|
"arguments": {
|
||||||
|
"command": "cd /home/vx/src/openkknd-qwen14b && ./build.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
insgesamt 136
|
||||||
|
drwxr-xr-x 1 vx vx 144 13. Jul 09:38 .
|
||||||
|
drwxr-xr-x 1 vx vx 142 13. Jul 09:36 ..
|
||||||
|
drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets
|
||||||
|
drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks
|
||||||
|
-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh
|
||||||
|
drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery
|
||||||
|
drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include
|
||||||
|
-rw-r--r-- 1 vx vx 997 13. Jul 09:36 MISSION.md
|
||||||
|
-rwxr-xr-x 1 vx vx 124680 13. Jul 09:38 openkknd
|
||||||
|
-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd
|
||||||
|
drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src
|
||||||
|
==== 2026-07-13T09:38:03+02:00 DEEPSEEK start ====
|
||||||
|
deepseek exit=0
|
||||||
|
Context overflow: prompt too large for the model. Try /reset (or /new) to start a fresh session, or use a larger-context model.
|
||||||
|
insgesamt 136
|
||||||
|
drwxr-xr-x 1 vx vx 144 13. Jul 09:38 .
|
||||||
|
drwxr-xr-x 1 vx vx 142 13. Jul 09:36 ..
|
||||||
|
drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets
|
||||||
|
drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks
|
||||||
|
-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh
|
||||||
|
drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery
|
||||||
|
drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include
|
||||||
|
-rw-r--r-- 1 vx vx 1002 13. Jul 09:36 MISSION.md
|
||||||
|
-rwxr-xr-x 1 vx vx 124680 13. Jul 09:38 openkknd
|
||||||
|
-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd
|
||||||
|
drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src
|
||||||
|
==== 2026-07-13T09:38:15+02:00 DONE ====
|
||||||
|
-- openkknd-qwen14b --
|
||||||
|
-- openkknd-qwen14b --
|
||||||
|
binary=yes
|
||||||
|
README=no
|
||||||
|
git=no
|
||||||
|
-- openkknd-deepseek --
|
||||||
|
-- openkknd-deepseek --
|
||||||
|
binary=yes
|
||||||
|
README=no
|
||||||
|
git=no
|
||||||
46
kknd2-runs/20260713-093614-q14-deepseek/nohup.out
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
==== 2026-07-13T09:36:14+02:00 QWEN14 start ====
|
||||||
|
qwen14 exit=0
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "exec",
|
||||||
|
"arguments": {
|
||||||
|
"command": "cd /home/vx/src/openkknd-qwen14b && ./build.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
insgesamt 136
|
||||||
|
drwxr-xr-x 1 vx vx 144 13. Jul 09:38 .
|
||||||
|
drwxr-xr-x 1 vx vx 142 13. Jul 09:36 ..
|
||||||
|
drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets
|
||||||
|
drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks
|
||||||
|
-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh
|
||||||
|
drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery
|
||||||
|
drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include
|
||||||
|
-rw-r--r-- 1 vx vx 997 13. Jul 09:36 MISSION.md
|
||||||
|
-rwxr-xr-x 1 vx vx 124680 13. Jul 09:38 openkknd
|
||||||
|
-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd
|
||||||
|
drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src
|
||||||
|
==== 2026-07-13T09:38:03+02:00 DEEPSEEK start ====
|
||||||
|
deepseek exit=0
|
||||||
|
Context overflow: prompt too large for the model. Try /reset (or /new) to start a fresh session, or use a larger-context model.
|
||||||
|
insgesamt 136
|
||||||
|
drwxr-xr-x 1 vx vx 144 13. Jul 09:38 .
|
||||||
|
drwxr-xr-x 1 vx vx 142 13. Jul 09:36 ..
|
||||||
|
drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets
|
||||||
|
drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks
|
||||||
|
-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh
|
||||||
|
drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery
|
||||||
|
drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include
|
||||||
|
-rw-r--r-- 1 vx vx 1002 13. Jul 09:36 MISSION.md
|
||||||
|
-rwxr-xr-x 1 vx vx 124680 13. Jul 09:38 openkknd
|
||||||
|
-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd
|
||||||
|
drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src
|
||||||
|
==== 2026-07-13T09:38:15+02:00 DONE ====
|
||||||
|
-- openkknd-qwen14b --
|
||||||
|
binary=yes
|
||||||
|
README=no
|
||||||
|
git=no
|
||||||
|
-- openkknd-deepseek --
|
||||||
|
binary=yes
|
||||||
|
README=no
|
||||||
|
git=no
|
||||||
13
kknd2-runs/20260713-093614-q14-deepseek/prompt-deepseek.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
KKND2 mission. You are the deepseek-coder:6.7b coding agent.
|
||||||
|
|
||||||
|
Work ONLY in /home/vx/src/openkknd-deepseek (seeded from openkknd). Read MISSION.md.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
1) cd /home/vx/src/openkknd-deepseek
|
||||||
|
2) Use exec: ./build.sh — fix compile/link errors with real file edits
|
||||||
|
3) After green build: git init if needed, commit "kknd2: green build"
|
||||||
|
4) Write/update README.md (build + run)
|
||||||
|
5) Optional: fix cheap warnings after green build
|
||||||
|
|
||||||
|
Do not touch /home/vx/openkknd or other openkknd-* trees.
|
||||||
|
Use tools (exec, read, write, edit). Report what you did and build status.
|
||||||
13
kknd2-runs/20260713-093614-q14-deepseek/prompt-qwen14b.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
KKND2 mission. You are the qwen2.5-coder:14b coding agent.
|
||||||
|
|
||||||
|
Work ONLY in /home/vx/src/openkknd-qwen14b (seeded from openkknd). Read MISSION.md.
|
||||||
|
|
||||||
|
Tasks:
|
||||||
|
1) cd /home/vx/src/openkknd-qwen14b
|
||||||
|
2) Use exec: ./build.sh — fix compile/link errors with real file edits
|
||||||
|
3) After green build: git init if needed, commit "kknd2: green build"
|
||||||
|
4) Write/update README.md (build + run)
|
||||||
|
5) Optional: fix cheap warnings (MAX_PARTICLES redefine) after green build
|
||||||
|
|
||||||
|
Do not touch /home/vx/openkknd or other openkknd-* trees.
|
||||||
|
Use tools (exec, read, write, edit). Report what you did and build status.
|
||||||
170
kknd2-runs/20260713-093614-q14-deepseek/qwen14b-build.txt
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
openkknd: compiling...
|
||||||
|
In file included from src/rng.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/config.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/png.c:8:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/snapshot.c:6:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/snapshot.c: In Funktion »snapshot_capture«:
|
||||||
|
src/snapshot.c:62:5: warning: diese »if«-Klausel beschützt nicht … [-Wmisleading-indentation]
|
||||||
|
62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||||||
|
| ^~
|
||||||
|
src/snapshot.c:62:29: note: … diese Anweisung, aber letztere ist verwirrend eingerückt, als wäre sie abhängig von »if«
|
||||||
|
62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||||||
|
| ^~
|
||||||
|
In file included from src/map.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/map.c: In Funktion »map_init«:
|
||||||
|
src/map.c:66:13: warning: Variable »pos« wird nicht verwendet [-Wunused-variable]
|
||||||
|
66 | int pos = rng_range(rng, 8, m->w - 9);
|
||||||
|
| ^~~
|
||||||
|
In file included from src/sprites.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/units.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/buildings.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/projectiles.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/render.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/render.c: In Funktion »render_buildings«:
|
||||||
|
src/render.c:106:17: warning: Variable »pct« wird nicht verwendet [-Wunused-variable]
|
||||||
|
106 | int pct = (int)(b->build * 100);
|
||||||
|
| ^~~
|
||||||
|
In file included from src/input.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/ai.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/audio.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/save.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/engine.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
In file included from src/main.c:1:
|
||||||
|
include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment]
|
||||||
|
10 | * matching src/*.c translation units.
|
||||||
|
include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert
|
||||||
|
277 | #define MAX_PARTICLES 2048
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition
|
||||||
|
46 | #define MAX_PARTICLES 4000
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
src/main.c: In Funktion »main«:
|
||||||
|
src/main.c:50:16: warning: Variable »diff« gesetzt, aber nicht verwendet [-Wunused-but-set-variable=]
|
||||||
|
50 | Difficulty diff = DIFF_NORMAL;
|
||||||
|
| ^~~~
|
||||||
|
src/main.c: Auf höchster Ebene:
|
||||||
|
src/main.c:32:13: warning: »save_snapshot« definiert, aber nicht verwendet [-Wunused-function]
|
||||||
|
32 | static void save_snapshot(Game *g, int n) {
|
||||||
|
| ^~~~~~~~~~~~~
|
||||||
|
openkknd: built -> ./openkknd
|
||||||
|
usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd]
|
||||||
2
kknd2-runs/20260713-093614-q14-deepseek/qwen14b.err
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
[qwen2.5-coder:14b · 97809ms · ok]
|
||||||
8
kknd2-runs/20260713-093614-q14-deepseek/qwen14b.out
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "exec",
|
||||||
|
"arguments": {
|
||||||
|
"command": "cd /home/vx/src/openkknd-qwen14b && ./build.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
42
kknd2-runs/20260713-093614-q14-deepseek/run.sh
Executable file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -uo pipefail
|
||||||
|
export PATH="$HOME/.local/bin:/usr/bin:$PATH"
|
||||||
|
export OLLAMA_API_KEY=ollama-local
|
||||||
|
export OLLAMA_HOST=127.0.0.1
|
||||||
|
export OPENCLAW_TIMEOUT=1200
|
||||||
|
LOGDIR="${1:?}"
|
||||||
|
PROMPT_Q14=$(cat "$LOGDIR/prompt-qwen14b.txt")
|
||||||
|
PROMPT_DS=$(cat "$LOGDIR/prompt-deepseek.txt")
|
||||||
|
|
||||||
|
echo "==== $(date -Is) QWEN14 start ====" | tee -a "$LOGDIR/master.log"
|
||||||
|
ollama-gpu stop hermes3:8b 2>/dev/null || true
|
||||||
|
ollama-gpu stop qwen2.5-coder:7b 2>/dev/null || true
|
||||||
|
ollama-gpu stop deepseek-coder:6.7b 2>/dev/null || true
|
||||||
|
|
||||||
|
"$HOME/agent-coder" --qwen14 -s openkknd-qwen14b "$PROMPT_Q14" \
|
||||||
|
>"$LOGDIR/qwen14b.out" 2>"$LOGDIR/qwen14b.err"
|
||||||
|
echo "qwen14 exit=$?" | tee -a "$LOGDIR/master.log"
|
||||||
|
tail -50 "$LOGDIR/qwen14b.out" | tee -a "$LOGDIR/master.log"
|
||||||
|
(cd "$HOME/src/openkknd-qwen14b" && ./build.sh) >"$LOGDIR/qwen14b-build.txt" 2>&1 || true
|
||||||
|
ls -la "$HOME/src/openkknd-qwen14b" | tee -a "$LOGDIR/master.log"
|
||||||
|
|
||||||
|
echo "==== $(date -Is) DEEPSEEK start ====" | tee -a "$LOGDIR/master.log"
|
||||||
|
ollama-gpu stop qwen2.5-coder:14b 2>/dev/null || true
|
||||||
|
ollama-gpu stop qwen2.5-coder:7b 2>/dev/null || true
|
||||||
|
ollama-gpu stop hermes3:8b 2>/dev/null || true
|
||||||
|
|
||||||
|
"$HOME/agent-coder" --deepseek -s openkknd-deepseek "$PROMPT_DS" \
|
||||||
|
>"$LOGDIR/deepseek.out" 2>"$LOGDIR/deepseek.err"
|
||||||
|
echo "deepseek exit=$?" | tee -a "$LOGDIR/master.log"
|
||||||
|
tail -50 "$LOGDIR/deepseek.out" | tee -a "$LOGDIR/master.log"
|
||||||
|
(cd "$HOME/src/openkknd-deepseek" && ./build.sh) >"$LOGDIR/deepseek-build.txt" 2>&1 || true
|
||||||
|
ls -la "$HOME/src/openkknd-deepseek" | tee -a "$LOGDIR/master.log"
|
||||||
|
|
||||||
|
echo "==== $(date -Is) DONE ====" | tee -a "$LOGDIR/master.log"
|
||||||
|
# summary
|
||||||
|
for d in openkknd-qwen14b openkknd-deepseek; do
|
||||||
|
echo "-- $d --" | tee -a "$LOGDIR/master.log"
|
||||||
|
test -x "$HOME/src/$d/openkknd" && echo binary=yes || echo binary=no
|
||||||
|
test -f "$HOME/src/$d/README.md" && echo README=yes || echo README=no
|
||||||
|
test -d "$HOME/src/$d/.git" && echo git=yes || echo git=no
|
||||||
|
done | tee -a "$LOGDIR/master.log"
|
||||||
31
llm-configs-openkknd2/README.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# openkknd2 launchers (per host / tool)
|
||||||
|
|
||||||
|
Each script prepares **`$OPENKKND2_ROOT`** under `~/src/` (unique folder per tool/host) and prints the agent brief for autonomous work.
|
||||||
|
|
||||||
|
| Script | Host | Tool | Default workdir |
|
||||||
|
|--------|------|------|-----------------|
|
||||||
|
| `scripts/start-mac-osaurus.sh` | Mac | Osaurus agent | `~/src/openkknd2-osaurus` |
|
||||||
|
| `scripts/start-mac-jan.sh` | Mac | Jan | `~/src/openkknd2-jan` |
|
||||||
|
| `scripts/start-amanita-coder.sh` | amanita | agent-coder / OpenClaw | `~/src/openkknd2-am-coder` |
|
||||||
|
| `scripts/start-amanita-pics.sh` | amanita | am-agent-pics + code | `~/src/openkknd2-am-pics` |
|
||||||
|
| `scripts/start-amanita-vids.sh` | amanita | am-agent-vids + cinematics | `~/src/openkknd2-am-vids` |
|
||||||
|
| `scripts/common.sh` | all | shared bootstrap | — |
|
||||||
|
|
||||||
|
## Env
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LLM_CONFIGS=~/src/llm-configs # this repo
|
||||||
|
export OPENKKND2_ROOT=~/src/openkknd2-<tool>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agent must
|
||||||
|
|
||||||
|
1. Work only under `$OPENKKND2_ROOT` (plus read-only knowledge).
|
||||||
|
2. Stay autonomous → playable game + headless tests.
|
||||||
|
3. **No copyrighted KKnD assets.**
|
||||||
|
4. Cinematics: original pipeline (instruction 18–19).
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
- [../instructions/18-openkknd2-autonomous-rebuild.md](../instructions/18-openkknd2-autonomous-rebuild.md)
|
||||||
|
- [../instructions/19-openkknd2-cinematics.md](../instructions/19-openkknd2-cinematics.md)
|
||||||
108
llm-configs-openkknd2/scripts/common.sh
Executable file
|
|
@ -0,0 +1,108 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Bootstrap OPENKKND2_ROOT and seed legal stubs.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
openkknd2_bootstrap() {
|
||||||
|
local root="${1:?root}"
|
||||||
|
mkdir -p "$root"/{src,include,assets/cinematics,docs,scripts,tests}
|
||||||
|
if [[ ! -f "$root/LICENSE" ]]; then
|
||||||
|
cat >"$root/LICENSE" <<'L'
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 openkknd2 contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
L
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$root/THIRD_PARTY.md" ]]; then
|
||||||
|
cat >"$root/THIRD_PARTY.md" <<'T'
|
||||||
|
# Third-party
|
||||||
|
|
||||||
|
Only permissive / original materials. **Forbidden:** KKnD/KKnD2/Xtreme proprietary assets.
|
||||||
|
|
||||||
|
| Component | License | Note |
|
||||||
|
|-----------|---------|------|
|
||||||
|
| (none yet) | | |
|
||||||
|
T
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$root/docs/LORE.md" ]]; then
|
||||||
|
cat >"$root/docs/LORE.md" <<'O'
|
||||||
|
# Lore (original)
|
||||||
|
|
||||||
|
Working names (change freely; **do not** use commercial KKnD faction trademarks as official names):
|
||||||
|
|
||||||
|
- Faction A: **Ashcoat Compact** (scrap tech, discipline)
|
||||||
|
- Faction B: **Bloomed** (mutated ecology, swarm tactics)
|
||||||
|
|
||||||
|
Tone: dry wasteland humor, not a copy of any commercial script.
|
||||||
|
O
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$root/AGENT_LOG.md" ]]; then
|
||||||
|
echo "# Agent log" >"$root/AGENT_LOG.md"
|
||||||
|
echo "" >>"$root/AGENT_LOG.md"
|
||||||
|
echo "Started $(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$root/AGENT_LOG.md"
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$root/scripts/ci-headless.sh" ]]; then
|
||||||
|
cat >"$root/scripts/ci-headless.sh" <<'C'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
# Placeholder until engine exists: legal tree check
|
||||||
|
rg -i 'kknd2|krossfire|\.iso\b' --glob '!**/.git/**' . && {
|
||||||
|
echo "FORBIDDEN string found" >&2
|
||||||
|
exit 2
|
||||||
|
} || true
|
||||||
|
echo "ci-headless: stub OK (extend when binary exists)"
|
||||||
|
C
|
||||||
|
chmod +x "$root/scripts/ci-headless.sh"
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$root/README.md" ]]; then
|
||||||
|
cat >"$root/README.md" <<R
|
||||||
|
# openkknd2 (worktree)
|
||||||
|
|
||||||
|
Autonomous agent worktree. **Not** a KKnD remake.
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
export OPENKKND2_ROOT=$root
|
||||||
|
# build/test TBD by agent
|
||||||
|
./scripts/ci-headless.sh
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Instructions: \`\$LLM_CONFIGS/instructions/18-openkknd2-autonomous-rebuild.md\`
|
||||||
|
R
|
||||||
|
fi
|
||||||
|
export OPENKKND2_ROOT="$root"
|
||||||
|
echo "OPENKKND2_ROOT=$OPENKKND2_ROOT"
|
||||||
|
}
|
||||||
|
|
||||||
|
openkknd2_print_brief() {
|
||||||
|
local tool="$1"
|
||||||
|
cat <<EOF
|
||||||
|
=== openkknd2 agent brief ($tool) ===
|
||||||
|
OPENKKND2_ROOT=$OPENKKND2_ROOT
|
||||||
|
LLM_CONFIGS=${LLM_CONFIGS:-$HOME/src/llm-configs}
|
||||||
|
Knowledge: \$HOME/OsaurusKnowledge/openkknd2/ (or mirror in llm-configs)
|
||||||
|
Read and follow:
|
||||||
|
\$LLM_CONFIGS/instructions/18-openkknd2-autonomous-rebuild.md
|
||||||
|
\$LLM_CONFIGS/instructions/19-openkknd2-cinematics.md
|
||||||
|
Rules: original/permissive assets only; autonomous; headless tests after each chunk.
|
||||||
|
Cinematics params: MEDIA_WIDTH/HEIGHT/FPS/SECONDS via llm-configs/media/
|
||||||
|
EOF
|
||||||
|
}
|
||||||
240
llm-configs-openkknd2/scripts/rotate-models-amanita.sh
Executable file
|
|
@ -0,0 +1,240 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# rotate-models-amanita.sh — try coding models one-by-one until openkknd2 progresses.
|
||||||
|
#
|
||||||
|
# Run ON amanita (or: ssh amanita bash -s < this-script).
|
||||||
|
# Uses short Zusatzprompt; fresh OpenClaw session per model; progress gate on tree.
|
||||||
|
#
|
||||||
|
# OPENKKND2_ROOT=~/src/openkknd2-am-coder \
|
||||||
|
# LLM_CONFIGS=~/src/llm-configs \
|
||||||
|
# bash rotate-models-amanita.sh
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH}"
|
||||||
|
export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}"
|
||||||
|
ROOT="${OPENKKND2_ROOT:-$HOME/src/openkknd2-am-coder}"
|
||||||
|
LOG_DIR="${ROTATE_LOG_DIR:-$ROOT/rotate-logs}"
|
||||||
|
PROMPT_FILE="${PROMPT_FILE:-$LLM_CONFIGS/prompts/openkknd2-continue-short.md}"
|
||||||
|
MAX_TURNS="${ROTATE_MAX_TURNS:-12}"
|
||||||
|
AGENT="${AGENT_CODER:-$HOME/agent-coder}"
|
||||||
|
# Single-flight load policy: never run next model until previous fully stopped;
|
||||||
|
# unload other ollama models between slots; no parallel pulls.
|
||||||
|
SINGLE_FLIGHT="${ROTATE_SINGLE_FLIGHT:-1}"
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
unload_other_ollama() {
|
||||||
|
# Keep the model we are about to use; stop everything else loaded.
|
||||||
|
local keep="${1:-}"
|
||||||
|
command -v ollama >/dev/null 2>&1 || return 0
|
||||||
|
local name
|
||||||
|
while read -r name _; do
|
||||||
|
[[ -z "${name:-}" || "$name" == "NAME" ]] && continue
|
||||||
|
if [[ -n "$keep" && "$name" == *"$keep"* ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
ollama stop "$name" 2>/dev/null || true
|
||||||
|
done < <(ollama ps 2>/dev/null | awk 'NR>1 {print $1}' || true)
|
||||||
|
# Never start pulls mid-rotate
|
||||||
|
pkill -TERM -f "ollama pull" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# agent-coder model flags in order (skip already-failed qwen first if SKIP_QWEN=1)
|
||||||
|
# Devstral = agentic coding specialist (Mistral + All Hands)
|
||||||
|
MODELS_DEFAULT=(
|
||||||
|
"--devstral"
|
||||||
|
"--coder"
|
||||||
|
"--qwen314"
|
||||||
|
"--qwen14"
|
||||||
|
"--deepseek"
|
||||||
|
"--qwen38"
|
||||||
|
"--hermes"
|
||||||
|
)
|
||||||
|
# Optional: also try qwen3.6 last (heavy, already flaked once)
|
||||||
|
if [[ "${INCLUDE_QWEN36:-0}" == "1" ]]; then
|
||||||
|
MODELS_DEFAULT+=("--qwen36")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2206
|
||||||
|
MODELS=( ${ROTATE_MODELS:-${MODELS_DEFAULT[*]}} )
|
||||||
|
|
||||||
|
ts() { date -u +%Y-%m-%dT%H:%M:%SZ; }
|
||||||
|
stamp() { date -u +%Y%m%dT%H%M%SZ; }
|
||||||
|
|
||||||
|
code_stats() {
|
||||||
|
python3 - <<'PY'
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
root = Path(os.environ.get("ROOT", "."))
|
||||||
|
skip = {".git", "build", "node_modules", "rotate-logs"}
|
||||||
|
exts = {".c", ".h", ".cpp", ".hpp", ".cc", ".py", ".sh", ".cmake"}
|
||||||
|
files, lines = 0, 0
|
||||||
|
names = []
|
||||||
|
for p in root.rglob("*"):
|
||||||
|
if any(part in skip for part in p.parts):
|
||||||
|
continue
|
||||||
|
if not p.is_file():
|
||||||
|
continue
|
||||||
|
if p.suffix.lower() not in exts and p.name != "CMakeLists.txt":
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
text = p.read_text(encoding="utf-8", errors="ignore")
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
n = text.count("\n") + (1 if text and not text.endswith("\n") else 0)
|
||||||
|
files += 1
|
||||||
|
lines += n
|
||||||
|
names.append(str(p.relative_to(root)))
|
||||||
|
has_build = any("ok_build" in n for n in names)
|
||||||
|
has_ai = any("ok_ai" in n for n in names)
|
||||||
|
print(f"{files} {lines} {int(has_build)} {int(has_ai)}")
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
ci_green() {
|
||||||
|
if [[ -x "$ROOT/scripts/ci-headless.sh" ]]; then
|
||||||
|
(cd "$ROOT" && ./scripts/ci-headless.sh) >/tmp/ok2-rotate-ci.log 2>&1 && return 0
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
progressed() {
|
||||||
|
# args: before "files lines has_build has_ai"
|
||||||
|
local b_files b_lines b_b b_a a_files a_lines a_b a_a
|
||||||
|
read -r b_files b_lines b_b b_a <<<"$1"
|
||||||
|
read -r a_files a_lines a_b a_a <<<"$2"
|
||||||
|
# success if meaningful code growth OR new build/ai modules + still green
|
||||||
|
if (( a_lines >= b_lines + 40 )); then return 0; fi
|
||||||
|
if (( a_files > b_files && a_lines > b_lines )); then return 0; fi
|
||||||
|
if (( a_b > b_b || a_a > b_a )) && (( a_lines > b_lines )); then return 0; fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ! -x "$AGENT" ]]; then
|
||||||
|
echo "error: agent-coder missing at $AGENT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$PROMPT_FILE" ]]; then
|
||||||
|
echo "error: prompt missing: $PROMPT_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
PROMPT="$(cat "$PROMPT_FILE")"
|
||||||
|
SUMMARY="$LOG_DIR/rotate-$(stamp).md"
|
||||||
|
{
|
||||||
|
echo "# openkknd2 model rotation"
|
||||||
|
echo
|
||||||
|
echo "- started: $(ts)"
|
||||||
|
echo "- workdir: \`$ROOT\`"
|
||||||
|
echo "- prompt: \`$PROMPT_FILE\`"
|
||||||
|
echo "- max_turns: $MAX_TURNS"
|
||||||
|
echo
|
||||||
|
echo "| # | flag | session | before lines | after lines | progress | ci |"
|
||||||
|
echo "|---|------|---------|--------------|-------------|----------|----|"
|
||||||
|
} >"$SUMMARY"
|
||||||
|
|
||||||
|
echo "=== model rotation start $(ts) ===" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
echo "ROOT=$ROOT models=${MODELS[*]}" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
|
||||||
|
i=0
|
||||||
|
for flag in "${MODELS[@]}"; do
|
||||||
|
i=$((i + 1))
|
||||||
|
sess="ok2-rot${i}-$(echo "$flag" | tr -cd 'A-Za-z0-9')"
|
||||||
|
runlog="$LOG_DIR/run-${i}$(echo "$flag" | tr -cd 'A-Za-z0-9').log"
|
||||||
|
echo | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
echo ">>> [$i/${#MODELS[@]}] model flag=$flag session=$sess $(ts)" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
|
||||||
|
export ROOT
|
||||||
|
before="$(code_stats)"
|
||||||
|
read -r bf bl bb ba <<<"$before"
|
||||||
|
echo " before: files=$bf lines=$bl build=$bb ai=$ba" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
|
||||||
|
# Stop any leftover registered agent for this tree (best effort)
|
||||||
|
am-task stop all --reason "rotate: switch to $flag" 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
if [[ "$SINGLE_FLIGHT" == "1" ]]; then
|
||||||
|
# Map flag → ollama name fragment for keep-list
|
||||||
|
keep_frag=""
|
||||||
|
case "$flag" in
|
||||||
|
--devstral) keep_frag="devstral" ;;
|
||||||
|
--coder) keep_frag="qwen3-coder" ;;
|
||||||
|
--qwen36) keep_frag="qwen3.6" ;;
|
||||||
|
--qwen314) keep_frag="qwen3:14b" ;;
|
||||||
|
--qwen38) keep_frag="qwen3:8b" ;;
|
||||||
|
--qwen14) keep_frag="qwen2.5-coder:14b" ;;
|
||||||
|
--deepseek) keep_frag="deepseek" ;;
|
||||||
|
--hermes) keep_frag="hermes" ;;
|
||||||
|
esac
|
||||||
|
echo " single-flight: unload other ollama models (keep~$keep_frag)" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
unload_other_ollama "$keep_frag"
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set +e
|
||||||
|
(
|
||||||
|
cd "$ROOT"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
"$AGENT" $flag -s "$sess" -A --max-turns "$MAX_TURNS" --detach "$PROMPT"
|
||||||
|
) >"$runlog" 2>&1
|
||||||
|
launch_ec=$?
|
||||||
|
set -e
|
||||||
|
echo " launch_ec=$launch_ec log=$runlog" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
|
||||||
|
# Wait until no active am-task (or timeout)
|
||||||
|
deadline=$(( $(date +%s) + ${ROTATE_WAIT_SECS:-3600} ))
|
||||||
|
while (( $(date +%s) < deadline )); do
|
||||||
|
active="$(ls "$HOME/.am-agent-tasks/active"/*.json 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
if [[ "${active:-0}" == "0" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# still running
|
||||||
|
sleep 15
|
||||||
|
done
|
||||||
|
# if still active after wait — stop and move on
|
||||||
|
if ls "$HOME/.am-agent-tasks/active"/*.json >/dev/null 2>&1; then
|
||||||
|
echo " timeout waiting — stop all" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
am-task stop all --reason "rotate: wait timeout after $flag" 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
export ROOT
|
||||||
|
after="$(code_stats)"
|
||||||
|
read -r af al ab aa <<<"$after"
|
||||||
|
echo " after: files=$af lines=$al build=$ab ai=$aa" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
|
||||||
|
ci="fail"
|
||||||
|
if ci_green; then ci="green"; fi
|
||||||
|
echo " ci=$ci" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
|
||||||
|
prog="no"
|
||||||
|
if progressed "$before" "$after"; then prog="YES"; fi
|
||||||
|
|
||||||
|
echo "| $i | \`$flag\` | \`$sess\` | $bl | $al | $prog | $ci |" >>"$SUMMARY"
|
||||||
|
|
||||||
|
if [[ "$prog" == "YES" ]]; then
|
||||||
|
{
|
||||||
|
echo
|
||||||
|
echo "## WINNER"
|
||||||
|
echo
|
||||||
|
echo "- flag: \`$flag\`"
|
||||||
|
echo "- session: \`$sess\`"
|
||||||
|
echo "- lines: $bl → $al"
|
||||||
|
echo "- ci: $ci"
|
||||||
|
echo "- finished: $(ts)"
|
||||||
|
} >>"$SUMMARY"
|
||||||
|
echo "=== PROGRESS with $flag — stop rotation ===" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
echo "SUMMARY=$SUMMARY"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo " no meaningful progress — next model" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
done
|
||||||
|
|
||||||
|
{
|
||||||
|
echo
|
||||||
|
echo "## RESULT"
|
||||||
|
echo
|
||||||
|
echo "No model produced meaningful code growth within max_turns=$MAX_TURNS."
|
||||||
|
echo "- finished: $(ts)"
|
||||||
|
} >>"$SUMMARY"
|
||||||
|
echo "=== rotation exhausted without progress ===" | tee -a "$LOG_DIR/rotate.out"
|
||||||
|
echo "SUMMARY=$SUMMARY"
|
||||||
|
exit 1
|
||||||
13
llm-configs-openkknd2/scripts/start-amanita-coder.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}"
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "$LLM_CONFIGS/openkknd2/scripts/common.sh"
|
||||||
|
openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-am-coder}"
|
||||||
|
openkknd2_print_brief "amanita-coder"
|
||||||
|
if [[ -x "$HOME/agent-coder" ]]; then
|
||||||
|
echo "Launch example:"
|
||||||
|
echo " cd \"\$OPENKKND2_ROOT\" && ~/agent-coder --coder \"Read \$LLM_CONFIGS/instructions/18-openkknd2-autonomous-rebuild.md and continue autonomous openkknd2 build with headless tests.\""
|
||||||
|
else
|
||||||
|
echo "agent-coder not found at ~/agent-coder — start OpenClaw manually in \$OPENKKND2_ROOT"
|
||||||
|
fi
|
||||||
9
llm-configs-openkknd2/scripts/start-amanita-pics.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}"
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "$LLM_CONFIGS/openkknd2/scripts/common.sh"
|
||||||
|
openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-am-pics}"
|
||||||
|
openkknd2_print_brief "amanita-pics"
|
||||||
|
echo "Concept art path: use $LLM_CONFIGS/media/am-agent-pics with original prompts only."
|
||||||
|
echo " $LLM_CONFIGS/media/am-agent-pics --width 512 --height 512 --steps 2 --prompt 'original scrap-tech scout silhouette, wasteland, no logos'"
|
||||||
11
llm-configs-openkknd2/scripts/start-amanita-vids.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}"
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "$LLM_CONFIGS/openkknd2/scripts/common.sh"
|
||||||
|
openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-am-vids}"
|
||||||
|
openkknd2_print_brief "amanita-vids"
|
||||||
|
echo "Cinematics: instruction 19 + knowledge 03-cinematics."
|
||||||
|
echo " export MEDIA_SECONDS=2 MEDIA_FPS=10 MEDIA_WIDTH=640 MEDIA_HEIGHT=360"
|
||||||
|
echo " $LLM_CONFIGS/media/am-agent-vids --make-still --seconds 2 --fps 10"
|
||||||
|
echo "Move results into \$OPENKKND2_ROOT/assets/cinematics/"
|
||||||
8
llm-configs-openkknd2/scripts/start-mac-jan.sh
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}"
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "$LLM_CONFIGS/openkknd2/scripts/common.sh"
|
||||||
|
openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-jan}"
|
||||||
|
openkknd2_print_brief "mac-jan"
|
||||||
|
echo "Next: Jan Ops/Coder thread → paste brief; enable tools; stay under \$OPENKKND2_ROOT"
|
||||||
8
llm-configs-openkknd2/scripts/start-mac-osaurus.sh
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}"
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source "$LLM_CONFIGS/openkknd2/scripts/common.sh"
|
||||||
|
openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-osaurus}"
|
||||||
|
openkknd2_print_brief "mac-osaurus"
|
||||||
|
echo "Next: open Osaurus Coder → paste brief + instruction 18; work only under \$OPENKKND2_ROOT"
|
||||||
9
openkknd2-am-coder/.agent-coder-resume.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||||||
|
export LLM_CONFIGS=/home/vx/src/llm-configs
|
||||||
|
export OPENCLAW_MODEL=ollama/qwen3:14b
|
||||||
|
export OPENCLAW_TIMEOUT=900
|
||||||
|
export AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
cd /home/vx/src/openkknd2-am-coder
|
||||||
|
exec /home/vx/agent-coder -s ok2meta-r6-qwen314 -A --max-turns 8 --iref 21-always-agent-logs.md RESUME:\ continue\ from\ AGENT_LOG.md\ and\ STATUS.md\ in\ this\ workdir.\ Do\ not\ re-ask.\ Emit\ ##\ AUTONOMOUS_STOP\ only\ when\ truly\ blocked.
|
||||||
144
openkknd2-am-coder/.am-meta/META_LOG.md
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# am-meta log
|
||||||
|
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- created: 2026-07-22T05:21:20Z
|
||||||
|
|
||||||
|
- **2026-07-22T05:21:22Z** · meta-stop · hand off to am-meta
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T05:21:24Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38`
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- **2026-07-22T05:21:24Z** · meta detached pid=1722387 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `9 183 0`
|
||||||
|
- **2026-07-22T05:21:25Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8
|
||||||
|
- **2026-07-22T05:21:34Z** · after `9 183 0`
|
||||||
|
- **2026-07-22T05:21:34Z** · progress · NO with `--devstral` (streak_fail=1) — next model
|
||||||
|
|
||||||
|
### round 2 / 16 · `--coder` · before `9 183 0`
|
||||||
|
- **2026-07-22T05:21:35Z** · launch · flag=`--coder` session=`ok2meta-r2-coder` turns=8
|
||||||
|
- **2026-07-22T05:22:20Z** · meta-stop · redeploy race fix
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T05:22:25Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38`
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- **2026-07-22T05:22:25Z** · meta detached pid=1725467 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `9 183 0`
|
||||||
|
- **2026-07-22T05:22:26Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8
|
||||||
|
- **2026-07-22T05:28:56Z** · after `9 183 0`
|
||||||
|
- **2026-07-22T05:28:56Z** · progress · NO with `--devstral` (streak_fail=1) — next model
|
||||||
|
|
||||||
|
### round 2 / 16 · `--coder` · before `9 183 0`
|
||||||
|
- **2026-07-22T05:28:58Z** · launch · flag=`--coder` session=`ok2meta-r2-coder` turns=8
|
||||||
|
- **2026-07-22T05:34:48Z** · after `9 183 0`
|
||||||
|
- **2026-07-22T05:34:48Z** · progress · NO with `--coder` (streak_fail=2) — next model
|
||||||
|
|
||||||
|
### round 3 / 16 · `--qwen314` · before `9 183 0`
|
||||||
|
- **2026-07-22T05:34:49Z** · launch · flag=`--qwen314` session=`ok2meta-r3-qwen314` turns=8
|
||||||
|
- **2026-07-22T05:50:19Z** · after `10 208 2`
|
||||||
|
- **2026-07-22T05:50:19Z** · progress · YES with `--qwen314` — keep same model
|
||||||
|
|
||||||
|
### round 4 / 16 · `--qwen314` · before `10 208 2`
|
||||||
|
- **2026-07-22T05:50:20Z** · launch · flag=`--qwen314` session=`ok2meta-r4-qwen314` turns=8
|
||||||
|
- **2026-07-22T05:54:31Z** · after `10 208 2`
|
||||||
|
- **2026-07-22T05:54:31Z** · progress · NO with `--qwen314` (streak_fail=1) — next model
|
||||||
|
|
||||||
|
### round 5 / 16 · `--qwen14` · before `10 208 2`
|
||||||
|
- **2026-07-22T05:54:32Z** · launch · flag=`--qwen14` session=`ok2meta-r5-qwen14` turns=8
|
||||||
|
- **2026-07-22T05:59:02Z** · after `10 208 2`
|
||||||
|
- **2026-07-22T05:59:02Z** · progress · NO with `--qwen14` (streak_fail=2) — next model
|
||||||
|
|
||||||
|
### round 6 / 16 · `--deepseek` · before `10 208 2`
|
||||||
|
- **2026-07-22T05:59:03Z** · launch · flag=`--deepseek` session=`ok2meta-r6-deepseek` turns=8
|
||||||
|
- **2026-07-22T05:59:53Z** · after `10 208 2`
|
||||||
|
- **2026-07-22T05:59:53Z** · progress · NO with `--deepseek` (streak_fail=3) — next model
|
||||||
|
|
||||||
|
### round 7 / 16 · `--qwen38` · before `10 208 2`
|
||||||
|
- **2026-07-22T05:59:54Z** · launch · flag=`--qwen38` session=`ok2meta-r7-qwen38` turns=8
|
||||||
|
- **2026-07-22T06:03:58Z** · meta-stop · user: beende alle aufgaben
|
||||||
|
- **2026-07-22T06:05:00Z** · meta-stop · clean restart
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T06:05:01Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard)
|
||||||
|
- stalls_before_switch: 2 accept_needs: 1
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md
|
||||||
|
- **2026-07-22T06:05:01Z** · meta detached pid=3574950 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:05:02Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8
|
||||||
|
- **2026-07-22T06:05:32Z** · after `11 228 2`
|
||||||
|
- **2026-07-22T06:05:32Z** · needs · harvested NEED_INSTRUCTIONS → additive.md (mission unchanged)
|
||||||
|
- **2026-07-22T06:05:32Z** · DONE detected after round 1
|
||||||
|
- **2026-07-22T06:07:12Z** · meta-stop · restart after false-DONE fix
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T06:07:13Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard)
|
||||||
|
- stalls_before_switch: 2 accept_needs: 1
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md
|
||||||
|
- **2026-07-22T06:07:13Z** · meta detached pid=3577057 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:07:14Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8
|
||||||
|
- **2026-07-22T06:07:43Z** · after `11 228 2`
|
||||||
|
- **2026-07-22T06:07:43Z** · progress · NO with `--devstral` (streak_fail=1 < 2) — **retry same model**
|
||||||
|
|
||||||
|
### round 2 / 16 · `--devstral` · before `11 228 2` · streak_fail=1
|
||||||
|
- **2026-07-22T06:07:44Z** · launch · flag=`--devstral` session=`ok2meta-r2-devstral` turns=8
|
||||||
|
- **2026-07-22T06:09:11Z** · meta-stop · user: session end
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T06:09:21Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard)
|
||||||
|
- stalls_before_switch: 2 accept_needs: 1
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md
|
||||||
|
- **2026-07-22T06:09:21Z** · meta detached pid=3579576 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:09:22Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8
|
||||||
|
- **2026-07-22T06:09:37Z** · meta-stop · user: n — todos beenden
|
||||||
|
- **2026-07-22T06:09:56Z** · meta-stop · recover after wrong stop
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T06:09:57Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard)
|
||||||
|
- stalls_before_switch: 2 accept_needs: 1
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md
|
||||||
|
- **2026-07-22T06:09:57Z** · meta detached pid=3580490 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:09:59Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8
|
||||||
|
- **2026-07-22T06:10:28Z** · after `11 228 2`
|
||||||
|
- **2026-07-22T06:10:28Z** · progress · NO with `--devstral` (streak_fail=1 < 2) — **retry same model**
|
||||||
|
|
||||||
|
### round 2 / 16 · `--devstral` · before `11 228 2` · streak_fail=1
|
||||||
|
- **2026-07-22T06:10:29Z** · launch · flag=`--devstral` session=`ok2meta-r2-devstral` turns=8
|
||||||
|
- **2026-07-22T06:15:58Z** · after `11 228 2`
|
||||||
|
- **2026-07-22T06:15:58Z** · progress · NO with `--devstral` (streak_fail=2 ≥ 2) — **switch model**
|
||||||
|
|
||||||
|
### round 3 / 16 · `--coder` · before `11 228 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:15:59Z** · launch · flag=`--coder` session=`ok2meta-r3-coder` turns=8
|
||||||
|
- **2026-07-22T06:21:48Z** · after `12 273 2`
|
||||||
|
- **2026-07-22T06:21:48Z** · progress · YES with `--coder` — keep same model; reset stall counter
|
||||||
|
|
||||||
|
### round 4 / 16 · `--coder` · before `12 273 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:21:50Z** · launch · flag=`--coder` session=`ok2meta-r4-coder` turns=8
|
||||||
|
- **2026-07-22T06:26:39Z** · after `12 273 2`
|
||||||
|
- **2026-07-22T06:26:39Z** · progress · NO with `--coder` (streak_fail=1 < 2) — **retry same model**
|
||||||
|
|
||||||
|
### round 5 / 16 · `--coder` · before `12 273 2` · streak_fail=1
|
||||||
|
- **2026-07-22T06:26:40Z** · launch · flag=`--coder` session=`ok2meta-r5-coder` turns=8
|
||||||
|
- **2026-07-22T06:30:49Z** · after `12 273 2`
|
||||||
|
- **2026-07-22T06:30:49Z** · progress · NO with `--coder` (streak_fail=2 ≥ 2) — **switch model**
|
||||||
|
|
||||||
|
### round 6 / 16 · `--qwen314` · before `12 273 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:30:50Z** · launch · flag=`--qwen314` session=`ok2meta-r6-qwen314` turns=8
|
||||||
|
- **2026-07-22T06:46:00Z** · stall · AGENT_LOG frozen ≥900s while task active → stop child
|
||||||
6
openkknd2-am-coder/.am-meta/additive.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Additive instructions (harvested / evolved)
|
||||||
|
|
||||||
|
These lines may grow when the agent emits `## NEED_INSTRUCTIONS`.
|
||||||
|
They **must not** contradict the mission in `mission.md`.
|
||||||
|
Mission always wins.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=1722763 log=/tmp/agent-coder-detach-20260722T052130Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
### ACTIVE (/home/vx/.am-agent-tasks/active)
|
||||||
|
|
||||||
|
(none active)
|
||||||
|
|
||||||
|
### RECENT HISTORY (5) (/home/vx/.am-agent-tasks/history)
|
||||||
|
|
||||||
|
════════════════════════════════════════════════════════════
|
||||||
|
id: 20260722T051649Z-1717777
|
||||||
|
status: interrupted [process dead]
|
||||||
|
kind: agent-coder
|
||||||
|
host: amanita
|
||||||
|
model: ollama/devstral:latest
|
||||||
|
session: ok2-rot1-devstral
|
||||||
|
pid / pgid: 1717646 / 1717475
|
||||||
|
started: 2026-07-22T05:16:49Z
|
||||||
|
stopped: 2026-07-22T05:21:20Z
|
||||||
|
elapsed: 4m 31s
|
||||||
|
stop_reason: hand off to am-meta
|
||||||
|
stop_how: am-task stop
|
||||||
|
stop_hard: False
|
||||||
24
openkknd2-am-coder/.am-meta/launch-20260722T052135Zcoder.log
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=1723577 log=/tmp/agent-coder-detach-20260722T052143Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
### ACTIVE (/home/vx/.am-agent-tasks/active)
|
||||||
|
|
||||||
|
════════════════════════════════════════════════════════════
|
||||||
|
id: 20260722T052138Z-1723242
|
||||||
|
status: running [process alive]
|
||||||
|
kind: agent-coder
|
||||||
|
host: amanita
|
||||||
|
model: ollama/devstral:latest
|
||||||
|
session: ok2meta-r1-devstral
|
||||||
|
pid / pgid: 1722763 / 1722243
|
||||||
|
started: 2026-07-22T05:21:38Z
|
||||||
|
stopped: — (still running)
|
||||||
|
elapsed: 6s
|
||||||
|
title: CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir).
|
||||||
|
|
||||||
|
Headless smoke is GREEN (map + units). You implement the next milestones yourself.
|
||||||
|
|
||||||
|
This turn must produce REAL file changes (not just plans):
|
||||||
|
1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed
|
||||||
|
2. Add a simple faction AI tick (gather / build / train)
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=1725651 log=/tmp/agent-coder-detach-20260722T052230Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
### ACTIVE (/home/vx/.am-agent-tasks/active)
|
||||||
|
|
||||||
|
(none active)
|
||||||
|
|
||||||
|
### RECENT HISTORY (5) (/home/vx/.am-agent-tasks/history)
|
||||||
|
|
||||||
|
════════════════════════════════════════════════════════════
|
||||||
|
id: 20260722T052152Z-1724054
|
||||||
|
status: done [process dead]
|
||||||
|
kind: agent-coder
|
||||||
|
host: amanita
|
||||||
|
model: ollama/qwen3-coder:latest
|
||||||
|
session: ok2meta-r2-coder
|
||||||
|
pid / pgid: 1723577 / 1722243
|
||||||
|
started: 2026-07-22T05:21:52Z
|
||||||
|
stopped: 2026-07-22T05:22:19Z
|
||||||
|
elapsed: 27s
|
||||||
|
exit_code: 0
|
||||||
|
stop_reason: completed
|
||||||
|
stop_how: finish
|
||||||
24
openkknd2-am-coder/.am-meta/launch-20260722T052858Zcoder.log
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=1737910 log=/tmp/agent-coder-detach-20260722T052901Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
### ACTIVE (/home/vx/.am-agent-tasks/active)
|
||||||
|
|
||||||
|
(none active)
|
||||||
|
|
||||||
|
### RECENT HISTORY (5) (/home/vx/.am-agent-tasks/history)
|
||||||
|
|
||||||
|
════════════════════════════════════════════════════════════
|
||||||
|
id: 20260722T052232Z-1725762
|
||||||
|
status: done [process dead]
|
||||||
|
kind: agent-coder
|
||||||
|
host: amanita
|
||||||
|
model: ollama/devstral:latest
|
||||||
|
session: ok2meta-r1-devstral
|
||||||
|
pid / pgid: 1725651 / 1725180
|
||||||
|
started: 2026-07-22T05:22:32Z
|
||||||
|
stopped: 2026-07-22T05:28:47Z
|
||||||
|
elapsed: 6m 15s
|
||||||
|
exit_code: 0
|
||||||
|
stop_reason: completed
|
||||||
|
stop_how: finish
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=2574247 log=/tmp/agent-coder-detach-20260722T053453Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
### ACTIVE (/home/vx/.am-agent-tasks/active)
|
||||||
|
|
||||||
|
(none active)
|
||||||
|
|
||||||
|
### RECENT HISTORY (5) (/home/vx/.am-agent-tasks/history)
|
||||||
|
|
||||||
|
════════════════════════════════════════════════════════════
|
||||||
|
id: 20260722T052904Z-1738030
|
||||||
|
status: done [process dead]
|
||||||
|
kind: agent-coder
|
||||||
|
host: amanita
|
||||||
|
model: ollama/qwen3-coder:latest
|
||||||
|
session: ok2meta-r2-coder
|
||||||
|
pid / pgid: 1737910 / 1725180
|
||||||
|
started: 2026-07-22T05:29:04Z
|
||||||
|
stopped: 2026-07-22T05:34:35Z
|
||||||
|
elapsed: 5m 31s
|
||||||
|
exit_code: 0
|
||||||
|
stop_reason: completed
|
||||||
|
stop_how: finish
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3217597 log=/tmp/agent-coder-detach-20260722T055024Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: WARN
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 12.2GiB/62.7GiB used (19%) avail 50.5GiB
|
||||||
|
load 9.86 13.58 10.98 cpus=32
|
||||||
|
swap 7.7GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 4987/6144 MiB (81%) util 0% high
|
||||||
|
ollama qwen3:14b bdbd181c33f2 12 GB 68%/32% CPU/GPU 32768 4 minutes from now
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T053455Z-2574378 done qwen3:14b 15m13s 207 ok2meta-r3-qwen3 ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T052904Z-1738030 done qwen3-coder:latest 5m31s 207 ok2meta-r2-coder ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T052232Z-1725762 done devstral:latest 6m15s 207 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3338342 log=/tmp/agent-coder-detach-20260722T055435Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.9GiB/62.7GiB used (14%) avail 53.8GiB
|
||||||
|
load 8.99 10.51 10.27 cpus=32
|
||||||
|
swap 7.7GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T055027Z-3217726 done qwen3:14b 4m04s 207 ok2meta-r4-qwen3 ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T053455Z-2574378 done qwen3:14b 15m13s 207 ok2meta-r3-qwen3 ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T052904Z-1738030 done qwen3-coder:latest 5m31s 207 ok2meta-r2-coder ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3346536 log=/tmp/agent-coder-detach-20260722T055907Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.9GiB/62.7GiB used (14%) avail 53.7GiB
|
||||||
|
load 7.76 8.74 9.51 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T055438Z-3338469 done qwen2.5-coder:14b 4m08s 207 ok2meta-r5-qwen1 ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T055027Z-3217726 done qwen3:14b 4m04s 207 ok2meta-r4-qwen3 ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T053455Z-2574378 done qwen3:14b 15m13s 207 ok2meta-r3-qwen3 ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3348289 log=/tmp/agent-coder-detach-20260722T055958Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.8GiB/62.7GiB used (14%) avail 53.9GiB
|
||||||
|
load 6.07 8.23 9.30 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T055909Z-3346665 done deepseek-coder:6.7b 29s 207 ok2meta-r6-deeps ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T055438Z-3338469 done qwen2.5-coder:14b 4m08s 207 ok2meta-r5-qwen1 ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T055027Z-3217726 done qwen3:14b 4m04s 207 ok2meta-r4-qwen3 ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3575225 log=/tmp/agent-coder-detach-20260722T060506Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.8GiB/62.7GiB used (14%) avail 53.9GiB
|
||||||
|
load 5.14 8.06 9.00 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T060000Z-3348413 interrupted qwen3:8b 3m57s 226 ok2meta-r7-qwen3 ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T055909Z-3346665 done deepseek-coder:6.7b 29s 226 ok2meta-r6-deeps ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T055438Z-3338469 done qwen2.5-coder:14b 4m08s 226 ok2meta-r5-qwen1 ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3577255 log=/tmp/agent-coder-detach-20260722T060718Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.5GiB/62.7GiB used (14%) avail 54.2GiB
|
||||||
|
load 1.46 5.59 7.96 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T060509Z-3575357 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060000Z-3348413 interrupted qwen3:8b 3m57s 226 ok2meta-r7-qwen3 ~/src/openkknd2-am-coder
|
||||||
|
CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w…
|
||||||
|
20260722T055909Z-3346665 done deepseek-coder:6.7b 29s 226 ok2meta-r6-deeps ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3578155 log=/tmp/agent-coder-detach-20260722T060748Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.6GiB/62.7GiB used (14%) avail 54.1GiB
|
||||||
|
load 1.39 5.18 7.75 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T060720Z-3577414 done devstral:latest 14s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060509Z-3575357 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060000Z-3348413 interrupted qwen3:8b 3m57s 226 ok2meta-r7-qwen3 ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3579768 log=/tmp/agent-coder-detach-20260722T060925Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.7GiB/62.7GiB used (14%) avail 54.0GiB
|
||||||
|
load 2.27 4.51 7.25 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T060750Z-3578279 interrupted devstral:latest 1m20s 226 ok2meta-r2-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060720Z-3577414 done devstral:latest 14s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060509Z-3575357 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3580679 log=/tmp/agent-coder-detach-20260722T061002Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.7GiB/62.7GiB used (14%) avail 54.0GiB
|
||||||
|
load 1.71 4.12 7.02 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T060928Z-3579895 interrupted devstral:latest 8s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060750Z-3578279 interrupted devstral:latest 1m20s 226 ok2meta-r2-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060720Z-3577414 done devstral:latest 14s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3581522 log=/tmp/agent-coder-detach-20260722T061032Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.7GiB/62.7GiB used (14%) avail 54.0GiB
|
||||||
|
load 1.62 3.87 6.84 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T061005Z-3580802 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060928Z-3579895 interrupted devstral:latest 8s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060750Z-3578279 interrupted devstral:latest 1m20s 226 ok2meta-r2-devst ~/src/openkknd2-am-coder
|
||||||
24
openkknd2-am-coder/.am-meta/launch-20260722T061559Zcoder.log
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=3590824 log=/tmp/agent-coder-detach-20260722T061603Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 8.9GiB/62.7GiB used (14%) avail 53.8GiB
|
||||||
|
load 10.51 8.21 7.68 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T061035Z-3581648 done devstral:latest 5m10s 226 ok2meta-r2-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T061005Z-3580802 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T060928Z-3579895 interrupted devstral:latest 8s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
24
openkknd2-am-coder/.am-meta/launch-20260722T062150Zcoder.log
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=424118 log=/tmp/agent-coder-detach-20260722T062153Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: WARN
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 10.0GiB/62.7GiB used (16%) avail 52.7GiB
|
||||||
|
load 5.82 6.64 7.07 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 5069/6144 MiB (83%) util 0% high
|
||||||
|
ollama qwen3-coder:latest 06c1097efce0 20 GB 80%/20% CPU/GPU 32768 4 minutes fro
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T061605Z-3590955 done qwen3-coder:latest 5m40s 270 ok2meta-r3-coder ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T061035Z-3581648 done devstral:latest 5m10s 270 ok2meta-r2-devst ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T061005Z-3580802 done devstral:latest 13s 270 ok2meta-r1-devst ~/src/openkknd2-am-coder
|
||||||
24
openkknd2-am-coder/.am-meta/launch-20260722T062640Zcoder.log
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=1588119 log=/tmp/agent-coder-detach-20260722T062643Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: WARN
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 9.9GiB/62.7GiB used (16%) avail 52.8GiB
|
||||||
|
load 7.55 7.23 7.16 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 5069/6144 MiB (83%) util 0% high
|
||||||
|
ollama qwen3-coder:latest 06c1097efce0 20 GB 80%/20% CPU/GPU 32768 4 minutes fro
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T062156Z-424245 done qwen3-coder:latest 4m40s 270 ok2meta-r4-coder ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T061605Z-3590955 done qwen3-coder:latest 5m40s 270 ok2meta-r3-coder ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T061035Z-3581648 done devstral:latest 5m10s 270 ok2meta-r2-devst ~/src/openkknd2-am-coder
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder
|
||||||
|
logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md
|
||||||
|
detached pid=2474402 log=/tmp/agent-coder-detach-20260722T063054Z.log
|
||||||
|
control: am-task list | am-task stop latest | am-task resume latest
|
||||||
|
am-task overview · host resources: OK
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
RAM 9.0GiB/62.7GiB used (14%) avail 53.7GiB
|
||||||
|
load 5.73 6.57 6.86 cpus=32
|
||||||
|
swap 7.8GiB/62.7GiB (12%)
|
||||||
|
GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0%
|
||||||
|
ollama (no chat model loaded)
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
● running ● done ● interrupted ● failed ● run/DEAD · am-task show <id> | list --full
|
||||||
|
|
||||||
|
ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE
|
||||||
|
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
ACTIVE (none)
|
||||||
|
|
||||||
|
RECENT HISTORY (8)
|
||||||
|
20260722T062646Z-1588248 done qwen3-coder:latest 3m50s 270 ok2meta-r5-coder ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T062156Z-424245 done qwen3-coder:latest 4m40s 270 ok2meta-r4-coder ~/src/openkknd2-am-coder
|
||||||
|
# MISSION (immutable — do not renegotiate) # Mission (immutable) — o…
|
||||||
|
20260722T061605Z-3590955 done qwen3-coder:latest 5m40s 270 ok2meta-r3-coder ~/src/openkknd2-am-coder
|
||||||
150
openkknd2-am-coder/.am-meta/meta.out
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T05:21:24Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38`
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
<stdin>:25: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
<stdin>:25: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `9 183 0`
|
||||||
|
- **2026-07-22T05:21:34Z** · after `9 183 0`
|
||||||
|
- **2026-07-22T05:21:34Z** · progress · NO with `--devstral` (streak_fail=1) — next model
|
||||||
|
<stdin>:25: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
<stdin>:25: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 2 / 16 · `--coder` · before `9 183 0`
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T05:22:25Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38`
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `9 183 0`
|
||||||
|
- **2026-07-22T05:28:56Z** · after `9 183 0`
|
||||||
|
- **2026-07-22T05:28:56Z** · progress · NO with `--devstral` (streak_fail=1) — next model
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 2 / 16 · `--coder` · before `9 183 0`
|
||||||
|
- **2026-07-22T05:34:48Z** · after `9 183 0`
|
||||||
|
- **2026-07-22T05:34:48Z** · progress · NO with `--coder` (streak_fail=2) — next model
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 3 / 16 · `--qwen314` · before `9 183 0`
|
||||||
|
- **2026-07-22T05:50:19Z** · after `10 208 2`
|
||||||
|
- **2026-07-22T05:50:19Z** · progress · YES with `--qwen314` — keep same model
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 4 / 16 · `--qwen314` · before `10 208 2`
|
||||||
|
- **2026-07-22T05:54:31Z** · after `10 208 2`
|
||||||
|
- **2026-07-22T05:54:31Z** · progress · NO with `--qwen314` (streak_fail=1) — next model
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 5 / 16 · `--qwen14` · before `10 208 2`
|
||||||
|
- **2026-07-22T05:59:02Z** · after `10 208 2`
|
||||||
|
- **2026-07-22T05:59:02Z** · progress · NO with `--qwen14` (streak_fail=2) — next model
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 6 / 16 · `--deepseek` · before `10 208 2`
|
||||||
|
- **2026-07-22T05:59:53Z** · after `10 208 2`
|
||||||
|
- **2026-07-22T05:59:53Z** · progress · NO with `--deepseek` (streak_fail=3) — next model
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 7 / 16 · `--qwen38` · before `10 208 2`
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T06:05:01Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard)
|
||||||
|
- stalls_before_switch: 2 accept_needs: 1
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:05:32Z** · after `11 228 2`
|
||||||
|
- **2026-07-22T06:05:32Z** · needs · harvested NEED_INSTRUCTIONS → additive.md (mission unchanged)
|
||||||
|
- **2026-07-22T06:05:32Z** · DONE detected after round 1
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T06:07:13Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard)
|
||||||
|
- stalls_before_switch: 2 accept_needs: 1
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:07:43Z** · after `11 228 2`
|
||||||
|
- **2026-07-22T06:07:43Z** · progress · NO with `--devstral` (streak_fail=1 < 2) — **retry same model**
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 2 / 16 · `--devstral` · before `11 228 2` · streak_fail=1
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T06:09:21Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard)
|
||||||
|
- stalls_before_switch: 2 accept_needs: 1
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0
|
||||||
|
|
||||||
|
## meta-start 2026-07-22T06:09:57Z
|
||||||
|
- workdir: `/home/vx/src/openkknd2-am-coder`
|
||||||
|
- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard)
|
||||||
|
- stalls_before_switch: 2 accept_needs: 1
|
||||||
|
- max_rounds: 16 max_turns/round: 8 min_line_delta: 30
|
||||||
|
- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:10:28Z** · after `11 228 2`
|
||||||
|
- **2026-07-22T06:10:28Z** · progress · NO with `--devstral` (streak_fail=1 < 2) — **retry same model**
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 2 / 16 · `--devstral` · before `11 228 2` · streak_fail=1
|
||||||
|
- **2026-07-22T06:15:58Z** · after `11 228 2`
|
||||||
|
- **2026-07-22T06:15:58Z** · progress · NO with `--devstral` (streak_fail=2 ≥ 2) — **switch model**
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 3 / 16 · `--coder` · before `11 228 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:21:48Z** · after `12 273 2`
|
||||||
|
- **2026-07-22T06:21:48Z** · progress · YES with `--coder` — keep same model; reset stall counter
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 4 / 16 · `--coder` · before `12 273 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:26:39Z** · after `12 273 2`
|
||||||
|
- **2026-07-22T06:26:39Z** · progress · NO with `--coder` (streak_fail=1 < 2) — **retry same model**
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 5 / 16 · `--coder` · before `12 273 2` · streak_fail=1
|
||||||
|
- **2026-07-22T06:30:49Z** · after `12 273 2`
|
||||||
|
- **2026-07-22T06:30:49Z** · progress · NO with `--coder` (streak_fail=2 ≥ 2) — **switch model**
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
/home/vx/src/openkknd2-am-coder/.am-meta/state.json
|
||||||
|
|
||||||
|
### round 6 / 16 · `--qwen314` · before `12 273 2` · streak_fail=0
|
||||||
|
- **2026-07-22T06:46:00Z** · stall · AGENT_LOG frozen ≥900s while task active → stop child
|
||||||
|
20260722T063056Z-2474530
|
||||||