/* * The festival weekend, for the people (the game says what's on: World.fest, * game_festival.c): * * - Guests come in from the chicken farm's yard, over to their place at the * camping, the tent out of its bag and up (ai.c pitchTents, the two of them). * - A floor playing: over to it, a place on the dance floor in front of the * stage, dancing to its beat for a good while, then a break (a drink, a pee, * a sit down) and back. The better built the floor, the more fun. Sleepy: * off to the tent. Evenings the party people drink; some too much. * - The awareness team (said yes to at the Herberge): the crew see to anybody * lying about drunk - water, sat up, okay again. * - The storm: those who know it's coming (told by you, the crew, from a * stage) go in - the main building, the barn, your garages' kitchen - and * stay in till it's over. The crew tell the guests on their way in; with an * awareness team they go round the whole place, the tents too. * - Sunday: the guests' tents down (packed away), off out at the gate. */ #include #include #include "people/ai_internal.h" #include "world/car.h" #include "world/guests.h" #include "world/site.h" #include "world/tent.h" #include "world/why.h" #define DANCE_SPREAD 4.5f /* m: places on the dance floor round its middle */ #define DANCE_MIN 150.0f /* real s dancing at a go ... */ #define DANCE_MORE 300.0f /* ... up to this much longer */ #define DANCE_BREAK 60.0f /* real s off between goes (up to twice that) */ #define FUN_DANCING 0.0009f /* fun a real second dancing (x how good the floor is) */ #define FUN_BORED 0.00015f /* ... lost a real second awake with nothing playing (day and evening) */ #define TELL_REACH 4.0f /* m: near enough to tell somebody about the storm */ #define TELL_LOOK 25.0f /* m: the crew warn guests this near on their way in ... */ #define TELL_LOOK_TEAM 140.0f /* ... the awareness team goes round everywhere */ #define HELP_LOOK 45.0f /* m: the awareness team sees somebody lying drunk this near */ #define GATE_REACH 1.5f #define HARASS_EVERY 900.0f /* real s: a drunk party guest dancing gets too close to somebody about this often */ #define LITTER_RATE 0.45f /* pieces a guest drops a game hour awake (x 1.25 - the place's awareness, more drunk) ... */ #define LITTER_MOST 160 /* ... no more while this much lies about */ static bool crewMember(const Object *o) { return o->type == OBJ_HUMAN && o->cast_id > 0; } static Object *guestNumber(Object *const *objects, int count, int k) { for (int i = 0; i < count; i++) if (objects[i]->type == OBJ_HUMAN && objects[i]->guest == k) return objects[i]; return NULL; } static void stopDancing(Object *self) { if (self->ai.fest_floor) self->ai.fest_time = -(DANCE_BREAK + DANCE_BREAK * random01(&self->ai)); self->ai.fest_floor = 0; self->body.dance_goal = 0.0f; } static bool safeInside(const Object *o) { return o->indoors > 0.0f; } /* ---------- The storm ---------- */ /* The nearest who doesn't know yet (awake, or - the awareness team - asleep in a tent too), nobody else after them. */ static Object *untold(const Object *self, Object *const *objects, int count, float look, bool tents) { Object *best = NULL; float best_d = look; for (int i = 0; i < count; i++) { Object *o = objects[i]; if (o == self || o->type != OBJ_HUMAN || o->storm_told || o->gone_home || safeInside(o) || o->ai.meeting) continue; if ((o->in_tent || o->body.asleep) && !tents) continue; float d = flatDistance(o->position, self->position); if (d >= best_d) continue; bool taken = false; for (int j = 0; j < count && !taken; j++) taken = objects[j] != self && objects[j]->type == OBJ_HUMAN && objects[j]->ai.fest_who == o; if (!taken) { best_d = d; best = o; } } return best; } static void tellStorm(Object *self, Object *o) { o->storm_told = true; if (o->in_tent) { o->rain_nap = false; Tent_crawlOut(o); } if (o->body.asleep) Body_getUp(&o->body); o->ai.talking = false; static const char *const SAY[] = { "A storm's coming - get inside, quick! The barn or the house.", "Storm warning! Not in the tent - into the barn!", "Come on, inside - a big storm, any minute now!" }; Ai_say(self, SAY[(int)(random01(&self->ai) * 3.0f) % 3]); if (o->ai.speech_time <= 0.0f) Ai_say(o, random01(&o->ai) < 0.5f ? "What? Okay, okay - I'm going." : "Oh no. Right, inside."); } static Vec3 nearestDoor(const Object *self) { const Fest *f = &g_world->fest; int best = 0; for (int k = 1; k < 3; k++) if (flatDistance(f->shelter[k], self->position) < flatDistance(f->shelter[best], self->position)) best = k; return f->shelter[best]; } static bool storm(Object *self, Object *const *objects, int count, float dt) { Ai *ai = &self->ai; const Fest *f = &g_world->fest; if (f->stage != FEST_WARNED && f->stage != FEST_STORM) { ai->fest_who = NULL; return false; } if (!self->storm_told || safeInside(self) || self->body.asleep || self->in_tent) return false; stopDancing(self); self->sitting = false; /* the crew: telling the guests on the way (the awareness team: everybody, everywhere, the tents too) */ if (crewMember(self) && f->stage == FEST_WARNED && !self->lives_here) { Object *who = ai->fest_who; if (who && (who->storm_told || safeInside(who) || (ai->fest_walk += dt) > 90.0f)) who = ai->fest_who = NULL; if (!who) { who = ai->fest_who = untold(self, objects, count, f->awareness ? TELL_LOOK_TEAM : TELL_LOOK, f->awareness); ai->fest_walk = 0.0f; } if (who) { Vec3 at = who->in_tent ? Tent_door(who->in_tent) : who->position; if (ai_goToPoint(self, at, TELL_REACH, objects, count, dt)) { tellStorm(self, who); ai->fest_who = NULL; } return true; } } /* in: the nearest door */ for (int h = 0; h < 2; h++) if (self->hands.item[h] && self->hands.item[h]->type != OBJ_UMBRELLA) Hands_drop(self, h); Vec3 door = nearestDoor(self); if (!ai_goToPoint(self, door, 1.2f, objects, count, dt)) { self->movement = vec3_scale(self->movement, 1.3f); /* (hurrying) */ return true; } self->inside_door = door; self->indoors = 30.0f; ai->sheltering = false; return true; } /* ---------- The awareness team ---------- */ static bool seeTo(Object *self, Object *const *objects, int count, float dt) { Ai *ai = &self->ai; if (!g_world->fest.awareness || !crewMember(self) || self->lives_here || self->drunk > 0.4f) return false; if (Why_knows(self, WHY_AWARENESS) && (g_world->hour >= 18.0f || g_world->hour < 4.0f)) /* (on shift: awake till four) */ self->needs.sleepiness = fminf(self->needs.sleepiness, 0.72f); Object *who = ai->fest_who; if (who && (!who->passed_out || !Body_isDown(&who->body) || (ai->fest_walk += dt) > 120.0f)) who = ai->fest_who = NULL; if (!who) { float best_d = HELP_LOOK; for (int i = 0; i < count; i++) { Object *o = objects[i]; if (o == self || o->type != OBJ_HUMAN || !o->passed_out || !Body_isDown(&o->body) || o->in_tent) continue; float d = flatDistance(o->position, self->position); bool taken = false; for (int j = 0; j < count && !taken; j++) taken = objects[j] != self && objects[j]->type == OBJ_HUMAN && objects[j]->ai.fest_who == o; if (d < best_d && !taken) { best_d = d; who = o; } } if (!who) return false; ai->fest_who = who; ai->fest_walk = 0.0f; Ai_say(self, "Somebody's down over there - I'll have a look."); } stopDancing(self); if (!ai_goToPoint(self, who->position, 1.7f, objects, count, dt)) return true; Hands_touch(self, vec3_add(who->position, vec3(0.0f, 0.3f, 0.0f))); Ai_say(self, random01(ai) < 0.5f ? "Hey, are you okay? Here - drink some water." : "Easy now. Sit up - water, then you'll be fine."); who->drunk = fminf(who->drunk, 0.45f); /* (water, sat up: back on their feet soon) */ who->needs.sleepiness = fminf(who->needs.sleepiness, 0.5f); who->fun = fminf(1.0f, who->fun + 0.08f); g_world->fest.helped++; ai->fest_who = NULL; return true; } /* ---------- Guests ---------- */ /* The evening: the party people drink (their own), some too much. */ static void partyDrinks(Object *self) { float h = g_world->hour, game_h = g_world->game_dt / 3600.0f; bool evening = h >= 20.0f || h < 3.0f; if (!evening || self->body.asleep) return; float party = (float)((self->guest * 2654435761u) >> 24 & 255u) / 255.0f; /* (how much of a party person: their own) */ float most = party < 0.6f ? 0.35f : 0.6f + 0.6f * (party - 0.6f) / 0.4f; /* (a few end up over 1: down) */ if (self->drunk < most) self->drunk = fminf(most, self->drunk + 0.22f * game_h); } /* Their place at the camping: over there, the tent out (the first of the two to get there). */ static bool camp(Object *self, Object *const *objects, int count, float dt) { if (self->camp_spot) return false; Vec3 spot = Guest_campSpot(self->guest); spot.y = self->position.y; Vec3 side = vec3(self->guest % 2 ? -1.2f : 1.2f, 0.0f, 1.6f); /* (beside where the tent goes) */ if (!ai_goToPoint(self, vec3_add(spot, side), 0.8f, objects, count, dt)) return true; self->camp_spot = 1; Object *mate = guestNumber(objects, count, Guest_mate(self->guest)); bool there = Tent_of(objects, count, self) != NULL; for (int i = 0; i < count && !there; i++) { const Object *o = objects[i]; there = (o->type == OBJ_TENT && !o->broken && (o->owner == self || (mate && o->owner == mate))) || (o->type == OBJ_GEAR && o->variant >= GEAR_TENT_SMALL && !o->broken && (o->owner == self || (mate && o->owner == mate))); } if (!there) { spot.y = World_groundHeight(g_world, spot.x, spot.z, self->position.y + 2.0f) + 0.02f; Object *bag = Gear_create(GEAR_TENT_BIG, self, spot); if (bag && !World_add(g_world, bag)) bag = NULL; if (bag) { Object *tent = Tent_unpack(g_world, bag, Guest_campDoor()); if (tent) tent->owner2 = mate; Ai_say(self, random01(&self->ai) < 0.5f ? "Here we are - tent up, then the music!" : "This is our spot. Right, the tent."); } } return true; } /* A cup, a can, a bit of paper: dropped where they stand (less where people look out for the place). */ static void litter(Object *self) { const Fest *f = &g_world->fest; if (f->stage < FEST_OPEN || f->stage > FEST_LEAVING || f->litter >= LITTER_MOST || self->heading_home) return; float rate = LITTER_RATE * (1.25f - g_world->aware) * (1.0f + 0.6f * fminf(self->drunk, 1.0f)) * (self->supporter ? 0.3f : 1.0f); if (random01(&self->ai) >= rate * g_world->game_dt / 3600.0f) return; Vec3 f2 = Object_facing(self); Vec3 at = vec3_add(self->position, vec3(-f2.z * 0.3f, 0.0f, f2.x * 0.3f)); at.y = World_groundHeight(g_world, at.x, at.z, self->position.y + 1.0f); Site_litter(g_world, at, self->guest + (int)(random01(&self->ai) * 7.0f), self); } static float leaveHour(const Object *self) { return 10.0f + 4.5f * (float)((self->guest - 1) / 2) / (float)GUEST_SPOTS; } /* (two by two, from ten) */ /* Sunday: off home - the tent down and gone, out at the gate. */ static bool leave(Object *self, Object *const *objects, int count, float dt) { bool sunday = self->guest && !self->supporter && g_world->fest.stage == FEST_LEAVING && g_world->hour >= leaveHour(self); if (!sunday && !self->heading_home) return false; if (self->ai.mode == AI_WORK) Ai_stopWork(self); stopDancing(self); self->sitting = false; if (self->in_tent) { self->rain_nap = false; Tent_crawlOut(self); } if (self->body.asleep) { Body_getUp(&self->body); return true; } Object *tent = Tent_of(objects, count, self); if (tent) { /* (packed up and away with them) */ if (!ai_goToPoint(self, Tent_door(tent), 1.2f, objects, count, dt)) return true; Tent_takeDown(objects, count, tent); float messy = (1.0f - self->fun) * (1.2f - g_world->aware); /* (left behind: the ones who didn't care) */ for (int k = 0; k < 3 && g_world->fest.litter < LITTER_MOST + 30; k++) if (self->guest && random01(&self->ai) < messy) Site_litter(g_world, vec3_add(Tent_door(tent), vec3(0.4f * (float)k - 0.2f, 0.0f, 0.5f)), self->guest + k * 3, self); Object_useUp(tent); Ai_say(self, "That's the tent packed. What a weekend!"); return true; } Vec3 gate = Guest_gate(); gate.y = self->position.y; self->needs.sleepiness = fminf(self->needs.sleepiness, 0.6f); /* (they'll sleep in the car) */ if (!ai_goToPoint(self, gate, GATE_REACH, objects, count, dt) && (self->ai.fest_walk += dt) < 400.0f) return true; /* (lost on the way: gone anyway) */ freeHands(self, NULL); self->gone_home = true; self->inside_door = Guest_gate(); self->indoors = 1e7f; return true; } /* A floor playing: dancing there a while. */ static bool dance(Object *self, Object *const *objects, int count, float dt) { Ai *ai = &self->ai; const Fest *f = &g_world->fest; if (ai->fest_floor && !(f->on & (1u << (ai->fest_floor - 1)))) stopDancing(self); /* (the music stopped) */ if (ai->fest_time < 0.0f && !ai->fest_floor) { ai->fest_time += dt; return false; } bool guest = self->guest > 0; if (!guest && !ai->fest_floor && (g_world->hour > 3.0f && g_world->hour < 19.0f)) return false; /* (the crew: evenings and nights) */ if (self->needs.sleepiness > 0.8f || self->needs.bladder > 0.8f || self->drunk > 0.9f || Body_isSmoking(&self->body)) { stopDancing(self); return false; } if (!ai->fest_floor) { if (!f->on) return false; float pick = random01(ai) * 1.2f, sum = 0.0f; /* (the better floors pull more; each guest a favourite) */ int fav = guest ? (self->guest * 7) % FEST_FLOORS : self->cast_id % FEST_FLOORS; float w[FEST_FLOORS], all = 0.0f; for (int k = 0; k < FEST_FLOORS; k++) { w[k] = (f->on & (1u << k)) ? f->good[k] * (k == fav ? 2.0f : 1.0f) : 0.0f; all += w[k]; } if (all <= 0.0f) return false; for (int k = 0; k < FEST_FLOORS && !ai->fest_floor; k++) { sum += w[k] / all * 1.2f; if (pick <= sum) ai->fest_floor = k + 1; } if (!ai->fest_floor) { ai->fest_time = -DANCE_BREAK; return false; } /* (not now) */ float a = 6.2832f * random01(ai), r = DANCE_SPREAD * sqrtf(random01(ai)); ai->fest_spot = vec3_add(f->dance[ai->fest_floor - 1], vec3(cosf(a) * r, 0.0f, sinf(a) * r)); ai->fest_time = 0.0f; if (random01(ai) < 0.3f) Ai_say(self, ai->fest_floor == 2 ? "The Obstwiese's going off - come on!" : "I love this one - let's dance!"); } int k = ai->fest_floor - 1; self->sitting = false; if (flatDistance(self->position, ai->fest_spot) > 0.9f) { self->body.dance_goal = 0.0f; if (!ai_goToPoint(self, ai->fest_spot, 0.6f, objects, count, dt)) { if ((ai->fest_walk += dt) > 150.0f) { stopDancing(self); ai->fest_walk = 0.0f; } /* (can't get there) */ return true; } } ai->fest_walk = 0.0f; stand(self); turnTowards(self, vec3_scale(f->front[k], -1.0f), dt); self->body.dance_goal = 1.0f; if (guest && !self->female && self->drunk > 0.6f && self->harass_t <= 0.0f && !self->ai.harass) { /* (a drunk one, now and then: too close to somebody) */ float party = (float)((self->guest * 2654435761u) >> 24 & 255u) / 255.0f; if (party > 0.75f && random01(ai) < dt / HARASS_EVERY) { Object *best = NULL; float best_d = 6.0f; for (int i = 0; i < count; i++) { Object *o = objects[i]; if (o == self || o->type != OBJ_HUMAN || !o->female || o->guest == Guest_mate(self->guest) || Body_isDown(&o->body)) continue; float d = flatDistance(o->position, self->position); if (d < best_d) { best_d = d; best = o; } } if (best) { ai->harass = best; ai->harass_walk = 0.0f; stopDancing(self); return false; } } } self->body.dance_beat = f->clock * f->bpm[k] / 60.0f + 0.03f * (float)(self->guest % 3); self->body.dance_style = guest ? self->guest % 4 : self->cast_id % 4; if (guest) self->fun = fminf(1.0f, self->fun + FUN_DANCING * f->good[k] * dt); if ((ai->fest_time += dt) > DANCE_MIN + DANCE_MORE * (float)((self->guest * 13 + self->cast_id * 7) % 10) / 10.0f) stopDancing(self); return true; } bool ai_festival(Object *self, Object *const *objects, int count, float dt) { if (!g_world) return false; const Fest *f = &g_world->fest; if (f->stage == FEST_NONE) { self->body.dance_goal = 0.0f; self->ai.fest_floor = 0; return false; } if (self->guest) { /* (they brought their own food and drink: the crew's is the crew's) */ self->needs.hunger = fminf(self->needs.hunger, 0.3f); self->needs.thirst = fminf(self->needs.thirst, 0.3f); partyDrinks(self); if (!self->body.asleep && !self->in_tent && !Body_isDown(&self->body)) litter(self); if (!f->on && !self->body.asleep && g_world->hour > 9.0f && f->stage == FEST_OPEN) self->fun = fmaxf(0.0f, self->fun - FUN_BORED * dt); } if (((self->guest && !self->supporter && f->stage == FEST_LEAVING && g_world->hour >= leaveHour(self)) || self->heading_home) && (self->in_tent || self->body.asleep)) { if (self->in_tent) { self->rain_nap = false; Tent_crawlOut(self); } /* (time to go: up, even after a long night) */ if (self->body.asleep) { self->drunk = fminf(self->drunk, 0.5f); self->needs.sleepiness = fminf(self->needs.sleepiness, 0.6f); Body_getUp(&self->body); } return true; } if (Body_isDown(&self->body) || self->in_tent || self->body.asleep) return false; if (storm(self, objects, count, dt)) return true; if (f->stage == FEST_WARNED || f->stage == FEST_STORM) { stopDancing(self); return false; } /* (not told: they carry on - the rain, a tent...) */ AiMode m = self->ai.mode; if (m != AI_IDLE && m != AI_WANDER && m != AI_TABLE) { stopDancing(self); return false; } /* (busy with something) */ if (seeTo(self, objects, count, dt)) return true; if ((self->guest || self->heading_home) && leave(self, objects, count, dt)) return true; if (self->guest) { if (camp(self, objects, count, dt)) return true; Object *tent = Tent_of(objects, count, self); if (tent && tent->tent_stage < TENT_UP) { stopDancing(self); return false; } /* (the tent first: ai.c pitchTents) */ if (self->needs.sleepiness > 0.8f && tent && tent->tent_stage >= TENT_UP && flatDistance(tent->position, self->position) > 50.0f) { stopDancing(self); /* (sleepy: back to the tent - ai.c goToBed takes it from there) */ ai_goToPoint(self, Tent_door(tent), 2.0f, objects, count, dt); return true; } } if (f->stage != FEST_OPEN) { stopDancing(self); return false; } return dance(self, objects, count, dt); }