/* * The shopping run: asked in a talk (talk.c NODE_SHOP), the one with the car * keys walks over to the car, backs it out of the yard, turns and drives off * along the way in; an hour later (game time) the car comes back the same way * with the boot full - boards, nails, screws, paint, food and drink - parks * where it stood and the boot opens: the driver and whoever is idle nearby * unload it (ai.c unloadCar; material in front of the car room, food into the * kitchen: car.c Car_dropSpot). The money comes out of the cash box as they * leave. */ #include #include "game/game_internal.h" #include "world/car.h" #define SHOP_AWAY 3600.0f /* game s at the shops (there and back included) */ #define TO_CAR_MAX 120.0f /* s walking to the car: then they're in it anyway (stuck somewhere) */ #define BACK_OUT 10.0f /* m backed out of the parking spot ... */ #define BACK_SPEED 1.6f /* ... this fast ... */ #define TURN_TIME 3.0f /* ... turned round in this long (s) ... */ #define DRIVE_TOP 6.0f /* ... then off (m/s) */ #define DRIVE_ACCEL 1.6f #define DRIVE_BRAKE 1.4f #define HIDDEN_DEPTH 300.0f /* m under the ground while it's away (out of sight, out of the way) */ #define HELPERS 2 /* idle people near who come and unload too */ #define HELPER_NEAR 45.0f static Object *theCar(Game *game) { if (game->arrival.car) return game->arrival.car; for (int i = 0; i < game->world.count; i++) if (game->world.objects[i]->type == OBJ_CAR) return game->arrival.car = game->world.objects[i]; return NULL; } static int total(const int list[SHOP_ITEMS]) { int sum = 0; for (int k = 0; k < SHOP_ITEMS; k++) sum += list[k] * SHOP_PRICE[k]; return sum; } void game_shopStart(Game *game, Object *driver, const int list[SHOP_ITEMS]) { Errand *e = &game->errand; Object *car = theCar(game); if (!car || !driver || e->stage != ERRAND_NONE || !game_drivePath(game)) return; memset(e, 0, sizeof *e); for (int k = 0; k < SHOP_ITEMS; k++) e->list[k] = list[k]; e->driver = driver; e->stage = ERRAND_TO_CAR; if (Ai_isHelping(driver)) Ai_stopHelping(driver); driver->ai.mode = AI_IDLE; for (int h = 0; h < 2; h++) if (driver->hands.item[h] && driver->hands.state[h] == HAND_HOLDING) Hands_drop(driver, h); /* (hands free to drive) */ driver->ai.errand = true; driver->ai.errand_with = NULL; driver->ai.errand_there = false; driver->ai.errand_to = Car_door(car, 0); game->world.shopping = true; } /* Out of sight while it's away: down under the ground (and the driver in it). */ static void hide(Game *game, Object *car) { Car_place(&game->world, car, game->arrival.path[0], car->rotation.y); car->position.y -= HIDDEN_DEPTH; Object_syncColliders(car); for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; if (o->riding != car) continue; o->position.y -= HIDDEN_DEPTH; o->sit_at = o->position; Object_syncColliders(o); } } /* The boot full of what was on the list. */ static void loadUp(Object *car, const int list[SHOP_ITEMS]) { for (int k = 0; k < list[SHOP_FOOD]; k++) { /* (food and drink at the bottom, the material on top) */ Car_load(car, CARGO_WATER, NULL); Car_load(car, CARGO_BEER, NULL); Car_load(car, CARGO_SAUSAGES, NULL); Car_load(car, CARGO_FOOD, NULL); } for (int k = 0; k < list[SHOP_PAINT]; k++) Car_load(car, CARGO_PAINT, NULL); for (int k = 0; k < list[SHOP_FIXINGS]; k++) { Car_load(car, CARGO_SCREWS, NULL); Car_load(car, CARGO_NAILS, NULL); } for (int k = 0; k < list[SHOP_BOARDS]; k++) Car_load(car, CARGO_BOARDS, NULL); } /* Parked again: out, the boot up, unloading (the driver and a couple of idle people near). */ static void arrived(Game *game, Object *car) { Errand *e = &game->errand; Object *d = e->driver; Car_place(&game->world, car, game->arrival.path[ARRIVAL_SAMPLES - 1], game->arrival.park_yaw); if (d && d->riding) Car_getOut(d); Car_openBoot(car, true); int n = 0; for (int i = 0; i < game->world.count; i++) { Object *o = game->world.objects[i]; bool idle = o->ai.mode == AI_IDLE || o->ai.mode == AI_WANDER; if (o->type != OBJ_HUMAN || o->riding || o->lives_here || Body_isDown(&o->body) || o->in_tent || o->indoors > 0.0f) continue; if (o != d && (n >= HELPERS || !idle || o->ai.on_project || hypotf(o->position.x - car->position.x, o->position.z - car->position.z) > HELPER_NEAR)) continue; o->ai.unload = true; o->ai.unload_car = car; o->ai.unload_time = 0.0f; o->ai.side = (float)(n % 3 - 1); if (o != d) n++; } if (d) Ai_say(d, "We're back! Come on, help me unload - the boot's full."); static char line[96]; snprintf(line, sizeof line, "%s is back from the hardware store - help unload the car", d ? d->name : "The car"); say(game, line); game->world.shopping = false; memset(e, 0, sizeof *e); } void game_shop(Game *game, float dt) { Errand *e = &game->errand; if (e->stage == ERRAND_NONE) return; Object *car = theCar(game), *d = e->driver; Arrival *a = &game->arrival; if (!car || !game_drivePath(game)) { memset(e, 0, sizeof *e); game->world.shopping = false; return; } e->time += e->stage == ERRAND_AWAY ? game->world.game_dt : dt; Vec3 pos, dir; static char line[96]; switch (e->stage) { case ERRAND_TO_CAR: /* walking over (ai_project.c ai_errand); then in, the money out of the box, off */ if (!d || Body_isDown(&d->body)) { if (d) d->ai.errand = false; memset(e, 0, sizeof *e); game->world.shopping = false; return; } if (!d->ai.errand_there && e->time < TO_CAR_MAX) return; d->ai.errand = d->ai.errand_there = false; Car_openBoot(car, false); Car_getIn(car, d, 0); game->world.cash -= (float)total(e->list); e->stage = ERRAND_LEAVING; e->time = 0.0f; e->s = a->length; e->speed = 0.0f; snprintf(line, sizeof line, "%s drives off to the hardware store (%d EUR from the cash box)", d->name, total(e->list)); say(game, line); break; case ERRAND_LEAVING: { float back_to = a->length - BACK_OUT; if (e->s > back_to) { /* backing out, still nose to the barn */ e->s = fmaxf(back_to, e->s - BACK_SPEED * dt); game_driveAt(a, e->s, &pos, &dir); Car_place(&game->world, car, pos, yaw_facing(dir)); e->time = 0.0f; return; } game_driveAt(a, e->s, &pos, &dir); if (e->time < TURN_TIME) { /* turning round where it is */ float t = smoothstepf(0.0f, TURN_TIME, e->time); Car_place(&game->world, car, pos, yaw_facing(dir) + PI_F * t); return; } e->speed = fminf(e->speed + DRIVE_ACCEL * dt, DRIVE_TOP); e->s = fmaxf(0.0f, e->s - e->speed * dt); game_driveAt(a, e->s, &pos, &dir); Car_place(&game->world, car, pos, yaw_facing(vec3_scale(dir, -1.0f))); if (e->s <= 0.0f) { hide(game, car); e->stage = ERRAND_AWAY; e->time = 0.0f; } break; } case ERRAND_AWAY: if (e->time < SHOP_AWAY) return; loadUp(car, e->list); e->stage = ERRAND_BACK; e->time = 0.0f; e->s = 0.0f; e->speed = 3.0f; break; case ERRAND_BACK: { /* in again the way it came the first time, braking for its spot */ float left = a->length - e->s; float brake = sqrtf(fmaxf(0.0f, 2.0f * DRIVE_BRAKE * left)); e->speed = fminf(fminf(e->speed + DRIVE_ACCEL * dt, DRIVE_TOP), brake + 0.15f); e->s = fminf(a->length, e->s + e->speed * dt); game_driveAt(a, e->s, &pos, &dir); float yaw = yaw_facing(dir); if (left < 4.0f) yaw = lerpf(yaw, a->park_yaw, smoothstepf(4.0f, 0.0f, left)); Car_place(&game->world, car, pos, yaw); if (e->s >= a->length - 0.01f) arrived(game, car); break; } default: break; } } void game_shopLoaded(Game *game) { Errand *e = &game->errand; Object *car = theCar(game); game->world.shopping = e->stage != ERRAND_NONE; if (e->stage == ERRAND_NONE || !car || !game_drivePath(game)) return; if (e->stage == ERRAND_TO_CAR && e->driver) { /* (on the way over again) */ e->driver->ai.errand = true; e->driver->ai.errand_there = false; e->driver->ai.errand_to = Car_door(car, 0); } else if (e->stage == ERRAND_LEAVING) { /* (gone, as good as) */ hide(game, car); e->stage = ERRAND_AWAY; e->time = 0.0f; } else if (e->stage == ERRAND_AWAY) hide(game, car); else if (e->stage == ERRAND_BACK) arrived(game, car); }