#include "world/tent.h" #include "world/car.h" #include "people/hands.h" #include "render/shapes.h" #include "world/weather.h" #include #include #define LAY_TIME 4.0f /* s (one person, average): spreading it out, the poles through their sleeves */ #define POLES_SMALL 16.0f /* ... the poles in and up */ #define POLES_BIG 24.0f #define COVER_SMALL 10.0f /* ... the cover over it */ #define COVER_BIG 14.0f #define PEG_TIME 2.5f /* ... each peg (with a stone to hit it: twice as quick) */ #define DOOR_OUT 0.45f /* in front of the door: this far out from the cover */ #define BED_REACH 0.8f /* a sleeping bag or mat put down this near the door goes in */ #define DOWN_QUICKER 0.6f /* taking it down: each bit this much of the time putting it up took ... */ #define FOLD_TIME 7.0f /* ... and folding it up into its bag at the end */ /* How far the stage shows: up, its work; coming down, the other way round. */ static float shownWork(const Object *tent) { return tent->tent_down ? 1.0f - tent->tent_work : tent->tent_work; } static Vec3 local(const Object *tent, Vec3 p) { return vec3_add(tent->position, mat4_mul_dir(mat4_rotate_y(tent->rotation.y), p)); } static Vec3 toLocal(const Object *tent, Vec3 w) { return mat4_mul_dir_transposed(mat4_rotate_y(tent->rotation.y), vec3_sub(w, tent->position)); } /* Everybody's own cover colour (by name, like their other things). */ static Vec3 flyColor(const Object *tent) { const Object *o = tent->owner; if (o && o->guest) { /* the guests': all sorts */ static const Vec3 FLY[] = { { 0.78f, 0.22f, 0.20f }, { 0.20f, 0.42f, 0.70f }, { 0.85f, 0.62f, 0.15f }, { 0.25f, 0.55f, 0.35f }, { 0.55f, 0.30f, 0.62f }, { 0.15f, 0.60f, 0.62f }, { 0.60f, 0.60f, 0.58f }, { 0.88f, 0.42f, 0.18f } }; return FLY[((o->guest - 1) / 2) % 8]; } if (tent->variant) return vec3(0.40f, 0.44f, 0.24f); /* the big one: olive */ if (!o || o->type == OBJ_PLAYER) return vec3(0.22f, 0.36f, 0.56f); /* yours: blue */ if (!strcmp(o->name, "Toni")) return vec3(0.78f, 0.42f, 0.16f); /* Toni's: orange */ return vec3(0.30f, 0.50f, 0.30f); } /* Its look and its box for how far it's up (built again only when that shows). */ static void reshape(Object *tent) { int big = tent->variant, steps = tent->tent_stage == TENT_POLES || tent->tent_stage == TENT_COVER ? (int)(shownWork(tent) * 12.0f) : 0; int shown = tent->tent_stage * 1000 + steps * 10 + tent->stored; if (shown == tent->shown) return; tent->shown = shown; Mesh_destroy(&tent->mesh); tent->mesh = Shapes_tent(big, tent->tent_stage, (float)steps / 12.0f, tent->stored, vec3(0.90f, 0.86f, 0.62f), flyColor(tent)); float h = tent->tent_stage == TENT_LAID ? 0.04f : tent->tent_stage == TENT_POLES ? fmaxf(0.04f, TENT_H(big) * shownWork(tent)) : TENT_H(big); tent->colliders[0].center = vec3(0.0f, h * 0.5f, 0.0f); tent->colliders[0].half_extents = vec3(TENT_W(big) * 0.5f, h * 0.5f, TENT_L(big) * 0.5f); Object_syncColliders(tent); } Object *Tent_unpack(World *world, Object *bag, Vec3 door_dir) { if (!bag || bag->type != OBJ_GEAR || bag->variant < GEAR_TENT_SMALL || bag->is_held || bag->broken) return NULL; int big = bag->variant == GEAR_TENT_BIG; Vec3 at = bag->position; at.y = Terrain_height(&world->terrain, at.x, at.z); /* (on the ground: not on the sleeping bags lying there - it hovered) */ Object *tent = Object_create(OBJ_TENT, PALETTE_STANDARD, at, vec3_zero(), big ? 1.0f : 0.0f); if (!tent) return NULL; tent->owner = bag->owner; if (big && bag->owner && !strcmp(bag->owner->name, "Volkram")) /* (the big one is Volkram's and Jana's) */ for (int i = 0; i < world->count; i++) if (world->objects[i]->type == OBJ_HUMAN && !strcmp(world->objects[i]->name, "Jana")) tent->owner2 = world->objects[i]; snprintf(tent->name, sizeof(tent->name), "Tent"); door_dir = vec3_normalize(vec3(door_dir.x, 0.0f, door_dir.z)); Object_setRotation(tent, vec3(0.0f, yaw_facing(door_dir), 0.0f)); reshape(tent); if (!World_add(world, tent)) return NULL; Object_useUp(bag); return tent; } static bool hasStone(const Object *who) { for (int h = 0; h < 2; h++) if (who->hands.item[h] && who->hands.state[h] == HAND_HOLDING && who->hands.item[h]->type == OBJ_STONE) return true; return false; } void Tent_takeDown(Object *const *objects, int count, Object *tent) { if (tent->type != OBJ_TENT || tent->tent_down || tent->broken) return; for (int i = 0; i < count; i++) { /* (anybody in it: out) */ Object *o = objects[i]; if (o->in_tent != tent) continue; o->rain_nap = false; if (o->body.asleep) Body_getUp(&o->body); Tent_crawlOut(o); } tent->tent_down = true; tent->tent_work = 0.0f; if (tent->tent_stage >= TENT_UP) { tent->tent_stage = TENT_PEGS; tent->stored = TENT_PEGS(tent->variant); } else if (tent->tent_stage == TENT_PEGS && tent->stored == 0) tent->tent_stage = TENT_COVER; reshape(tent); } void Tent_refresh(Object *tent) { tent->shown = -1; reshape(tent); } bool Tent_movable(const Object *tent) { return tent && tent->type == OBJ_TENT && !tent->broken && (tent->tent_stage < TENT_PEGS || (tent->tent_stage == TENT_PEGS && tent->stored == 0) || (tent->tent_down && tent->tent_stage <= TENT_COVER)); } void Tent_startMove(Object *tent) { if (tent->tent_down) { /* (the pegs out on the way down: now it's going up again where it's put) */ tent->tent_down = false; if (tent->tent_stage == TENT_COVER) { tent->tent_stage = TENT_PEGS; tent->stored = 0; } tent->tent_work = 0.0f; reshape(tent); } tent->tent_dragged = true; } /* Coming down: the pegs out, the cover off, the poles down, folded up (packed in Tent_update). */ static int workDown(Object *tent, float rate, float dt) { int big = tent->variant; float time = tent->tent_stage == TENT_LAID ? FOLD_TIME : DOWN_QUICKER * (tent->tent_stage == TENT_POLES ? (big ? POLES_BIG : POLES_SMALL) : tent->tent_stage == TENT_COVER ? (big ? COVER_BIG : COVER_SMALL) : PEG_TIME); tent->tent_work += dt * rate / time; tent->tent_idle = 0.0f; int done = -1; if (tent->tent_work >= 1.0f) { if (tent->tent_stage == TENT_LAID) { tent->tent_work = 1.0f; return TENT_DOWN_PACKED; } /* (Tent_update packs it) */ tent->tent_work = 0.0f; if (tent->tent_stage == TENT_PEGS) { if (--tent->stored <= 0) { tent->stored = 0; tent->tent_stage = TENT_COVER; done = TENT_DOWN + TENT_COVER; } } else { tent->tent_stage--; done = TENT_DOWN + tent->tent_stage; } } reshape(tent); return done; } int Tent_work(Object *tent, Object *who, float dt) { if (tent->type != OBJ_TENT || tent->broken || tent->tent_dragged || (tent->tent_stage >= TENT_UP && !tent->tent_down)) return -1; int big = tent->variant; float rate = (0.75f + 0.5f * who->wits) * (1.0f - 0.5f * fminf(who->drunk, 1.0f)); if (tent->tent_down) return workDown(tent, rate, dt); float time = tent->tent_stage == TENT_LAID ? LAY_TIME : tent->tent_stage == TENT_POLES ? (big ? POLES_BIG : POLES_SMALL) : tent->tent_stage == TENT_COVER ? (big ? COVER_BIG : COVER_SMALL) : PEG_TIME / (hasStone(who) ? 2.0f : 1.0f); tent->tent_work += dt * rate / time; tent->tent_idle = 0.0f; int done = -1; if (tent->tent_work >= 1.0f) { tent->tent_work = 0.0f; if (tent->tent_stage == TENT_PEGS) { if (++tent->stored >= TENT_PEGS(big)) { tent->tent_stage = TENT_UP; done = TENT_UP; } } else { tent->tent_stage++; done = tent->tent_stage; } } reshape(tent); return done; } const char *Tent_job(const Object *tent) { if (tent->tent_down) switch (tent->tent_stage) { case TENT_LAID: return "folding it up into its bag"; case TENT_POLES: return "taking the poles down"; case TENT_COVER: return "pulling the cover off"; default: return "pulling the pegs out"; } switch (tent->tent_stage) { case TENT_LAID: return "spreading it out"; case TENT_POLES: return "putting the poles in"; case TENT_COVER: return "pulling the cover over"; case TENT_PEGS: return "hammering the pegs in"; default: return "done"; } } static Vec3 nextPeg(const Object *tent) { float x, z; Shapes_tentPeg(tent->variant, tent->tent_down ? (tent->stored > 0 ? tent->stored - 1 : 0) : tent->stored, &x, &z); return vec3(x, 0.0f, z); } Vec3 Tent_workSpot(const Object *tent, float side) { const float hw = TENT_W(tent->variant) * 0.5f, hl = TENT_L(tent->variant) * 0.5f; float s = side < 0.0f ? -1.0f : 1.0f; switch (tent->tent_stage) { case TENT_COVER: return local(tent, vec3(s * 0.5f * fabsf(side), 0.0f, -hl * 1.14f - 0.55f)); /* at the back, pulling it over */ case TENT_PEGS: { Vec3 p = nextPeg(tent), out = vec3_normalize(vec3(p.x, 0.0f, p.z)); return local(tent, vec3_add(p, vec3_scale(out, 0.45f))); } default: return local(tent, vec3(s * (hw + 0.55f), 0.0f, 0.0f)); /* beside it */ } } Vec3 Tent_handsAt(const Object *tent, const Object *who, float clock) { const int big = tent->variant; const float hw = TENT_W(big) * 0.5f, hl = TENT_L(big) * 0.5f, h = TENT_H(big); Vec3 me = toLocal(tent, who->position); float s = me.x < 0.0f ? -1.0f : 1.0f; switch (tent->tent_stage) { case TENT_LAID: return local(tent, vec3(s * hw, 0.08f, clampf(me.z, -hl, hl))); case TENT_POLES: return local(tent, vec3(s * hw * 0.7f, fmaxf(0.1f, 0.55f * h * shownWork(tent)), clampf(me.z, -hl * 0.5f, hl * 0.5f))); case TENT_COVER: return local(tent, vec3(s * 0.3f, h * 0.8f, -hl + 2.0f * hl * shownWork(tent))); case TENT_PEGS: { Vec3 p = nextPeg(tent); p.y = 0.06f + 0.14f * fabsf(sinf(clock * 7.0f)); /* (hitting it in) */ return local(tent, p); } default: return Tent_door(tent); } } Vec3 Tent_door(const Object *tent) { return local(tent, vec3(0.0f, 0.0f, TENT_L(tent->variant) * 0.5f * 1.14f + DOOR_OUT)); } bool Tent_isTheirs(const Object *tent, const Object *who) { return tent && tent->type == OBJ_TENT && who && (tent->owner == who || tent->owner2 == who); } Object *Tent_of(Object *const *objects, int count, const Object *who) { for (int i = 0; i < count; i++) if (objects[i]->type == OBJ_TENT && !objects[i]->broken && Tent_isTheirs(objects[i], who)) return objects[i]; return NULL; } static int slotOf(const Object *tent, const Object *who) { return tent->owner2 == who ? 1 : 0; } bool Tent_hasBed(const Object *tent, const Object *who) { return tent && (tent->tent_beds & (1 << (2 * slotOf(tent, who)))) != 0; } void Tent_crawlIn(Object *tent, Object *who) { const int big = tent->variant; float x = big ? (slotOf(tent, who) ? 0.45f : -0.45f) : 0.0f; Vec3 at = local(tent, vec3(x, 0.0f, TENT_L(big) * 0.5f - 0.95f)); /* (lying back into it, the feet towards the door) */ who->in_tent = tent; who->sitting = false; who->position = vec3(at.x, tent->position.y, at.z); who->rotation.y = tent->rotation.y; /* (facing the door) */ who->velocity = vec3_zero(); who->movement = vec3_zero(); Object_syncColliders(who); who->body.last_facing = Object_facing(who); /* (lies back from where they face now: the head to the back of the tent) */ Body_lieDown(&who->body); } void Tent_crawlOut(Object *who) { Object *tent = who->in_tent; if (!tent) return; Vec3 door = Tent_door(tent); who->in_tent = NULL; who->position = vec3(door.x, tent->position.y, door.z); who->rotation.y = tent->rotation.y; who->body.replant = true; Object_syncColliders(who); } /* Folded up: its bag where it lay (left packed), the sleeping bags and mats that were in it at the door. */ static void pack(World *world, Object *tent) { const int big = tent->variant; Object *bag = Gear_create(big ? GEAR_TENT_BIG : GEAR_TENT_SMALL, tent->owner, vec3_add(tent->position, vec3(0.0f, 0.02f, 0.0f))); if (bag) { bag->keep = true; bag->rotation.y = tent->rotation.y; Object_syncColliders(bag); if (!World_add(world, bag)) Object_destroy(bag); } Object *sleeper[2] = { tent->owner, tent->owner2 }; for (int k = 0; k < 4; k++) { if (!(tent->tent_beds & (1 << k)) || !sleeper[k / 2]) continue; Vec3 at = local(tent, vec3((big ? (k / 2 ? 0.45f : -0.45f) : 0.0f) + (k % 2 ? 0.3f : -0.3f), 0.0f, TENT_L(big) * 0.5f * 1.14f + DOOR_OUT + 0.3f)); at.y = World_groundHeight(world, at.x, at.z, at.y + 0.5f) + 0.02f; Object *thing = Gear_create(k % 2 ? GEAR_MAT : GEAR_SLEEPING_BAG, sleeper[k / 2], at); if (!thing) continue; thing->rotation.y = tent->rotation.y; Object_syncColliders(thing); if (!World_add(world, thing)) Object_destroy(thing); } tent->tent_beds = 0; tent->owner = tent->owner2 = NULL; Object_useUp(tent); } /* The wind and a tent not pegged down (pegs under half in): it slides and turns along the ground, more standing on its poles than lying flat, till it snags on something or somebody gets hold of it (working on it). */ #define BLOW_STANDING 4.0f /* m/s: on its poles, it goes from this wind ... */ #define BLOW_FLAT 6.5f /* ... lying flat from this */ #define BLOW_PUSH 0.13f /* m/s it slides for each m/s over that */ #define BLOW_HELD 1.5f /* s: worked on this recently, it's held */ static bool notIt(const Object *o, const void *tent) { return o == tent || o->is_character || !o->is_static; } static void blowAbout(World *world, Object *tent, float dt) { if (tent->tent_stage >= TENT_UP || (tent->tent_stage == TENT_PEGS && tent->stored * 2 >= TENT_PEGS(tent->variant)) || tent->tent_idle < BLOW_HELD) return; float speed = Weather_windSpeed(world), from = tent->tent_stage == TENT_LAID ? BLOW_FLAT : BLOW_STANDING; if (speed <= from) return; Vec3 dir = vec3_scale(world->wind, 1.0f / speed); float push = (speed - from) * BLOW_PUSH * (tent->tent_stage == TENT_LAID ? 0.5f : 1.0f); float reach = 0.5f * fmaxf(TENT_W(tent->variant), TENT_L(tent->variant)) + 0.15f; Vec3 from_p = vec3_add(tent->position, vec3(0.0f, 0.3f, 0.0f)); if (World_rayCast(world, from_p, dir, reach, notIt, tent, NULL, NULL) >= 0.0f) return; /* (snagged on something) */ tent->position = vec3_add(tent->position, vec3_scale(dir, push * dt)); tent->position.y = Terrain_height(&world->terrain, tent->position.x, tent->position.z); Object_setRotation(tent, vec3(0.0f, tent->rotation.y + 0.06f * push * sinf(tent->position.x * 1.7f + tent->position.z) * dt, 0.0f)); Object_syncColliders(tent); world->tent_blowing = tent; tent->tent_blown = 6.0f; /* (its owner comes running) */ } #define RAIN_NAP_END 0.15f /* rain: under this, out of the tent again */ void Tent_update(World *world, float dt) { world->tent_blowing = NULL; for (int i = 0; i < world->count; i++) { Object *o = world->objects[i]; if (o->type == OBJ_TENT) { o->tent_idle += dt; o->tent_blown = fmaxf(0.0f, o->tent_blown - dt); if (o->tent_down && !o->broken && o->tent_stage == TENT_LAID && o->tent_work >= 1.0f) pack(world, o); else if (!o->broken) blowAbout(world, o, dt); continue; } if (o->rain_nap && (!o->body.asleep || world->rain < RAIN_NAP_END)) { /* in out of the rain: up when it stops */ o->rain_nap = false; if (o->body.asleep && o->needs.sleepiness < 0.6f) Body_getUp(&o->body); } if (o->is_character && o->in_tent && !o->body.asleep && o->body.pose == POSE_UP) { Tent_crawlOut(o); continue; } /* a sleeping bag or a mat put down at the door of its owner's tent: in it goes */ if (o->type != OBJ_GEAR || o->variant > GEAR_MAT || o->is_held || o->broken || !o->owner) continue; Object *tent = Tent_of(world->objects, world->count, o->owner); if (!tent || tent->tent_stage < TENT_COVER) continue; Vec3 door = Tent_door(tent); if (hypotf(o->position.x - door.x, o->position.z - door.z) > BED_REACH) continue; tent->tent_beds |= 1 << (2 * slotOf(tent, o->owner) + (o->variant == GEAR_MAT ? 1 : 0)); Object_useUp(o); } }