feat(core): add minecart movement along rail networks
This commit is contained in:
1 parent
168627e065
commit
48d677dd5e
2 files changed
+234
No files matched your search
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "Rail.h"
|
||||
#include "Container.h"
|
||||
#include "World.h"
|
||||
#include "Math.h"
|
||||
|
||||
namespace vf {
|
||||
|
||||
enum class CartKind : uint8_t {
|
||||
Empty,
|
||||
Chest,
|
||||
Hopper,
|
||||
};
|
||||
|
||||
constexpr int chestCartSlots = 27;
|
||||
constexpr int hopperCartSlots = 5;
|
||||
|
||||
struct CartConfig {
|
||||
float maxSpeed = 0.4f;
|
||||
float friction = 0.02f;
|
||||
float slopeAccel = 0.05f;
|
||||
float poweredAccel = 0.06f;
|
||||
float poweredLaunch = 0.06f;
|
||||
float brakeFactor = 0.5f;
|
||||
float stopThreshold = 0.005f;
|
||||
int hopperCooldownTicks = 4;
|
||||
};
|
||||
|
||||
struct Minecart {
|
||||
Vec3i railPos;
|
||||
RailDir moveDir = RailDir::East;
|
||||
float speed = 0.0f;
|
||||
float offset = 0.0f;
|
||||
CartKind kind = CartKind::Empty;
|
||||
bool derailed = false;
|
||||
int hopperCooldown = 0;
|
||||
Container inventory;
|
||||
};
|
||||
|
||||
Minecart makeMinecart(const Vec3i& railPos, RailDir moveDir, CartKind kind);
|
||||
|
||||
// World-space centre of the cart: the rail cell centre pushed along the heading
|
||||
// by the sub-cell offset.
|
||||
Vec3 cartWorldPos(const Minecart& cart);
|
||||
|
||||
// Advances the cart one simulation tick: applies powered-rail boost or braking,
|
||||
// slope gravity and rolling friction, then walks the track by the resulting
|
||||
// speed, switching heading through curves and derailing at track ends. Hopper
|
||||
// carts also pull one item per cooldown from a container above their cell.
|
||||
void tickMinecart(World& world, Minecart& cart, const CartConfig& config,
|
||||
const std::function<bool(const Vec3i&)>& poweredRailActive,
|
||||
ContainerStore& store);
|
||||
|
||||
// Ticks a batch of carts, then resolves collisions deterministically in index
|
||||
// order: two carts that land on the same rail cell exchange momentum (equal
|
||||
// mass, elastic 1D) and separate. A head-on pair bounces apart; a moving cart
|
||||
// hitting a stalled one hands over its speed and stops.
|
||||
void stepMinecarts(World& world, std::vector<Minecart>& carts, const CartConfig& config,
|
||||
const std::function<bool(const Vec3i&)>& poweredRailActive,
|
||||
ContainerStore& store);
|
||||
|
||||
} // namespace vf
|
||||
@@ -0,0 +1,168 @@
|
||||
#include "vf/Minecart.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace vf {
|
||||
|
||||
namespace {
|
||||
|
||||
int slotsFor(CartKind kind) {
|
||||
switch (kind) {
|
||||
case CartKind::Chest: return chestCartSlots;
|
||||
case CartKind::Hopper: return hopperCartSlots;
|
||||
case CartKind::Empty: return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void applyRailForces(World& world, Minecart& cart, const CartConfig& config,
|
||||
const std::function<bool(const Vec3i&)>& poweredRailActive,
|
||||
RailShape shape) {
|
||||
Block block = world.blockAt(cart.railPos.x, cart.railPos.y, cart.railPos.z);
|
||||
|
||||
if (block == Block::PoweredRail) {
|
||||
if (poweredRailActive(cart.railPos)) {
|
||||
cart.speed += config.poweredAccel;
|
||||
if (cart.speed < config.poweredLaunch) cart.speed = config.poweredLaunch;
|
||||
} else {
|
||||
cart.speed *= config.brakeFactor;
|
||||
}
|
||||
} else {
|
||||
cart.speed *= (1.0f - config.friction);
|
||||
}
|
||||
|
||||
if (isAscending(shape)) {
|
||||
RailDir up = ascendUpDir(shape);
|
||||
if (cart.moveDir == up) cart.speed -= config.slopeAccel;
|
||||
else if (cart.moveDir == opposite(up)) cart.speed += config.slopeAccel;
|
||||
}
|
||||
|
||||
if (cart.speed < 0.0f) {
|
||||
cart.moveDir = opposite(cart.moveDir);
|
||||
cart.speed = -cart.speed;
|
||||
}
|
||||
|
||||
cart.speed = std::min(cart.speed, config.maxSpeed);
|
||||
if (cart.speed < config.stopThreshold) cart.speed = 0.0f;
|
||||
}
|
||||
|
||||
void serviceHopperCart(Minecart& cart, const CartConfig& config, ContainerStore& store) {
|
||||
if (cart.kind != CartKind::Hopper) return;
|
||||
if (cart.hopperCooldown > 0) {
|
||||
--cart.hopperCooldown;
|
||||
return;
|
||||
}
|
||||
Container* srcAbove = store.at(cart.railPos + Vec3i{0, 1, 0});
|
||||
if (srcAbove && srcAbove->transferTo(cart.inventory, 1) > 0)
|
||||
cart.hopperCooldown = config.hopperCooldownTicks;
|
||||
}
|
||||
|
||||
Vec3 velocityVector(const Minecart& cart) {
|
||||
Vec3i step = railDelta(cart.moveDir);
|
||||
return Vec3{static_cast<float>(step.x) * cart.speed,
|
||||
static_cast<float>(step.y) * cart.speed,
|
||||
static_cast<float>(step.z) * cart.speed};
|
||||
}
|
||||
|
||||
void assignVelocity(Minecart& cart, const Vec3& vel) {
|
||||
float ax = std::fabs(vel.x);
|
||||
float az = std::fabs(vel.z);
|
||||
if (ax < 1e-6f && az < 1e-6f) {
|
||||
cart.speed = 0.0f;
|
||||
return;
|
||||
}
|
||||
if (ax >= az) {
|
||||
cart.moveDir = vel.x >= 0.0f ? RailDir::East : RailDir::West;
|
||||
cart.speed = ax;
|
||||
} else {
|
||||
cart.moveDir = vel.z >= 0.0f ? RailDir::South : RailDir::North;
|
||||
cart.speed = az;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Minecart makeMinecart(const Vec3i& railPos, RailDir moveDir, CartKind kind) {
|
||||
Minecart cart;
|
||||
cart.railPos = railPos;
|
||||
cart.moveDir = moveDir;
|
||||
cart.kind = kind;
|
||||
cart.inventory = Container{slotsFor(kind)};
|
||||
return cart;
|
||||
}
|
||||
|
||||
Vec3 cartWorldPos(const Minecart& cart) {
|
||||
Vec3i step = railDelta(cart.moveDir);
|
||||
return Vec3{
|
||||
static_cast<float>(cart.railPos.x) + 0.5f + static_cast<float>(step.x) * cart.offset,
|
||||
static_cast<float>(cart.railPos.y) + 0.5f + static_cast<float>(step.y) * cart.offset,
|
||||
static_cast<float>(cart.railPos.z) + 0.5f + static_cast<float>(step.z) * cart.offset,
|
||||
};
|
||||
}
|
||||
|
||||
void tickMinecart(World& world, Minecart& cart, const CartConfig& config,
|
||||
const std::function<bool(const Vec3i&)>& poweredRailActive,
|
||||
ContainerStore& store) {
|
||||
if (cart.derailed) return;
|
||||
if (!isRailBlock(world.blockAt(cart.railPos.x, cart.railPos.y, cart.railPos.z))) {
|
||||
cart.derailed = true;
|
||||
cart.speed = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
bool switched = poweredRailActive(cart.railPos);
|
||||
RailShape shape = inferRailShape(world, cart.railPos, switched);
|
||||
applyRailForces(world, cart, config, poweredRailActive, shape);
|
||||
|
||||
cart.offset += cart.speed;
|
||||
while (cart.offset >= 1.0f && cart.speed > 0.0f) {
|
||||
cart.offset -= 1.0f;
|
||||
bool junction = poweredRailActive(cart.railPos);
|
||||
RailStep next = advanceOnRail(world, cart.railPos, cart.moveDir, junction);
|
||||
if (!next.valid) {
|
||||
cart.derailed = true;
|
||||
cart.speed = 0.0f;
|
||||
cart.offset = 0.0f;
|
||||
return;
|
||||
}
|
||||
cart.railPos = next.nextPos;
|
||||
cart.moveDir = next.exitDir;
|
||||
}
|
||||
|
||||
serviceHopperCart(cart, config, store);
|
||||
}
|
||||
|
||||
void stepMinecarts(World& world, std::vector<Minecart>& carts, const CartConfig& config,
|
||||
const std::function<bool(const Vec3i&)>& poweredRailActive,
|
||||
ContainerStore& store) {
|
||||
std::vector<Vec3i> previous(carts.size());
|
||||
for (std::size_t i = 0; i < carts.size(); ++i) previous[i] = carts[i].railPos;
|
||||
|
||||
for (Minecart& cart : carts)
|
||||
tickMinecart(world, cart, config, poweredRailActive, store);
|
||||
|
||||
for (std::size_t i = 0; i < carts.size(); ++i) {
|
||||
for (std::size_t j = i + 1; j < carts.size(); ++j) {
|
||||
Minecart& a = carts[i];
|
||||
Minecart& b = carts[j];
|
||||
if (a.derailed || b.derailed) continue;
|
||||
if (previous[i] == previous[j]) continue;
|
||||
|
||||
bool sameCell = a.railPos == b.railPos;
|
||||
bool passedThrough = a.railPos == previous[j] && b.railPos == previous[i];
|
||||
if (!sameCell && !passedThrough) continue;
|
||||
|
||||
Vec3 va = velocityVector(a);
|
||||
Vec3 vb = velocityVector(b);
|
||||
assignVelocity(a, vb);
|
||||
assignVelocity(b, va);
|
||||
|
||||
a.railPos = previous[i];
|
||||
b.railPos = previous[j];
|
||||
a.offset = 0.0f;
|
||||
b.offset = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vf
|
||||
Reference in new issue
Block a user