Entropie git · main
Entropie festival game; first steps
C 88.5% Markdown 9.1% GLSL 2.3%git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gzentropie/src/game/game_dev.c raw
/*
* Dev menu (F1): pick anybody (you too) and change what they are, how they
* feel and who they are. Up / Down: the line (the list scrolls), Left / Right:
* change it.
*/
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "game/game_internal.h"
#include "world/injury.h"
#include "world/weather.h"
typedef enum {
DEV_WHO, DEV_TELEPORT, DEV_TIME, DEV_SPEED, DEV_WEATHER, DEV_WIND, DEV_START, DEV_FESTIVAL, DEV_BIGTREE, DEV_FEMALE, DEV_URINELLA, DEV_SPORTY, DEV_HURT, DEV_DRUNK, DEV_ENERGY,
DEV_BLADDER, DEV_HUNGER, DEV_THIRST, DEV_SLEEPY, DEV_STRENGTH, DEV_ANNOYED,
DEV_PRIDE, DEV_LIVELY, DEV_NERVES, DEV_CLUMSY, DEV_WITS, DEV_HEIGHTS, DEV_WILLING, DEV_COMMIT, DEV_LIKE, DEV_LOVE,
DEV_LIKES, /* likes for each job ... */
DEV_SKILLS = DEV_LIKES + 4, /* ... skill at each job */
DEV_ROWS = DEV_SKILLS + 4
} DevRow;
static const char *const JOB[4] = { "firewood", "trees", "trash", "fire" };
#define CREW_PAGES 3
/* The people (you first), and the one picked. */
static int people(const Game *game, Object **out, int max) {
int n = 0;
if (game->player && n < max) out[n++] = game->player;
for (int i = 0; i < game->world.count && n < max; i++) {
Object *o = game->world.objects[i];
if (o->is_character && o->type == OBJ_HUMAN) out[n++] = o;
}
return n;
}
static Object *picked(const Game *game) {
Object *list[32];
int n = people(game, list, 32);
if (n == 0) return NULL;
int k = ((game->dev_who % n) + n) % n;
return list[k];
}
static float step01(float v, int dir, float step) {
return fminf(1.0f, fmaxf(0.0f, v + (float)dir * step));
}
static float stepSigned(float v, int dir, float step) {
return fminf(1.0f, fmaxf(-1.0f, v + (float)dir * step));
}
/* Dev only: you to the parkour (Right) or back to the yard (Left). */
static void teleport(Game *game, int dir) {
Object *p = game->player;
Vec3 to, along;
if (dir > 0) Site_courseFrame(&to, &along);
else { to = Site_yard(); along = vec3_normalize(vec3_sub(Site_landmark(), to)); }
if (p->on_ladder) Ladder_getOff(p);
for (int h = 0; h < 2; h++) if (p->hands.state[h] != HAND_FREE) Hands_drop(p, h);
Hands_release(p);
p->climbing = p->sitting = false;
p->position = vec3(to.x, World_groundHeight(&game->world, to.x, to.z, to.y + 50.0f) + 0.05f, to.z);
p->velocity = p->movement = vec3_zero();
p->rotation.y = atan2f(along.z, along.x) + PI_F / 2.0f;
p->body.replant = true;
Object_syncColliders(p);
game->camera.yaw = atan2f(along.z, along.x) * 180.0f / PI_F;
game->camera.pitch = 0.0f;
Camera_rotate(&game->camera, 0.0f, 0.0f);
say(game, dir > 0 ? "Teleported to the parkour" : "Teleported to the yard");
}
/* Sporty changes top speed and jump too (as when the person was made). */
static void setFitness(Object *o, float fit) {
float was = 1.0f + 0.25f * (o->fitness - 0.5f), now = 1.0f + 0.25f * (fit - 0.5f);
float jwas = 1.0f + 0.15f * (o->fitness - 0.5f), jnow = 1.0f + 0.15f * (fit - 0.5f);
o->top_speed *= now / was;
o->jump_speed *= jnow / jwas;
o->fitness = fit;
}
void game_dev(Game *game, const Input *input) {
if (input->crew_menu) { game->show_crew = !game->show_crew; if (game->show_crew) game->show_dev = false; }
if (game->show_crew && input->page) { game->crew_page = (game->crew_page + input->page + CREW_PAGES) % CREW_PAGES; return; }
if (input->dev_menu) { game->show_dev = !game->show_dev; if (game->show_dev) game->show_crew = false; }
if (!game->show_dev) return;
if (input->menu_move) game->dev_row = (game->dev_row + input->menu_move + DEV_ROWS) % DEV_ROWS;
int dir = input->page;
if (!dir) return;
if (game->dev_row == DEV_WHO) {
game->dev_who += dir;
return;
}
if (game->dev_row == DEV_TELEPORT) {
teleport(game, dir);
return;
}
if (game->dev_row == DEV_TIME) { /* an hour on or back */
float day = 24.0f * 3600.0f;
game->daycycle.seconds = fmodf(game->daycycle.seconds + (float)dir * 3600.0f + day, day);
return;
}
if (game->dev_row == DEV_SPEED) {
static const float SPEEDS[] = { 1.0f, 3.0f, 10.0f, 30.0f, 60.0f, 120.0f };
int k = 0;
for (int i = 0; i < 6; i++) if (fabsf(game->daycycle.time_scale - SPEEDS[i]) < fabsf(game->daycycle.time_scale - SPEEDS[k])) k = i;
k = k + dir < 0 ? 0 : k + dir > 5 ? 5 : k + dir;
game->daycycle.time_scale = SPEEDS[k];
return;
}
if (game->dev_row == DEV_WEATHER) {
game->world.weather = (game->world.weather + dir + WEATHER_KINDS) % WEATHER_KINDS;
if (game->world.weather == WEATHER_AUTO) game->world.rain_left = 0.0f; /* (on its own again: from now) */
return;
}
if (game->dev_row == DEV_WIND) {
game->world.wind_kind = (game->world.wind_kind + dir + WIND_KINDS) % WIND_KINDS;
if (game->world.wind_kind == WIND_AUTO) game->world.wind_left = 0.0f; /* (on its own again: from now) */
return;
}
if (game->dev_row == DEV_START) {
if (dir > 0) game_skipArrival(game);
return;
}
if (game->dev_row == DEV_BIGTREE) {
if (dir > 0) game_bigTreeJump(game);
return;
}
if (game->dev_row == DEV_FESTIVAL) {
if (dir > 0) game_festivalJump(game);
return;
}
Object *o = picked(game);
if (!o) return;
Needs *n = &o->needs;
Character *c = &o->ai.character;
int row = game->dev_row;
if (row >= DEV_LIKES && row < DEV_SKILLS) {
float *like = &c->likes[ORDER_FIREWOOD + row - DEV_LIKES];
*like = stepSigned(*like, dir, 0.1f);
return;
}
if (row >= DEV_SKILLS && row < DEV_ROWS) {
float *skill = &o->skill[ORDER_FIREWOOD + row - DEV_SKILLS];
*skill = step01(*skill, dir, 0.1f);
return;
}
switch ((DevRow)row) {
case DEV_FEMALE: o->female = !o->female; break;
case DEV_URINELLA: o->urinella = !o->urinella; o->urinellas = o->urinella ? POCKET_URINELLAS : 0; break;
case DEV_SPORTY: setFitness(o, step01(o->fitness, dir, 0.05f)); break;
case DEV_HURT: game_injuryHeal(game, o); break;
case DEV_DRUNK: o->drunk = fminf(1.5f, fmaxf(0.0f, o->drunk + (float)dir * 0.1f)); break;
case DEV_ENERGY: n->stamina = step01(n->stamina, dir, 0.1f); break;
case DEV_BLADDER: n->bladder = step01(n->bladder, dir, 0.1f); break;
case DEV_HUNGER: n->hunger = step01(n->hunger, dir, 0.1f); break;
case DEV_THIRST: n->thirst = step01(n->thirst, dir, 0.1f); break;
case DEV_SLEEPY: n->sleepiness = step01(n->sleepiness, dir, 0.1f); break;
case DEV_STRENGTH: o->strength = fminf(200.0f, fmaxf(30.0f, o->strength + (float)dir * 10.0f)); break;
case DEV_ANNOYED: o->ai.annoyed = step01(o->ai.annoyed, dir, 0.1f); break;
case DEV_PRIDE: o->temperament.pride = stepSigned(o->temperament.pride, dir, 0.1f); break;
case DEV_LIVELY: o->temperament.energy = stepSigned(o->temperament.energy, dir, 0.1f); break;
case DEV_NERVES: o->temperament.nerves = step01(o->temperament.nerves, dir, 0.1f); break;
case DEV_CLUMSY: o->body.quirk[3] = stepSigned(o->body.quirk[3], dir, 0.2f); break; /* (clumsiness = 0.5 + 0.5 x this) */
case DEV_WITS: o->wits = step01(o->wits, dir, 0.05f); break;
case DEV_HEIGHTS: o->height_fear = step01(o->height_fear, dir, 0.05f); break;
case DEV_WILLING: c->willing = step01(c->willing, dir, 0.1f); break;
case DEV_COMMIT: o->commitment = step01(o->commitment, dir, 0.1f); break;
case DEV_LIKE: o->like = step01(o->like, dir, 0.1f); break;
case DEV_LOVE: o->love = step01(o->love, dir, 0.1f); break;
default: break;
}
}
/* One line of the menu. */
static void devLine(const Game *game, const Object *o, int row, char *out) {
const Needs *n = &o->needs;
const Character *c = &o->ai.character;
if (row >= DEV_LIKES && row < DEV_SKILLS) {
snprintf(out, 96, "Likes %s: %+.1f", JOB[row - DEV_LIKES], c->likes[ORDER_FIREWOOD + row - DEV_LIKES]);
return;
}
if (row >= DEV_SKILLS && row < DEV_ROWS) {
snprintf(out, 96, "Skill %s: %.0f%%", JOB[row - DEV_SKILLS], o->skill[ORDER_FIREWOOD + row - DEV_SKILLS] * 100.0f);
return;
}
switch ((DevRow)row) {
case DEV_WHO: snprintf(out, 96, "Who: %s%s", o->name, o->type == OBJ_PLAYER ? " (you)" : ""); break;
case DEV_TELEPORT: snprintf(out, 96, "Teleport you: <- yard parkour ->"); break;
case DEV_TIME: {
int m = (int)(game->daycycle.seconds / 60.0f);
snprintf(out, 96, "Time: %02d:%02d (an hour back / on)", (m / 60) % 24, m % 60);
break;
}
case DEV_SPEED: snprintf(out, 96, "Time runs: %.0fx", game->daycycle.time_scale); break;
case DEV_WEATHER: snprintf(out, 96, "Weather: %s (raining %.0f%%, wet %.0f%%)", Weather_name((WeatherKind)game->world.weather),
game->world.rain * 100.0f, game->world.wet * 100.0f); break;
case DEV_WIND: snprintf(out, 96, "Wind: %s (%.0f m/s)", Weather_windName((WindKind)game->world.wind_kind), Weather_windSpeed(&game->world)); break;
case DEV_BIGTREE: snprintf(out, 96, "The big tree: %s (on ->)", game_bigTreeStatus(game)); break;
case DEV_FESTIVAL: snprintf(out, 96, "The festival: %s", game_festivalJumpText(game)); break;
case DEV_START: snprintf(out, 96, "The start: %s", game->arrival.task == TASK_NONE && game->arrival.stage != ARRIVAL_DRIVING ? "done" : "skip it ->"); break;
case DEV_FEMALE: snprintf(out, 96, "Female: %s", o->female ? "yes" : "no"); break;
case DEV_URINELLA: snprintf(out, 96, "Urinella: %s (%d left)", o->urinella ? "yes" : "no", o->urinellas); break;
case DEV_SPORTY: snprintf(out, 96, "Sporty: %.2f", o->fitness); break;
case DEV_HURT: {
char what[56];
if (o->injured) snprintf(what, sizeof what, "a broken %s (lying there)", o->injured == INJ_ARM ? "arm" : "leg");
else if (o->hospital_until > 0.0f) snprintf(what, sizeof what, "in the hospital");
else if (o->look.cast) snprintf(what, sizeof what, "%s in a cast", Injury_castKind(o) == INJ_ARM ? "arm" : "leg");
else snprintf(what, sizeof what, "%s", o->sprain > 0.0f || o->scrapes ? "" : "nothing");
size_t len = strlen(what);
if (o->sprain > 0.0f) snprintf(what + len, sizeof what - len, "%sa twisted ankle (%.0f h)", len ? ", " : "", (double)(o->sprain / 3600.0f));
len = strlen(what);
if (o->scrapes) snprintf(what + len, sizeof what - len, "%s%d scrapes", len ? ", " : "", o->scrapes);
snprintf(out, 96, "Hurt: %s%s", what, strcmp(what, "nothing") ? " (<- / ->: heal)" : "");
break;
}
case DEV_DRUNK: snprintf(out, 96, "Drunk: %.1f", o->drunk); break;
case DEV_ENERGY: snprintf(out, 96, "Energy: %.0f%%", n->stamina * 100.0f); break;
case DEV_BLADDER: snprintf(out, 96, "Bladder: %.0f%%", n->bladder * 100.0f); break;
case DEV_HUNGER: snprintf(out, 96, "Hunger: %.0f%%", n->hunger * 100.0f); break;
case DEV_THIRST: snprintf(out, 96, "Thirst: %.0f%%", n->thirst * 100.0f); break;
case DEV_SLEEPY: snprintf(out, 96, "Sleepy: %.0f%%", n->sleepiness * 100.0f); break;
case DEV_STRENGTH: snprintf(out, 96, "Strength: %.0f", o->strength); break;
case DEV_ANNOYED: snprintf(out, 96, "Annoyed: %.0f%%", o->ai.annoyed * 100.0f); break;
case DEV_PRIDE: snprintf(out, 96, "Pride (shy .. proud): %+.1f", o->temperament.pride); break;
case DEV_LIVELY: snprintf(out, 96, "Lively (tired .. lively): %+.1f", o->temperament.energy); break;
case DEV_NERVES: snprintf(out, 96, "Nerves (calm .. restless): %.1f", o->temperament.nerves); break;
case DEV_CLUMSY: snprintf(out, 96, "Clumsy: %.0f%%", (0.5f + 0.5f * o->body.quirk[3]) * 100.0f); break;
case DEV_WITS: snprintf(out, 96, "Wits: %.2f", o->wits); break;
case DEV_HEIGHTS: snprintf(out, 96, "Afraid of heights: %.2f", o->height_fear); break;
case DEV_WILLING: snprintf(out, 96, "Willing: %.0f%%", c->willing * 100.0f); break;
case DEV_COMMIT: snprintf(out, 96, "Into the festival (commitment): %.0f%%", (double)(o->commitment * 100.0f)); break;
case DEV_LIKE: snprintf(out, 96, "Likes you: %.0f%% (the place's awareness %.0f%%)", (double)(o->like * 100.0f), (double)(game->world.aware * 100.0f)); break;
case DEV_LOVE: snprintf(out, 96, "Drawn to you (love): %.0f%%", (double)(o->love * 100.0f)); break;
default: out[0] = '\0'; break;
}
}
int Game_devLines(const Game *game, char lines[][96], int max, int *chosen) {
Object *o = picked(game);
if (!o || max < 2) return 0;
int shown = max - 1; /* (a window onto the list, around the line picked) */
int first = game->dev_row - shown / 2;
if (first > DEV_ROWS - shown) first = DEV_ROWS - shown;
if (first < 0) first = 0;
snprintf(lines[0], 96, "Dev (F1 closes; Up / Down: line %d/%d, Left / Right: change)", game->dev_row + 1, DEV_ROWS);
int n = 1;
for (int row = first; row < DEV_ROWS && n < max; row++) {
devLine(game, o, row, lines[n]);
if (row == game->dev_row) *chosen = n;
n++;
}
return n;
}