#include "world/field.h" #include "world/site.h" #include "world/cast.h" #include #include _Static_assert(FIELD_CELLS == WORLD_FIELD_CELLS, "world.h WORLD_FIELD_CELLS"); #define REFUGE_SPREAD 2.5f /* m: let-go snails round the refuge */ #define STAGE_CLEAR 3.6f /* m round the stage: no grass to mow there */ static unsigned mix(unsigned h) { h ^= h >> 16; h *= 0x7feb352du; h ^= h >> 15; h *= 0x846ca68bu; h ^= h >> 16; return h; } static float dice(unsigned k) { return (float)(mix(k) & 0xffffu) / 65535.0f; } static int cellOf(float x, float z) { int ix = (int)floorf((x - FIELD_X0) / FIELD_CELL), iz = (int)floorf((z - FIELD_Z0) / FIELD_CELL); return ix < 0 || iz < 0 || ix >= FIELD_NX || iz >= FIELD_NZ ? -1 : iz * FIELD_NX + ix; } static Vec3 middleOf(const World *world, int c) { float x = FIELD_X0 + ((float)(c % FIELD_NX) + 0.5f) * FIELD_CELL, z = FIELD_Z0 + ((float)(c / FIELD_NX) + 0.5f) * FIELD_CELL; return vec3(x, world->has_terrain ? Terrain_height(&world->terrain, x, z) : 0.0f, z); } bool Field_inside(float x, float z) { return cellOf(x, z) >= 0; } float Field_mownAt(const World *world, float x, float z) { int c = cellOf(x, z); if (c < 0) return 0.0f; return world->mown[c] == FIELD_OFF ? 1.0f : (float)world->mown[c] / (float)FIELD_MOWN; } static bool snailWild(const Object *o) { return o->type == OBJ_SNAIL && o->variant == 0 && !o->broken && !o->is_held; } int Field_mow(World *world, Vec3 at, float radius, int *killed) { int done = 0, dead = 0; for (int dz = -2; dz <= 2; dz++) for (int dx = -2; dx <= 2; dx++) { int c = cellOf(at.x + (float)dx * FIELD_CELL, at.z + (float)dz * FIELD_CELL); if (c < 0 || world->mown[c] >= FIELD_MOWN) continue; Vec3 m = middleOf(world, c); if (hypotf(m.x - at.x, m.z - at.z) > radius + FIELD_CELL * 0.5f) continue; if (++world->mown[c] >= FIELD_MOWN) done++; world->mow_rev++; for (int i = 0; i < world->count; i++) { /* (a snail in the grass there: too late) */ Object *o = world->objects[i]; if (!snailWild(o) || cellOf(o->position.x, o->position.z) != c) continue; Object_useUp(o); world->snails_killed++; dead++; } } if (killed) *killed = dead; return done; } float Field_done(const World *world) { int all = 0, mown = 0; for (int c = 0; c < FIELD_CELLS; c++) { if (world->mown[c] == FIELD_OFF) continue; all++; if (world->mown[c] >= FIELD_MOWN) mown++; } return all ? (float)mown / (float)all : 1.0f; } bool Field_nextUnmown(const World *world, Vec3 from, Vec3 *out) { int best = -1; float best_d = 1e9f; for (int c = 0; c < FIELD_CELLS; c++) { if (world->mown[c] >= FIELD_MOWN) continue; Vec3 m = middleOf(world, c); float d = hypotf(m.x - from.x, m.z - from.z) + 0.3f * (float)(c / FIELD_NX); /* (row by row, roughly) */ if (d < best_d) { best_d = d; best = c; } } if (best < 0) return false; *out = middleOf(world, best); return true; } static bool addSnail(World *world, Vec3 at, int variant) { at.y = World_groundHeight(world, at.x, at.z, at.y + 2.0f) + 0.02f; Object *s = Object_create(OBJ_SNAIL, PALETTE_WARM, at, vec3_zero(), (float)variant); if (!s) return false; s->rotation.y = 6.2832f * dice((unsigned)(at.x * 977.0f) ^ (unsigned)(at.z * 131.0f)); Object_syncColliders(s); return World_add(world, s); } bool Field_prepare(World *world) { memset(world->mown, 0, sizeof world->mown); for (int i = 0; i < world->count; i++) { /* (under and round the stage: nothing to mow) */ const Object *s = world->objects[i]; if (s->type != OBJ_STAGE) continue; for (int c = 0; c < FIELD_CELLS; c++) { Vec3 m = middleOf(world, c); if (hypotf(m.x - s->position.x, m.z - s->position.z) < STAGE_CLEAR) world->mown[c] = FIELD_OFF; } } for (int k = 0; k < FIELD_SNAILS; k++) { /* the snails: about the field ... */ float x = FIELD_X0 + 0.5f + dice(1000u + (unsigned)k) * ((float)FIELD_NX * FIELD_CELL - 1.0f); float z = FIELD_Z0 + 0.5f + dice(2000u + (unsigned)k) * ((float)FIELD_NZ * FIELD_CELL - 1.0f); int c = cellOf(x, z); if (c >= 0 && world->mown[c] == FIELD_OFF) continue; if (!addSnail(world, vec3(x, 0.0f, z), 0)) return false; } Vec3 orchard = Site_floorSpot(PROJECT_OBSTWIESE); /* ... and in the orchard, round where its stage goes */ for (int k = 0; k < ORCHARD_SNAILS; k++) { float a = 6.2832f * dice(3000u + (unsigned)k), r = 4.5f + 6.0f * dice(4000u + (unsigned)k); if (!addSnail(world, vec3(orchard.x + cosf(a) * r, orchard.y, orchard.z + sinf(a) * r), 0)) return false; } return true; } int Field_snailsInField(const World *world) { int n = 0; for (int i = 0; i < world->count; i++) if (snailWild(world->objects[i]) && Field_inside(world->objects[i]->position.x, world->objects[i]->position.z)) n++; return n; } int Field_snailsLeft(const World *world) { int n = 0; for (int i = 0; i < world->count; i++) if (snailWild(world->objects[i])) n++; return n; } bool Field_pickSnail(World *world, Object *who, Object *snail) { if (!snailWild(snail) || who->snails >= SNAIL_BUCKET) return false; (void)world; Object_useUp(snail); who->snails++; return true; } int Field_release(World *world, Object *who) { int n = who->snails; if (n <= 0) return 0; Vec3 refuge = Site_snailRefuge(); for (int k = 0; k < n; k++) { unsigned seed = (unsigned)(world->snails_rescued + k) * 7919u; float a = 6.2832f * dice(seed), r = REFUGE_SPREAD * sqrtf(dice(seed + 1u)); addSnail(world, vec3(refuge.x + cosf(a) * r, refuge.y, refuge.z + sinf(a) * r), 1); } world->snails_rescued += n; who->snails = 0; return n; } Object *Field_artLog(const World *world) { Vec3 spot = Site_artSpot(); Object *best = NULL; float best_d = ART_REACH; for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o->type != OBJ_LOG || o->is_held || o->lifted || o->broken || o->size.y < 0.8f || o->size.x < 0.2f) continue; float d = hypotf(o->position.x - spot.x, o->position.z - spot.z); if (d < best_d) { best_d = d; best = o; } } return best; } Object *Field_carve(World *world, Object *log) { Vec3 at = Object_colliderCenter(log, 0); float d = log->size.x, h = fminf(fmaxf(log->size.y, 0.9f), 1.7f); at.y = World_groundHeight(world, at.x, at.z, at.y + 1.0f); Object *art = Object_create(OBJ_SCULPTURE, PALETTE_WARM, at, vec3(d, h, d), (float)(Field_artPieces(world) % 3)); if (!art) return NULL; art->rotation.y = log->rotation.y; Object_syncColliders(art); if (!World_add(world, art)) { Object_destroy(art); return NULL; } Object_useUp(log); return art; } int Field_artPieces(const World *world) { int n = 0; for (int i = 0; i < world->count; i++) if (world->objects[i]->type == OBJ_SCULPTURE && !world->objects[i]->broken) n++; return n; }