second commit

This commit is contained in:
2026-10-18 11:39:58 -04:00
parent aff6a4f902
commit 78e77a0cdc
4 changed files with 466 additions and 55 deletions
+260 -50
View File
@@ -93,6 +93,11 @@ Engine::Engine()
glfwSetMouseButtonCallback(window->handle, mouse_button_callback);
glfwSwapInterval(1);
// Start from the real framebuffer size (can differ from the requested size under DPI scaling)
int fbw, fbh;
glfwGetFramebufferSize(window->handle, &fbw, &fbh);
framebuffer_size_callback(window->handle, fbw, fbh);
rendersystem = new RenderSystem();
rendersystem->Initialize();
physicsengine = new Physics();
@@ -243,17 +248,6 @@ void Engine::Initiate()
crosshair->Dot = true;
crosshair->Apply();
TextBox* tb = new TextBox();
tb->font = arial;
tb->text = "ASDASD\nASDASD\nADSADSA";
tb->fontsize = 0.5f;
tb->Color = { 0.0f, 0.0f, 0.0f };
tb->t2d.position = { 0.5f, 0.5f, 0.0f, 0.0f };
tb->t2d.center = { 0.0f, 0.0f };
tb->textCenter = { 0.0f, 0.5f };
tb->t2d.size = { 0.2f, 0.1f, 0.0f, 0.0f };
tb->BindToRenderSystem();
TextBox* fpsCounter = new TextBox();
fpsCounter->font = arial;
fpsCounter->text = "TEST1231231123";
@@ -295,7 +289,7 @@ void Engine::Initiate()
grappleText->t2d.position = { 0.5f, 1.0f, 0.0f, -20.0f };
grappleText->t2d.center = { 0.5f, 1.0f };
grappleText->textCenter = { 0.5f, 0.5f };
grappleText->t2d.size = { 0.0f, 0.0f, 300.0f, 40.0f };
grappleText->t2d.size = { 0.0f, 0.0f, 560.0f, 40.0f };
grappleText->BindToRenderSystem();
ImageBox* testgui = new ImageBox();
@@ -325,7 +319,7 @@ void Engine::Initiate()
Mesh* debugcube = CreateCubeMesh();
debugcube->name = "debugcube";
debugcube->t.ScaleTo({ 0.8f, 0.8f, 0.8f });
debugcube->BindToRenderSystem();
// Punch hitbox only: intentionally not bound to the render system, so it isn't drawn
mainf->AddChild(debugcube);
Mesh* floor = CreateCubeMesh();
@@ -352,22 +346,6 @@ void Engine::Initiate()
wallBody->friction = 0.0f;
physicsengine->AddObject(wallBody);
Mesh* cube = CreateCubeMesh();
cube->name = "cube";
cube->meshTexture = blanktex;
cube->t.ScaleTo({ 1.0f, 1.0f, 1.0f });
cube->t.TranslateTo({ 5.0f, 4.0f, 6.5f });
cube->BindToRenderSystem();
mainf->AddChild(cube);
Mesh* cube2 = CreateCubeMesh();
cube2->name = "cubee";
cube2->meshTexture = blanktex;
cube2->t.ScaleTo({ 1.0f, 1.0f, 1.0f });
cube2->t.TranslateTo({ 10.0f, 4.0f, 10.5f });
cube2->BindToRenderSystem();
mainf->AddChild(cube2);
// Ground check
auto IsGrounded = [&]() -> bool {
Ray downRay;
@@ -390,16 +368,16 @@ void Engine::Initiate()
// body = 50 damage (two shots). Respawns the target when it dies.
// Movement tuning
const float airControl = 0.5f; // fraction of ground acceleration available in the air
const float grappleRange = 4.0f; // metres from the player
const float grappleRange = 8.0f; // metres from the player
const float grappleSpeed = 28.0f; // launch speed toward the hit point
const float grappleCooldownTime = 2.5f; // seconds
float grappleCooldown = 0.0f;
// Slide (key C): locked direction, committed for slideDuration, speed eases from
// Slide (key Left Ctrl): locked direction, committed for slideDuration, speed eases from
// slideBoost x ground top speed down to 1x by slideFalloffTime.
const float slideBoost = 2.0f;
const float slideDuration = 0.8f;
const float slideFalloffTime = 0.7f;
const float slideDuration = 1.2f;
const float slideFalloffTime = 1.05f;
const float slideDropY = 0.65f; // standing centre height (0.9) minus lying (0.25)
const float standEyeOffset = 0.7f; // eyes 0.7 above body centre (body centre 0.9, height 1.8)
const float slideEyeOffset = 0.25f; // lying down, eyes just above the floor
@@ -408,17 +386,119 @@ void Engine::Initiate()
float slideTime = 0.0f;
glm::vec3 slideDir(0.0f);
// Shockwave (key Q): projectile that explodes on the first surface it touches. The blast
// launches the player, the NPC and loose props away from the impact point. No damage.
const float shockSpeed = 30.0f;
const float shockGravity = 6.0f;
const float shockLifetime = 3.0f;
const float shockRadius = 7.0f;
const float shockPower = 26.0f; // launch speed at the centre of the blast
const float shockCooldownTime = 6.0f;
const float blastShowTime = 0.25f;
const glm::vec3 hiddenPos(0.0f, -1000.0f, 0.0f);
float shockCooldown = 0.0f;
bool shockActive = false;
float shockAge = 0.0f;
float blastTime = 0.0f;
glm::vec3 shockVel(0.0f);
Mesh* shockMesh = CreateCubeMesh();
shockMesh->name = "shockwave";
shockMesh->meshTexture = blanktex;
shockMesh->t.ScaleTo({ 0.3f, 0.3f, 0.3f });
shockMesh->t.TranslateTo(hiddenPos);
shockMesh->BindToRenderSystem();
mainf->AddChild(shockMesh);
Mesh* blastMesh = CreateCubeMesh();
blastMesh->name = "shockblast";
blastMesh->meshTexture = blanktex;
blastMesh->t.TranslateTo(hiddenPos);
blastMesh->BindToRenderSystem();
mainf->AddChild(blastMesh);
// fromNet = another player's blast: we only launch ourselves (the other machines launch
// themselves the same way, since remote players are just puppets on this machine).
auto Explode = [&](const glm::vec3& center, bool fromNet = false,
float radius = shockRadius, float power = shockPower) {
auto launch = [&](const glm::vec3& pos, p* body) {
const glm::vec3 offset = pos - center;
const float dist = glm::length(offset);
if (dist >= radius) return;
// Away from the blast, biased upward so grounded targets actually lift off
glm::vec3 dir = dist > 0.001f ? offset / dist : glm::vec3(0.0f, 1.0f, 0.0f);
dir.y = std::max(dir.y, 0.0f) + 0.6f;
dir = glm::normalize(dir);
const float falloff = 1.0f - 0.7f * (dist / radius); // 100% at centre, 30% at the edge
body->linearvelocity += dir * power * falloff;
body->WakeUp();
if (body == characterBody && sliding) {
// The slide overrides horizontal velocity every frame, so end it and stand back up
sliding = false;
glm::vec3 p0 = testcharacter->body->t.GetTranslation();
p0.y += slideDropY + 0.02f;
testcharacter->body->t.TranslateTo(p0);
}
};
launch(testcharacter->body->t.GetTranslation(), characterBody);
if (!fromNet) {
launch(npc->body->t.GetTranslation(), npcBody);
for (p* obj : physicsengine->GetObjects()) {
if (!obj->velocity || obj == characterBody || obj == npcBody) continue;
launch(obj->pointer->t.GetTranslation(), obj);
}
NetBlast nb;
nb.center = center;
nb.radius = radius;
nb.power = power;
if (lan && lan->IsActive()) lan->SendBlast(nb);
shockActive = false;
shockMesh->t.TranslateTo(hiddenPos);
}
blastMesh->t.TranslateTo(center);
blastMesh->t.ScaleTo(glm::vec3(0.5f));
blastTime = blastShowTime;
camera->AddScreenshake(0.25f);
};
const int maxHealth = 100;
const int bodyDamage = 50;
const int punchDamage = 25;
const float headFraction = 0.8f;
// Random respawn spot on the floor (NPC and player), kept out of the wall (x -9.5..-6.5, z -11..11)
std::mt19937 respawnRng(std::random_device{}());
auto RandomSpawn = [&]() -> glm::vec3 {
std::uniform_real_distribution<float> spread(-20.0f, 20.0f);
for (;;) {
const float x = spread(respawnRng), z = spread(respawnRng);
if (x > -9.5f && x < -6.5f && z > -11.0f && z < 11.0f) continue;
return { x, 0.9f, z };
}
};
auto Shoot = [&]() {
Ray ray;
ray.origin = camera->t.GetTranslation();
ray.direction = glm::normalize(camera->t.GetFrontVector());
auto targetHit = IsRayInT(ray, npc->body->t);
if (!targetHit.has_value()) return;
const float targetDist = glm::distance(ray.origin, targetHit.value());
// Nearest shootable target along the ray: the NPC or any remote (LAN) player
float targetDist = 1e9f;
glm::vec3 targetPoint(0.0f);
t* targetT = nullptr;
int targetRemoteId = -1; // -1 = the NPC
auto consider = [&](t& tr, int remoteId) {
auto h = IsRayInT(ray, tr);
if (!h.has_value()) return;
const float d = glm::distance(ray.origin, h.value());
if (d < targetDist) { targetDist = d; targetPoint = h.value(); targetT = &tr; targetRemoteId = remoteId; }
};
consider(npc->body->t, -1);
for (auto& kv : remotes) consider(kv.second.character->body->t, kv.first);
if (!targetT) return;
// Walls/props in front of the target block the shot
for (p* obj : physicsengine->GetObjects()) {
@@ -429,16 +509,27 @@ void Engine::Initiate()
return;
}
const float heightFrac = (targetHit->y - npc->body->t.GetTranslation().y)
/ npc->body->t.GetScale().y + 0.5f;
const float heightFrac = (targetPoint.y - targetT->GetTranslation().y)
/ targetT->GetScale().y + 0.5f;
const bool headshot = heightFrac >= headFraction;
npc->body->Health -= headshot ? maxHealth : bodyDamage;
std::cout << (headshot ? "HEADSHOT" : "BODY SHOT") << " - NPC HP " << npc->body->Health << "\n";
const int damage = headshot ? maxHealth : bodyDamage;
camera->AddScreenshake(0.05f);
if (targetRemoteId >= 0) {
// Another player: their machine owns their health, so tell it
NetHit hit;
hit.damage = damage;
if (lan && lan->IsActive()) lan->SendHit((uint8_t)targetRemoteId, hit);
std::cout << (headshot ? "HEADSHOT" : "BODY SHOT") << " - player " << targetRemoteId << "\n";
return;
}
npc->body->Health -= damage;
std::cout << (headshot ? "HEADSHOT" : "BODY SHOT") << " - NPC HP " << npc->body->Health << "\n";
if (npc->body->Health <= 0) {
npc->body->Health = maxHealth;
npc->body->t.TranslateTo({ 3.0f, 0.9f, 3.0f });
npc->body->t.TranslateTo(RandomSpawn());
npcBody->linearvelocity = glm::vec3(0.0f);
npcBody->WakeUp();
}
@@ -454,7 +545,8 @@ void Engine::Initiate()
const glm::vec3 camFront = camera->t.GetFrontVector();
const glm::vec3 camRight = camera->t.GetRightVector();
const float movespeed = 3.0f * 1.3f;
const float walkScale = 0.5f; // WASD speed relative to the original 3.9
const float movespeed = 3.0f * 1.3f * walkScale;
// --------------------------------------------------------------------
// Input � mouse
@@ -516,10 +608,30 @@ void Engine::Initiate()
npcBody->WakeUp();
npcBody->linearvelocity += playerFront * 5.0f + glm::vec3(0.0f, 3.0f, 0.0f);
camera->AddScreenshake(0.15f); // landing a punch feels impactful
npc->body->Health -= punchDamage;
std::cout << "PUNCH - NPC HP " << npc->body->Health << "\n";
if (npc->body->Health <= 0) {
npc->body->Health = maxHealth;
npc->body->t.TranslateTo(RandomSpawn());
npcBody->linearvelocity = glm::vec3(0.0f);
npcBody->WakeUp();
}
}
else {
std::cout << "MISSED\n";
}
// Punch LAN players too: their machine applies the damage and knockback
for (auto& kv : remotes) {
if (!TInT(debugcube->t, kv.second.character->body->t).has_value()) continue;
NetHit ph;
ph.damage = punchDamage;
ph.knockback = playerFront * 5.0f + glm::vec3(0.0f, 3.0f, 0.0f);
if (lan && lan->IsActive()) lan->SendHit((uint8_t)kv.first, ph);
camera->AddScreenshake(0.15f);
std::cout << "PUNCH - player " << kv.first << "\n";
}
}
if (buffer.GetCode() == GLFW_KEY_LEFT_SHIFT && buffer.IsPress() && !sliding) {
glm::vec3 dashDir = glm::length(inputVel) > 0.0f
@@ -529,7 +641,7 @@ void Engine::Initiate()
characterBody->linearvelocity.z += dashDir.z * 18.0f;
camera->AddScreenshake(0.04f);
}
if (buffer.GetCode() == 'C' && buffer.IsPress() && !sliding && IsGrounded()) {
if (buffer.GetCode() == GLFW_KEY_LEFT_CONTROL && buffer.IsPress() && !sliding && IsGrounded()) {
const glm::vec3 horizVel(characterBody->linearvelocity.x, 0.0f, characterBody->linearvelocity.z);
if (glm::length(inputVel) > 0.0f || glm::length(horizVel) > 1.0f) {
slideDir = glm::length(inputVel) > 0.0f ? glm::normalize(inputVel) : glm::normalize(horizVel);
@@ -570,12 +682,21 @@ void Engine::Initiate()
camera->AddScreenshake(0.1f);
}
}
if (buffer.GetCode() == 'Q' && buffer.IsPress() && !shockActive && shockCooldown <= 0.0f) {
const glm::vec3 front = glm::normalize(camFront);
shockMesh->t.TranslateTo(camera->t.GetTranslation() + front * 0.6f);
shockVel = front * shockSpeed;
shockActive = true;
shockAge = 0.0f;
shockCooldown = shockCooldownTime;
camera->AddScreenshake(0.05f);
}
if (buffer.GetCode() == 'G' && buffer.IsPress()) {
camera->AddScreenshake(0.2f);
}
if (buffer.GetCode() == GLFW_KEY_SPACE && buffer.IsPress()) {
if (!sliding && IsGrounded())
characterBody->linearvelocity.y = 5.0f;
characterBody->linearvelocity.y = 12.0f;
}
}
@@ -594,7 +715,7 @@ void Engine::Initiate()
// Direction is locked; speed eases from boost x down to 1x (quadratic: drops fastest early)
slideTime += deltatime;
const float u = glm::clamp(slideTime / slideFalloffTime, 0.0f, 1.0f);
const float speed = groundTopSpeed * (1.0f + (slideBoost - 1.0f) * (1.0f - u) * (1.0f - u));
const float speed = (groundTopSpeed / walkScale) * (1.0f + (slideBoost - 1.0f) * (1.0f - u) * (1.0f - u));
characterBody->linearvelocity.x = slideDir.x * speed;
characterBody->linearvelocity.z = slideDir.z * speed;
@@ -634,6 +755,48 @@ void Engine::Initiate()
debugcube->t.RotateToQuaternion(LookAt(-playerFront));
}
// Shockwave projectile: arc forward, explode on the first collider along this frame's path
shockCooldown = std::max(shockCooldown - deltatime, 0.0f);
if (shockActive) {
shockVel.y -= shockGravity * deltatime;
shockAge += deltatime;
const glm::vec3 from = shockMesh->t.GetTranslation();
const glm::vec3 step = shockVel * deltatime;
const float stepLen = glm::length(step);
Ray ray;
ray.origin = from;
ray.direction = step / stepLen;
float bestDist = stepLen;
glm::vec3 bestPoint(0.0f);
bool found = false;
for (p* obj : physicsengine->GetObjects()) {
if (obj == characterBody || !obj->collision) continue;
auto hit = IsRayInT(ray, obj->pointer->t);
if (!hit.has_value()) continue;
const float d = glm::distance(from, hit.value());
if (d <= bestDist) { bestDist = d; bestPoint = hit.value(); found = true; }
}
if (found) Explode(bestPoint);
else if (shockAge >= shockLifetime) {
shockActive = false;
shockMesh->t.TranslateTo(hiddenPos);
}
else shockMesh->t.TranslateTo(from + step);
}
if (blastTime > 0.0f) {
blastTime -= deltatime;
if (blastTime <= 0.0f) {
blastMesh->t.TranslateTo(hiddenPos);
}
else {
// Expand to the full blast radius over its lifetime
const float u = 1.0f - blastTime / blastShowTime;
blastMesh->t.ScaleTo(glm::vec3(0.5f + (shockRadius * 2.0f - 0.5f) * u));
}
}
rain->t.TranslateTo(camera->t.GetTranslation() + glm::vec3(0.0f, 20.0f, 0.0f));
for (int i = 0; i < 30; i++) rain->Emit();
@@ -647,8 +810,7 @@ void Engine::Initiate()
camera->t.TranslateTo(testcharacter->body->t.GetTranslation() + glm::vec3(0.0f, eyeOffset, 0.0f));
Sound* music = static_cast<Sound*>(mainf->GetFirstChildOfName("music"));
Mesh* cube = static_cast<Mesh*>(mainf->GetFirstChildOfName("cube"));
music->Update3DPosition(cube->t.GetTranslation());
music->Update3DPosition(5.0f, 4.0f, 6.5f); // where the old floating cube was
soundsystem->SetListenerPosition(
camera->t.GetTranslation(),
@@ -675,10 +837,21 @@ void Engine::Initiate()
}
fpsCounter->text = std::to_string(measuredFps) + " FPS";
}
// Player respawn: at 0 HP or after falling off the map, teleport to a random spot
if (testcharacter->body->Health <= 0 || testcharacter->body->t.GetTranslation().y < -20.0f) {
testcharacter->body->Health = maxHealth;
testcharacter->body->t.TranslateTo(RandomSpawn());
characterBody->linearvelocity = glm::vec3(0.0f);
characterBody->WakeUp();
sliding = false;
}
hpText->text = std::to_string(std::max(testcharacter->body->Health, 0)) + "/100 HP";
grappleText->text = grappleCooldown > 0.0f
? "GRAPPLE " + std::to_string(grappleCooldown).substr(0, 3) + "s"
: "GRAPPLE READY";
grappleText->text += shockCooldown > 0.0f
? " | SHOCKWAVE " + std::to_string(shockCooldown).substr(0, 3) + "s"
: " | SHOCKWAVE READY (Q)";
// --------------------------------------------------------------------
// Step systems
@@ -702,7 +875,20 @@ void Engine::Initiate()
const double now = glfwGetTime();
std::vector<NetUpdate> updates;
lan->Receive(updates);
std::vector<NetBlast> blasts;
std::vector<NetHit> hits;
lan->Receive(updates, &blasts, &hits);
for (const NetBlast& b : blasts)
Explode(b.center, true, b.radius, b.power);
// Damage from other players (respawn handles dying, see the player respawn check)
for (const NetHit& h : hits) {
testcharacter->body->Health -= h.damage;
characterBody->linearvelocity += h.knockback;
characterBody->WakeUp();
camera->AddScreenshake(0.15f);
std::cout << "HIT by player " << (int)h.sender << " for " << h.damage
<< " - HP " << testcharacter->body->Health << "\n";
}
for (const NetUpdate& u : updates) {
auto it = remotes.find(u.id);
if (it == remotes.end()) {
@@ -757,10 +943,31 @@ void Engine::Initiate()
// ============================================================================
void framebuffer_size_callback(GLFWwindow* window, int w, int h)
{
if (w <= 0 || h <= 0) return; // minimised
// Everything pixel-based (viewport, projection aspect, GUI layout, scissor) reads these
::window->width = w;
::window->height = h;
glViewport(0, 0, w, h);
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
// F11: toggle fullscreen on the primary monitor, restoring the old window rect on exit
if (key == GLFW_KEY_F11 && action == GLFW_PRESS) {
static int wx = 100, wy = 100, ww = 1280, wh = 720;
if (glfwGetWindowMonitor(window)) {
glfwSetWindowMonitor(window, nullptr, wx, wy, ww, wh, 0);
}
else {
glfwGetWindowPos(window, &wx, &wy);
glfwGetWindowSize(window, &ww, &wh);
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
}
glfwSwapInterval(1);
}
if (action == GLFW_PRESS) keyboard->KeyDown(key);
else if (action == GLFW_RELEASE) keyboard->KeyUp(key);
}
@@ -773,8 +980,11 @@ void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
mouse->OnMouseMove(static_cast<float>(xposIn), static_cast<float>(yposIn));
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPos(window, ::window->width / 2, ::window->height / 2);
mouse->OnMouseMove(::window->width / 2.0f, ::window->height / 2.0f);
// Cursor coordinates are in window units, which can differ from framebuffer pixels (DPI scaling)
int ww, wh;
glfwGetWindowSize(window, &ww, &wh);
glfwSetCursorPos(window, ww / 2.0, wh / 2.0);
mouse->OnMouseMove(ww / 2.0f, wh / 2.0f);
}
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)