Nimbin[12]?Games / Entropie / entropie/src/world/cast.c

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

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.gz
entropie/src/world/cast.c 17.3 KB · 209 lines raw
#include "world/cast.h"

#include <stdio.h>

/* Who they are: likes (firewood, trees, trash, fire), how willing, skills, a few words; tempo, thinking it over, breaks. */
#define SKILLS(wood, trees, trash, fire) \
    { [ORDER_FIREWOOD] = wood, [ORDER_TREES] = trees, [ORDER_TRASH] = trash, [ORDER_FIRE] = fire }
#define LOOK(sr, sg, sb, hr, hg, hb, tr, tg, tb, pr, pg, pb, fr, fg, fb, tall, hairdo, sleeves) \
    { .skin = { sr, sg, sb }, .hair = { hr, hg, hb }, .shirt = { tr, tg, tb }, .pants = { pr, pg, pb }, .shoes = { fr, fg, fb }, \
      .height = tall, .hair_style = hairdo, .long_sleeves = sleeves }

static const CastMember CAST[] = {
    { .name = "You", .temperament = { .pride = 0.0f, .energy = 0.2f, .nerves = 0.1f },
      .look = LOOK(0.93f, 0.74f, 0.60f, 0.30f, 0.19f, 0.12f, 0.86f, 0.42f, 0.32f, 0.24f, 0.30f, 0.48f, 0.22f, 0.16f, 0.13f, 1.78f, HAIR_SHORT, false),
      .character = { { 0 }, 1.0f, SKILLS(0.3f, 0.3f, 0.3f, 0.3f), NULL, 1.0f, 0.0f, BREAK_NONE, 0.0f, 0.0f },
      .kg = 75, .age = 28, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true, .filter_habit = true, .pocket_tray = true,
      .smoker = 0.3f, .wits = 0.6f, .height_fear = 0.2f, .worn = 0.15f, .fitness = 0.5f, .tolerance = 1.0f,
      .tobacco = POUCH_ROLLUPS, .papers = PAPERS_PACK, .filters = FILTERS_BAG,
      .role = "the web things with Volkram; helps or takes over wherever needed", .leads = PROJECT_WEB, .helps = PROJECT_NONE, .commitment = 1.0f },
    { .name = "Volkram", .temperament = { .pride = 0.5f, .energy = 0.1f, .nerves = 0.0f },
      .look = LOOK(0.90f, 0.70f, 0.56f, 0.42f, 0.30f, 0.18f, 0.36f, 0.46f, 0.36f, 0.30f, 0.27f, 0.24f, 0.25f, 0.18f, 0.12f, 1.86f, HAIR_LONG, true),
      .character = { SKILLS(0.8f, 0.3f, 0.0f, 0.6f), 0.7f, SKILLS(0.6f, 0.4f, 0.3f, 0.6f),
                     "Volkram. Jana and I share a tent. Splitting wood clears my head.", 1.0f, 0.8f, BREAK_COFFEE, 90.0f, 30.0f },
      .kg = 88, .age = 31, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true, .filter_habit = true,
      .smoker = 0.6f, .wits = 0.7f, .height_fear = 0.15f, .worn = 0.2f, .fitness = 0.55f, .tolerance = 1.2f,
      .tobacco = 40, .papers = 20, .filters = 60, .keys = "Car keys",
      .role = "leads the festival organisation; the web things with you", .leads = PROJECT_ORGA, .helps = PROJECT_WEB, .commitment = 0.95f },
    { .name = "Jana", .temperament = { .pride = 0.1f, .energy = 0.7f, .nerves = 0.3f },
      .look = LOOK(0.96f, 0.80f, 0.68f, 0.70f, 0.36f, 0.20f, 0.92f, 0.78f, 0.36f, 0.42f, 0.30f, 0.46f, 0.85f, 0.82f, 0.74f, 1.66f, HAIR_LONG, false),
      .character = { SKILLS(0.0f, -0.5f, 0.6f, 0.9f), 0.8f, SKILLS(0.3f, 0.2f, 0.5f, 0.6f),
                     "Jana! I'm here for the fire and the people. And I like it tidy.", 1.1f, 0.5f, BREAK_CHAT, 70.0f, 30.0f },
      .kg = 60, .age = 29, .hand = HAND_LEFT, .garment = GARMENT_SKIRT, .female = true, .urinella = true, .filter_habit = true, .pocket_tray = true,
      .smoker = 0.2f, .wits = 0.65f, .height_fear = 0.5f, .worn = 0.1f, .fitness = 0.7f, .tolerance = 0.8f,
      .tobacco = 10, .papers = 5,
      .role = "one of the crew (her task not settled yet)", .leads = PROJECT_NONE, .helps = PROJECT_DECORATION, .commitment = 0.75f },
    { .name = "Toni", .temperament = { .pride = -0.3f, .energy = 0.4f, .nerves = 0.6f },
      .look = LOOK(0.72f, 0.52f, 0.38f, 0.12f, 0.10f, 0.10f, 0.30f, 0.62f, 0.60f, 0.55f, 0.48f, 0.36f, 0.32f, 0.22f, 0.16f, 1.74f, HAIR_SHORT, false),
      .character = { SKILLS(0.4f, 0.6f, -0.4f, 0.3f), 0.5f, SKILLS(0.4f, 0.4f, 0.2f, 0.3f),
                     "Toni. First time out here. Show me how and I'll do it.", 1.2f, 0.3f, BREAK_WALK, 80.0f, 25.0f },
      .kg = 70, .age = 26, .hand = HAND_RIGHT, .garment = GARMENT_SHORTS, .lighter = true, .filter_habit = true,
      .smoker = 0.7f, .wits = 0.5f, .height_fear = 0.1f, .worn = 0.35f, .fitness = 0.8f, .tolerance = 0.9f,
      .tobacco = 25, .papers = 0, .filters = 15,
      .role = "artist organisation with Rouven; builds along; mows the Kartoffelacker with the scythe", .leads = PROJECT_MOWING, .helps = PROJECT_ARTISTS,
      .commitment = 0.8f },
    { .name = "Kurti", .temperament = { .pride = 0.3f, .energy = -0.6f, .nerves = 0.0f },
      .look = { .skin = { 0.90f, 0.72f, 0.62f }, .hair = { 0.82f, 0.82f, 0.80f }, .shirt = { 0.55f, 0.50f, 0.42f }, .pants = { 0.33f, 0.36f, 0.30f },
                .shoes = { 0.20f, 0.16f, 0.12f }, .height = 1.70f, .hair_style = HAIR_SHORT, .long_sleeves = true, .belly = 1.0f, .keeps_shirt = true },
      .character = { SKILLS(0.6f, 0.7f, 0.2f, 0.8f), 0.4f, SKILLS(0.8f, 0.9f, 0.4f, 0.8f),
                     "Kurti. This is my house - you're welcome. I've felled more trees than you've seen.", 0.7f, 2.2f, BREAK_SIT, 45.0f, 50.0f },
      .kg = 72, .age = 74, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true, .lives_here = true,
      .smoker = 0.8f, .wits = 0.75f, .height_fear = 0.7f, .worn = 0.4f, .fitness = 0.12f, .tolerance = 1.4f,
      .tobacco = 60, .papers = 30, .keys = "House keys",
      .role = "the farm is his: the host", .leads = PROJECT_NONE, .helps = PROJECT_NONE, .commitment = 0.6f },
    { .name = "Martin", .temperament = { .pride = 0.8f, .energy = 0.3f, .nerves = 0.1f },
      .look = LOOK(0.88f, 0.66f, 0.50f, 0.30f, 0.22f, 0.16f, 0.20f, 0.24f, 0.40f, 0.24f, 0.26f, 0.30f, 0.18f, 0.14f, 0.12f, 1.84f, HAIR_SHORT, false),
      .character = { SKILLS(0.4f, 0.9f, -0.6f, 0.3f), 0.5f, SKILLS(0.7f, 0.8f, 0.2f, 0.4f),
                     "Martin. Kurti's eldest. Give me a chainsaw, not a broom.", 1.2f, 0.2f, BREAK_NONE, 0.0f, 0.0f },
      .kg = 92, .age = 30, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true, .lives_here = true,
      .smoker = 0.4f, .wits = 0.55f, .height_fear = 0.05f, .worn = 0.2f, .fitness = 0.6f, .tolerance = 1.3f, .cigarettes = 12,
      .role = "Kurti's eldest: one of the hosts", .leads = PROJECT_NONE, .helps = PROJECT_NONE, .commitment = 0.5f },
    { .name = "Jonas", .temperament = { .pride = -0.1f, .energy = 0.6f, .nerves = 0.2f },
      .look = LOOK(0.90f, 0.70f, 0.55f, 0.36f, 0.26f, 0.18f, 0.80f, 0.20f, 0.18f, 0.50f, 0.46f, 0.38f, 0.60f, 0.24f, 0.20f, 1.80f, HAIR_SHORT, false),
      .character = { SKILLS(0.6f, 0.4f, 0.3f, 0.5f), 0.85f, SKILLS(0.4f, 0.4f, 0.4f, 0.4f),
                     "Jonas. The younger one. Dad says I talk too much.", 1.15f, 0.4f, BREAK_CHAT, 80.0f, 30.0f },
      .kg = 76, .age = 25, .hand = HAND_LEFT, .garment = GARMENT_SHORTS, .lives_here = true,
      .smoker = 0.0f, .wits = 0.45f, .height_fear = 0.2f, .worn = 0.1f, .fitness = 0.85f, .tolerance = 1.0f,
      .role = "Kurti's younger son: one of the hosts", .leads = PROJECT_NONE, .helps = PROJECT_NONE, .commitment = 0.6f },
    /* ----- the crew, turning up over the first days ----- */
    { .name = "Kilian", .temperament = { .pride = 0.3f, .energy = 0.5f, .nerves = 0.2f },
      .look = LOOK(0.92f, 0.73f, 0.60f, 0.55f, 0.40f, 0.22f, 0.48f, 0.24f, 0.58f, 0.18f, 0.18f, 0.20f, 0.30f, 0.26f, 0.22f, 1.83f, HAIR_LONG, false),
      .character = { SKILLS(0.6f, 0.4f, 0.0f, 0.5f), 0.6f, SKILLS(0.6f, 0.5f, 0.3f, 0.5f),
                     "Kilian. The Obstwiese is going to be a psy floor like you've never seen. Give me wood and a week.", 1.1f, 0.6f, BREAK_COFFEE, 100.0f, 25.0f },
      .kg = 78, .age = 33, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true,
      .smoker = 0.5f, .wits = 0.7f, .height_fear = 0.1f, .worn = 0.25f, .fitness = 0.65f, .tolerance = 1.0f,
      .tobacco = 50, .papers = 30, .filters = 40,
      .role = "builds the Obstwiese stage (psytrance)", .leads = PROJECT_OBSTWIESE, .helps = PROJECT_HOFBUEHNE, .commitment = 0.9f, .arrives = 2.0f },
    { .name = "Julia", .temperament = { .pride = 0.2f, .energy = 0.8f, .nerves = 0.1f },
      .look = LOOK(0.94f, 0.78f, 0.66f, 0.20f, 0.14f, 0.10f, 0.36f, 0.52f, 0.30f, 0.30f, 0.30f, 0.34f, 0.40f, 0.30f, 0.20f, 1.70f, HAIR_LONG, false),
      .character = { SKILLS(0.6f, 0.4f, 0.4f, 0.4f), 0.95f, SKILLS(0.7f, 0.6f, 0.7f, 0.6f),
                     "Julia. Tell me what needs doing - building's what I like best.", 1.35f, 0.2f, BREAK_NONE, 0.0f, 0.0f },
      .kg = 62, .age = 27, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .female = true, .filter_habit = true,
      .smoker = 0.0f, .wits = 0.8f, .height_fear = 0.05f, .worn = 0.1f, .fitness = 0.75f, .tolerance = 0.9f,
      .role = "quick at every job, very good at building: wherever she's needed", .leads = PROJECT_NONE, .helps = PROJECT_HOFBUEHNE, .commitment = 0.9f,
      .arrives = 2.0f },
    { .name = "Stella", .temperament = { .pride = 0.1f, .energy = 0.5f, .nerves = 0.3f },
      .look = LOOK(0.95f, 0.78f, 0.66f, 0.85f, 0.62f, 0.30f, 0.90f, 0.50f, 0.66f, 0.30f, 0.50f, 0.52f, 0.70f, 0.60f, 0.40f, 1.68f, HAIR_LONG, false),
      .character = { SKILLS(-0.3f, -0.4f, 0.4f, 0.5f), 0.7f, SKILLS(0.2f, 0.1f, 0.5f, 0.4f),
                     "Stella. Doro and I do the art - and the snails, they're protected, you know.", 1.0f, 0.7f, BREAK_CHAT, 70.0f, 35.0f },
      .kg = 58, .age = 30, .hand = HAND_RIGHT, .garment = GARMENT_DRESS, .female = true, .urinella = true, .filter_habit = true, .pocket_tray = true,
      .smoker = 0.3f, .wits = 0.7f, .height_fear = 0.4f, .worn = 0.1f, .fitness = 0.6f, .tolerance = 0.8f,
      .tobacco = 20, .papers = 15, .filters = 30,
      .role = "art and decoration with Doro; the snail rescue", .leads = PROJECT_DECORATION, .helps = PROJECT_SNAILS, .commitment = 0.85f, .arrives = 5.0f },
    { .name = "Doro", .temperament = { .pride = 0.0f, .energy = 0.3f, .nerves = 0.2f },
      .look = LOOK(0.88f, 0.66f, 0.52f, 0.25f, 0.16f, 0.12f, 0.30f, 0.30f, 0.56f, 0.58f, 0.40f, 0.30f, 0.28f, 0.22f, 0.18f, 1.72f, HAIR_SHORT, true),
      .character = { SKILLS(-0.2f, -0.3f, 0.5f, 0.4f), 0.75f, SKILLS(0.2f, 0.1f, 0.5f, 0.4f),
                     "Doro. Paint, fabric, lights in the trees - and nobody steps on a snail.", 0.95f, 0.8f, BREAK_SIT, 80.0f, 35.0f },
      .kg = 64, .age = 32, .hand = HAND_LEFT, .garment = GARMENT_TROUSERS, .female = true, .lighter = true,
      .smoker = 0.0f, .wits = 0.75f, .height_fear = 0.3f, .worn = 0.15f, .fitness = 0.55f, .tolerance = 0.9f,
      .role = "leads the snail rescue; art and decoration with Stella", .leads = PROJECT_SNAILS, .helps = PROJECT_DECORATION, .commitment = 0.85f,
      .arrives = 5.0f },
    { .name = "Kerry", .temperament = { .pride = 0.4f, .energy = 0.0f, .nerves = 0.0f },
      .look = LOOK(0.86f, 0.64f, 0.48f, 0.50f, 0.42f, 0.36f, 0.34f, 0.30f, 0.26f, 0.26f, 0.30f, 0.22f, 0.26f, 0.20f, 0.14f, 1.79f, HAIR_LONG, true),
      .character = { SKILLS(0.6f, 0.9f, -0.5f, 0.6f), 0.5f, SKILLS(0.8f, 0.9f, 0.2f, 0.6f),
                     "Kerry. Give me a log and a chainsaw and I'll find what's in it.", 0.9f, 1.2f, BREAK_COFFEE, 120.0f, 40.0f },
      .kg = 84, .age = 41, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true,
      .smoker = 0.6f, .wits = 0.7f, .height_fear = 0.1f, .worn = 0.2f, .fitness = 0.6f, .tolerance = 1.3f, .cigarettes = 8,
      .role = "wood art with the chainsaw (needs logs and the chainsaw)", .leads = PROJECT_WOOD_ART, .helps = PROJECT_DECORATION, .commitment = 0.7f,
      .arrives = 20.0f },
    { .name = "Rouven", .temperament = { .pride = 0.3f, .energy = 0.4f, .nerves = 0.3f },
      .look = LOOK(0.93f, 0.75f, 0.62f, 0.24f, 0.18f, 0.12f, 0.20f, 0.20f, 0.22f, 0.20f, 0.22f, 0.28f, 0.82f, 0.82f, 0.80f, 1.81f, HAIR_SHORT, false),
      .character = { SKILLS(0.0f, -0.3f, 0.2f, 0.6f), 0.6f, SKILLS(0.3f, 0.2f, 0.4f, 0.5f),
                     "Rouven. Toni and I do the artists - who plays when, and that they're happy.", 1.0f, 0.6f, BREAK_CHAT, 70.0f, 30.0f },
      .kg = 74, .age = 29, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true, .filter_habit = true,
      .smoker = 0.4f, .wits = 0.75f, .height_fear = 0.3f, .worn = 0.15f, .fitness = 0.5f, .tolerance = 1.0f,
      .tobacco = 30, .papers = 20, .filters = 50,
      .role = "artist organisation with Toni", .leads = PROJECT_ARTISTS, .helps = PROJECT_HOFBUEHNE, .commitment = 0.8f, .arrives = 26.0f },
    { .name = "Pascal", .temperament = { .pride = 0.2f, .energy = 0.2f, .nerves = 0.1f },
      .look = LOOK(0.90f, 0.70f, 0.56f, 0.18f, 0.14f, 0.12f, 0.22f, 0.22f, 0.24f, 0.26f, 0.28f, 0.34f, 0.20f, 0.18f, 0.16f, 1.77f, HAIR_SHORT, true),
      .character = { SKILLS(0.3f, 0.2f, 0.0f, 0.3f), 0.7f, SKILLS(0.5f, 0.3f, 0.3f, 0.4f),
                     "Pascal. Light is what I do - and the main stage gets a proper floor.", 1.05f, 0.9f, BREAK_COFFEE, 110.0f, 30.0f },
      .kg = 80, .age = 35, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true,
      .smoker = 0.2f, .wits = 0.8f, .height_fear = 0.05f, .worn = 0.15f, .fitness = 0.55f, .tolerance = 1.1f, .cigarettes = 6,
      .role = "the light, and a proper floor for the main stage", .leads = PROJECT_LIGHT, .helps = PROJECT_HOFBUEHNE, .commitment = 0.85f,
      .arrives = 26.0f },
    { .name = "Enrico", .temperament = { .pride = 0.4f, .energy = 0.3f, .nerves = -0.2f },
      .look = LOOK(0.84f, 0.62f, 0.46f, 0.15f, 0.12f, 0.10f, 0.70f, 0.42f, 0.16f, 0.26f, 0.30f, 0.22f, 0.30f, 0.22f, 0.14f, 1.83f, HAIR_LONG, false),
      .character = { SKILLS(0.6f, 0.9f, -0.2f, 0.4f), 0.6f, SKILLS(0.7f, 0.95f, 0.3f, 0.5f),
                     "Enrico, Go-Darmstadt - the geo-dome lot. Trees? I've felled bigger ones than that.", 1.05f, 1.2f, BREAK_SIT, 100.0f, 30.0f },
      .kg = 86, .age = 38, .hand = HAND_RIGHT, .garment = GARMENT_TROUSERS, .lighter = true, .far_away = true,
      .smoker = 0.5f, .wits = 0.7f, .height_fear = 0.0f, .worn = 0.3f, .fitness = 0.7f, .tolerance = 1.3f, .cigarettes = 14,
      .role = "Go-Darmstadt (the geo-dome); fells the big old tree away from the phone line", .leads = PROJECT_BIG_TREE, .helps = PROJECT_KARTOFFELACKER,
      .commitment = 0.85f, .arrives = 21.0f },
};
#define CAST_COUNT ((int)(sizeof(CAST) / sizeof(CAST[0])))

int Cast_count(void) { return CAST_COUNT; }

const CastMember *Cast_get(int id) { return id >= 0 && id < CAST_COUNT ? &CAST[id] : NULL; }

Object *Cast_create(int id, Vec3 at) {
    const CastMember *c = Cast_get(id);
    if (!c) return NULL;
    Object *obj = Object_create(id == 0 ? OBJ_PLAYER : OBJ_HUMAN, PALETTE_MODERN, at, vec3(0.5f, 1.75f, 0.25f), c->kg);
    if (!obj) return NULL;
    obj->cast_id     = id;
    obj->temperament = c->temperament;
    obj->tolerance   = c->tolerance;
    obj->fitness     = c->fitness;
    FigureLook worn = Figure_wardrobe(id); /* (the jacket, its hood and a hat from the wardrobe; the rest their own) */
    obj->look = c->look;
    obj->look.jacket = worn.jacket;
    obj->look.hoodie = worn.hoodie;
    obj->look.hat = worn.hat;
    obj->look.hat_color = worn.hat_color;
    obj->look.garment = c->garment;
    Object_setPerson(obj, c->name, c->hand);
    obj->ai.character = c->character;
    obj->female       = c->female;
    obj->urinella     = c->urinella;
    obj->urinellas    = c->urinella ? POCKET_URINELLAS - 1 : 0;
    obj->urinella_tried = c->urinella ? 1 : 0; /* (who has them knows she likes them) */
    obj->lighter      = c->lighter;
    obj->lighter_uses = c->lighter ? LIGHTER_USES * (1 + id % 3) / 3 : 0; /* (some nearly empty) */
    obj->smoker       = c->smoker;
    obj->wits         = c->wits;
    obj->height_fear  = c->height_fear;
    obj->worn         = c->worn;
    obj->cigarettes   = c->cigarettes;
    obj->tobacco      = c->tobacco;
    obj->papers       = c->papers;
    obj->filters      = c->filters;
    obj->filter_habit = c->filter_habit;
    obj->pocket_tray  = c->pocket_tray;
    obj->lives_here   = c->lives_here;
    obj->keys         = c->keys;
    obj->work_trousers = id == 0; /* (you: work trousers, plenty of pockets) */
    obj->craving      = 0.3f + 0.15f * (float)(id % 3);
    obj->commitment   = c->commitment;
    obj->like         = c->lives_here ? 0.45f : 0.5f + 0.25f * c->commitment; /* (you: the crew are friends; the hosts friendly) */
    obj->love         = 0.05f + 0.2f * (float)((id * 2654435761u) >> 24 & 255u) / 255.0f; /* (a little, here and there) */
    obj->leads        = (int)c->leads;
    obj->helps        = (int)c->helps;
    for (int k = 0; k < ORDER_COUNT; k++) obj->skill[k] = c->character.skill[k];
    obj->tempo = c->character.tempo;
    Object_setHeight(obj, obj->look.height); /* (the body box their own size) */
    float years = clampf(1.0f - (float)(c->age - 45) / 60.0f, 0.5f, 1.0f); /* (old legs) */
    obj->top_speed  *= years;
    obj->jump_speed *= years;
    obj->pace = years * (1.0f + 0.12f * obj->temperament.energy); /* (Kurti: old and calm - he takes his time) */
    return obj;
}

const char *Cast_projectName(Project p) {
    static const char *const NAMES[PROJECT_COUNT] = {
        [PROJECT_NONE] = "-", [PROJECT_ORGA] = "organisation", [PROJECT_WEB] = "web and tickets", [PROJECT_ARTISTS] = "artists",
        [PROJECT_DECORATION] = "decoration", [PROJECT_LIGHT] = "light", [PROJECT_HOFBUEHNE] = "Hofbuehne (bands)",
        [PROJECT_OBSTWIESE] = "Obstwiese (psytrance)", [PROJECT_KARTOFFELACKER] = "Kartoffelacker (world music)",
        [PROJECT_HIDDEN] = "hidden floor (techno)", [PROJECT_MOWING] = "mowing (scythe)", [PROJECT_SNAILS] = "snail rescue",
        [PROJECT_WOOD_ART] = "wood art (chainsaw)", [PROJECT_BIG_TREE] = "big old tree",
    };
    return p >= 0 && p < PROJECT_COUNT && NAMES[p] ? NAMES[p] : "?";
}

Object *Cast_leadOf(Object *const *objects, int count, Project p) {
    if (p == PROJECT_NONE) return NULL;
    for (int i = 0; i < count; i++)
        if (objects[i]->is_character && objects[i]->cast_id >= 0 && objects[i]->leads == (int)p) return objects[i];
    return NULL;
}