diff --git a/resources/config2/teleports_uni_exterior.json b/resources/config2/teleports_uni_exterior.json index 6d22b3a..b320711 100644 --- a/resources/config2/teleports_uni_exterior.json +++ b/resources/config2/teleports_uni_exterior.json @@ -20,6 +20,8 @@ "positionZ": -9.65, "radius": 1.5, "active": true, + "automatic": true, + "automaticRadius": 1.4, "destinationLocation": "uni_interior", "destinationPositionX": 1.05971, "destinationPositionY": 0.0, diff --git a/resources/config2/teleports_uni_interior.json b/resources/config2/teleports_uni_interior.json index 1626099..96eb806 100644 --- a/resources/config2/teleports_uni_interior.json +++ b/resources/config2/teleports_uni_interior.json @@ -7,6 +7,8 @@ "positionZ": -10.1046, "radius": 1.8, "active": true, + "automatic": true, + "automaticRadius": 1.7, "destinationLocation": "uni_exterior", "destinationPositionX": 8.9718, "destinationPositionY": 0.0, diff --git a/src/Game.cpp b/src/Game.cpp index f99e6f2..22809ba 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -359,6 +359,16 @@ namespace ZL menuManager.tutorialShowTaxiHint(); }; + locations["location_dorm"]->tutorialInteractiveObjectsLocked = true; + + menuManager.tutorialUnlockInteractiveObjectsFunc = [this]() + { + if (locations["location_dorm"]) + { + locations["location_dorm"]->tutorialInteractiveObjectsLocked = false; + } + }; + menuManager.callTaxiFunc = [this]() { if (locations["location_dorm"]) @@ -386,6 +396,7 @@ namespace ZL if (currentLocation->player) { currentLocation->player->position = destPos; currentLocation->player->setTarget(destPos); + currentLocation->player->stopInPlace(); currentLocation->player->facingAngle = destRotY; currentLocation->player->targetFacingAngle = destRotY; } diff --git a/src/Location.cpp b/src/Location.cpp index fa0c989..0affede 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -213,6 +213,8 @@ namespace ZL item.value("positionZ", 0.0f) ); tz.radius = item.value("radius", 0.0f); + tz.automatic = item.value("automatic", false); + tz.automaticRadius = item.value("automaticRadius", tz.radius); tz.destinationLocation = item.value("destinationLocation", ""); tz.destinationPosition = Eigen::Vector3f( item.value("destinationPositionX", 0.0f), @@ -1406,7 +1408,7 @@ namespace ZL for (auto& [idx, t] : npcBumpsPlayerCooldown) t -= deltaS; // Check if player reached target interactive object - if (targetInteractiveObject && player && !targetInteractiveObject->isAnimating) { + if (!tutorialInteractiveObjectsLocked && targetInteractiveObject && player && !targetInteractiveObject->isAnimating) { const Eigen::Vector3f& approachTarget = targetInteractiveObject->hasInteractionPosition ? targetInteractiveObject->interactionPosition : targetInteractiveObject->position; @@ -1467,6 +1469,32 @@ namespace ZL return; } } + + if (player) { + for (auto& tz : teleportZones) { + if (!tz.active || !tz.automatic) continue; + const float autoR = tz.automaticRadius > 0.0f ? tz.automaticRadius : tz.radius; + const float dist = (player->position - tz.position).norm(); + + if (!tz.automaticInitialized) { + // If the player starts inside the zone (e.g. just arrived here via teleport), + // mark them as already inside so they must leave before it can fire. + tz.automaticPlayerInside = (dist <= autoR); + tz.automaticInitialized = true; + continue; + } + + const bool isInside = (dist <= autoR); + if (isInside && !tz.automaticPlayerInside) { + tz.automaticPlayerInside = true; + std::cout << "[TELEPORT] Auto-teleport triggered by zone '" << tz.id << "'" << std::endl; + if (onTeleport) onTeleport(tz.destinationLocation, tz.destinationPosition, tz.destinationRotationY); + return; + } else if (!isInside) { + tz.automaticPlayerInside = false; + } + } + } } void Location::handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my) diff --git a/src/Location.h b/src/Location.h index 298fb5c..87f4487 100644 --- a/src/Location.h +++ b/src/Location.h @@ -102,6 +102,8 @@ namespace ZL bool isNight = false; bool isDawn = false; + bool tutorialInteractiveObjectsLocked = false; + // Called when the player successfully taps the ground and a floor walk target is set. // Used by the tutorial system to detect the "tap to walk" gesture. std::function onPlayerFloorWalk; diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index 8356931..dd36b23 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -727,6 +727,11 @@ namespace ZL { case TutorialStep::Step3: tutorialStep = TutorialStep::Step4; nextRoot = hudStep4Root; + if (tutorialUnlockInteractiveObjectsFunc) + { + tutorialUnlockInteractiveObjectsFunc(); + tutorialUnlockInteractiveObjectsFunc = nullptr; + } break; default: return; // Step4/Step5 transitions are driven by onItemPickedUp diff --git a/src/MenuManager.h b/src/MenuManager.h index 84ab123..5f24372 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -81,6 +81,7 @@ namespace ZL { std::function chatOpenCallback; std::function skipCutsceneFunc; std::function callTaxiFunc; + std::function tutorialUnlockInteractiveObjectsFunc; // Called when a chat message bubble should be shown (text + direction) void onChatBubbleReady(const std::string& text, bool incoming); diff --git a/src/TeleportZone.cpp b/src/TeleportZone.cpp index 056418b..dd97395 100644 --- a/src/TeleportZone.cpp +++ b/src/TeleportZone.cpp @@ -14,8 +14,8 @@ void TeleportZone::initSparks(std::shared_ptr activeTex, std::shared_pt sparks->setEmissionPoints(emitPoints); sparks->setTexture(active ? activeTex : inactiveTex); sparks->setEmissionRate(50.0f); - sparks->setMaxParticles(80); - sparks->setParticleSize(0.05f); + sparks->setMaxParticles(160); + sparks->setParticleSize(0.10f); sparks->setBiasX(0.0f); sparks->setEmissionDirection(Vector3f{ 0.0f, 1.0f, 0.0f }); sparks->setEmissionRadius(radius); diff --git a/src/TeleportZone.h b/src/TeleportZone.h index 389a6b8..6e28652 100644 --- a/src/TeleportZone.h +++ b/src/TeleportZone.h @@ -20,6 +20,12 @@ struct TeleportZone { std::shared_ptr activeTexture; std::shared_ptr inactiveTexture; + bool automatic = false; + float automaticRadius = 0.0f; + // Runtime tracking — prevents re-firing immediately on arrival at destination. + bool automaticPlayerInside = false; + bool automaticInitialized = false; + void initSparks(std::shared_ptr activeTex, std::shared_ptr inactiveTex); void setActive(bool isActive); void update(float deltaMs);