diff --git a/src/core/application.cpp b/src/core/application.cpp index 8c94289a..d99f99f1 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -3428,10 +3428,15 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float glm::vec3 spawnCanonical = core::coords::serverToCanonical(glm::vec3(x, y, z)); glm::vec3 spawnRender = core::coords::canonicalToRender(spawnCanonical); - // Set camera position + // Set camera position and facing from server orientation if (renderer->getCameraController()) { + float yawDeg = 0.0f; + if (gameHandler) { + float canonicalYaw = gameHandler->getMovementInfo().orientation; + yawDeg = 180.0f - glm::degrees(canonicalYaw); + } renderer->getCameraController()->setOnlineMode(true); - renderer->getCameraController()->setDefaultSpawn(spawnRender, 0.0f, -15.0f); + renderer->getCameraController()->setDefaultSpawn(spawnRender, yawDeg, -15.0f); renderer->getCameraController()->reset(); } diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 5ff720b3..c8dba11f 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -8814,16 +8814,16 @@ void GameHandler::checkAreaTriggers() { bool inside = false; if (at.radius > 0.0f) { - // Sphere trigger — small minimum so player must be near the portal - float effectiveRadius = std::max(at.radius, 12.0f); + // Sphere trigger — use actual radius, with small floor for very tiny triggers + float effectiveRadius = std::max(at.radius, 3.0f); float dx = px - at.x; float dy = py - at.y; float dz = pz - at.z; float distSq = dx * dx + dy * dy + dz * dz; inside = (distSq <= effectiveRadius * effectiveRadius); } else if (at.boxLength > 0.0f || at.boxWidth > 0.0f || at.boxHeight > 0.0f) { - // Box trigger — small minimum so player must walk into the portal area - float boxMin = 16.0f; + // Box trigger — use actual size, with small floor for tiny triggers + float boxMin = 4.0f; float effLength = std::max(at.boxLength, boxMin); float effWidth = std::max(at.boxWidth, boxMin); float effHeight = std::max(at.boxHeight, boxMin); diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 925c020b..0998694a 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1116,9 +1116,24 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) { // Ground clutter (grass/pebbles/detail cards) should never block camera/movement. gpuModel.collisionNoBlock = true; } - // Spell effect models: particle-dominated with minimal geometry (e.g. LevelUp.m2) - gpuModel.isSpellEffect = hasParticles && model.vertices.size() <= 200 && - model.particleEmitters.size() >= 3; + // Spell effect / pure-visual models: particle-dominated with minimal geometry, + // or named effect models (light shafts, portals, emitters, spotlights) + bool effectByName = + (lowerName.find("lightshaft") != std::string::npos) || + (lowerName.find("volumetriclight") != std::string::npos) || + (lowerName.find("instanceportal") != std::string::npos) || + (lowerName.find("mageportal") != std::string::npos) || + (lowerName.find("worldtreeportal") != std::string::npos) || + (lowerName.find("particleemitter") != std::string::npos) || + (lowerName.find("bubbles") != std::string::npos) || + (lowerName.find("spotlight") != std::string::npos) || + (lowerName.find("hazardlight") != std::string::npos) || + (lowerName.find("lavasplash") != std::string::npos) || + (lowerName.find("lavabubble") != std::string::npos) || + (lowerName.find("wisps") != std::string::npos); + gpuModel.isSpellEffect = effectByName || + (hasParticles && model.vertices.size() <= 200 && + model.particleEmitters.size() >= 3); // Water vegetation: cattails, reeds, bulrushes, kelp, seaweed, lilypad near water gpuModel.isWaterVegetation = (lowerName.find("cattail") != std::string::npos) || @@ -2381,8 +2396,14 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const // Select pipeline based on blend mode uint8_t effectiveBlendMode = batch.blendMode; - if (model.isSpellEffect && (effectiveBlendMode == 4 || effectiveBlendMode == 5)) { - effectiveBlendMode = 3; + if (model.isSpellEffect) { + // Effect models: force additive blend for opaque/cutout batches + // so the mesh renders as a transparent glow, not a solid object + if (effectiveBlendMode <= 1) { + effectiveBlendMode = 3; // additive + } else if (effectiveBlendMode == 4 || effectiveBlendMode == 5) { + effectiveBlendMode = 3; + } } if (forceCutout) { effectiveBlendMode = 1;