minor fixing, adapting for mobile web
This commit is contained in:
parent
44cc0fba67
commit
eac914e073
@ -205,9 +205,25 @@ void Character::update(int64_t deltaMs) {
|
||||
cb();
|
||||
}
|
||||
|
||||
// While standing, optionally orient toward another character (e.g. the
|
||||
// player when this NPC is being talked to). The smoothed rotation block
|
||||
// below converges facingAngle to targetFacingAngle.
|
||||
if (faceTarget) {
|
||||
Eigen::Vector3f toFace = faceTarget->position - position;
|
||||
toFace.y() = 0.f;
|
||||
float d = toFace.norm();
|
||||
if (d > 1e-3f) {
|
||||
targetFacingAngle = atan2(toFace.x() / d, -toFace.z() / d);
|
||||
}
|
||||
else {
|
||||
targetFacingAngle = facingAngle;
|
||||
}
|
||||
}
|
||||
else {
|
||||
targetFacingAngle = facingAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hp <= 0)
|
||||
{
|
||||
@ -267,6 +283,10 @@ void Character::update(int64_t deltaMs) {
|
||||
float rotStep = rotationSpeed * static_cast<float>(deltaMs) / 1000.f;
|
||||
if (std::fabs(angleDiff) <= rotStep) {
|
||||
facingAngle = targetFacingAngle;
|
||||
// One-shot face-target: once aligned, stop tracking. Otherwise the NPC
|
||||
// would keep snapping to the player every tick after the conversation
|
||||
// started, even as the player moved away.
|
||||
faceTarget = nullptr;
|
||||
} else {
|
||||
facingAngle += (angleDiff > 0.f ? rotStep : -rotStep);
|
||||
}
|
||||
|
||||
@ -112,6 +112,11 @@ public:
|
||||
float attack_cooldown = 0.f;
|
||||
bool canAttack = false;
|
||||
Character* attackTarget = nullptr;
|
||||
// While standing still, smoothly rotate once to face this character.
|
||||
// Auto-cleared by update() the moment this character finishes rotating,
|
||||
// so it acts as a one-shot orient (e.g. an NPC turning to the player at
|
||||
// the start of a conversation, then holding that pose).
|
||||
Character* faceTarget = nullptr;
|
||||
bool isPlayer = false;
|
||||
bool useGpuSkinning = true;
|
||||
//bool useGpuSkinning = false;
|
||||
|
||||
307
src/Game.cpp
307
src/Game.cpp
@ -390,96 +390,51 @@ namespace ZL
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (event.type == SDL_FINGERDOWN) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERUP) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERMOTION) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
}
|
||||
#else
|
||||
// Emscripten on mobile browser: handle real touch events with per-finger IDs.
|
||||
// SDL_HINT_TOUCH_MOUSE_EVENTS="0" is set in main.cpp so these don't
|
||||
// Touch events (real fingers on Android and on mobile browsers via Emscripten).
|
||||
// SDL_HINT_TOUCH_MOUSE_EVENTS="0" is set in main.cpp so these do not
|
||||
// also fire SDL_MOUSEBUTTONDOWN, preventing double-processing.
|
||||
#ifdef EMSCRIPTEN
|
||||
if (event.type == SDL_FINGERDOWN) {
|
||||
if (event.type == SDL_FINGERDOWN || event.type == SDL_FINGERUP || event.type == SDL_FINGERMOTION) {
|
||||
int eventX = static_cast<int>(event.tfinger.x * Environment::width);
|
||||
int eventY = static_cast<int>(event.tfinger.y * Environment::height);
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
int64_t fingerId = static_cast<int64_t>(event.tfinger.fingerId);
|
||||
|
||||
if (event.type == SDL_FINGERDOWN) {
|
||||
onPointerDown(fingerId, eventX, eventY, mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERUP) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
onPointerUp(fingerId, eventX, eventY, mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERMOTION) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
else {
|
||||
onPointerMotion(fingerId, eventX, eventY, mx, my);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
|
||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
|
||||
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
std::cout << "\n========== MOUSE DOWN EVENT ==========" << std::endl;
|
||||
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||
|
||||
if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) {
|
||||
std::cout << "[CLICK] UI handled, skipping character movement" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentLocation)
|
||||
{
|
||||
currentLocation->handleDown(ZL::UiManager::MOUSE_FINGER_ID, event.button.x, event.button.y, mx, my);
|
||||
}
|
||||
///.....
|
||||
} else {
|
||||
handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||
}
|
||||
}
|
||||
else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||
// Mouse-left maps to a single virtual touch with MOUSE_FINGER_ID.
|
||||
// Right mouse is intentionally unused now — camera rotation is on hold-and-drag.
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
|
||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||
int eventX = event.button.x;
|
||||
int eventY = event.button.y;
|
||||
int mx = static_cast<int>((float)eventX / Environment::width * Environment::projectionWidth);
|
||||
int my = static_cast<int>((float)eventY / Environment::height * Environment::projectionHeight);
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
rightMouseDown = true;
|
||||
lastMouseX = event.button.x;
|
||||
lastMouseY = event.button.y;
|
||||
if (currentLocation)
|
||||
{
|
||||
currentLocation->rightMouseDown = true;
|
||||
currentLocation->lastMouseX = event.button.x;
|
||||
currentLocation->lastMouseY = event.button.y;
|
||||
}
|
||||
onPointerDown(ZL::UiManager::MOUSE_FINGER_ID, eventX, eventY, mx, my);
|
||||
}
|
||||
else {
|
||||
rightMouseDown = false;
|
||||
if (currentLocation)
|
||||
{
|
||||
currentLocation->rightMouseDown = false;
|
||||
}
|
||||
onPointerUp(ZL::UiManager::MOUSE_FINGER_ID, eventX, eventY, mx, my);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event.type == SDL_MOUSEMOTION) {
|
||||
int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
|
||||
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
|
||||
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||
if (currentLocation)
|
||||
{
|
||||
currentLocation->handleMotion(ZL::UiManager::MOUSE_FINGER_ID, event.motion.x, event.motion.y, mx, my);
|
||||
}
|
||||
int eventX = event.motion.x;
|
||||
int eventY = event.motion.y;
|
||||
int mx = static_cast<int>((float)eventX / Environment::width * Environment::projectionWidth);
|
||||
int my = static_cast<int>((float)eventY / Environment::height * Environment::projectionHeight);
|
||||
onPointerMotion(ZL::UiManager::MOUSE_FINGER_ID, eventX, eventY, mx, my);
|
||||
}
|
||||
|
||||
if (event.type == SDL_MOUSEWHEEL) {
|
||||
@ -560,7 +515,6 @@ namespace ZL
|
||||
if (event.type == SDL_KEYUP) {
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
render();
|
||||
|
||||
@ -568,39 +522,202 @@ namespace ZL
|
||||
|
||||
}
|
||||
|
||||
void Game::handleDown(int64_t fingerId, int mx, int my)
|
||||
int Game::countNonUiPointers() const
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
|
||||
menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);
|
||||
|
||||
int n = 0;
|
||||
for (const auto& kv : activePointers) {
|
||||
if (!kv.second.capturedByUi) ++n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void Game::handleUp(int64_t fingerId, int mx, int my)
|
||||
void Game::enterCameraDragMode(int eventX, int eventY)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
cameraDragging = true;
|
||||
if (currentLocation) {
|
||||
currentLocation->cameraDragging = true;
|
||||
// Anchor on the *current* position, not the original press, so the
|
||||
// camera doesn't snap by however far the finger drifted before the
|
||||
// movement threshold was crossed.
|
||||
currentLocation->lastMouseX = eventX;
|
||||
currentLocation->lastMouseY = eventY;
|
||||
}
|
||||
}
|
||||
|
||||
// Check BEFORE onTouchUp erases the finger from the map.
|
||||
// If this finger started on a UI element, don't notify space —
|
||||
// otherwise space would think the ship-control finger was released.
|
||||
bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
|
||||
void Game::exitCameraDragMode()
|
||||
{
|
||||
cameraDragging = false;
|
||||
if (currentLocation) {
|
||||
currentLocation->cameraDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Game::startPinch()
|
||||
{
|
||||
// Pick the first two non-UI pointers as pinch anchors.
|
||||
bool foundA = false, foundB = false;
|
||||
for (auto& kv : activePointers) {
|
||||
if (kv.second.capturedByUi) continue;
|
||||
if (!foundA) {
|
||||
pinchFingerA = kv.first;
|
||||
foundA = true;
|
||||
}
|
||||
else if (!foundB) {
|
||||
pinchFingerB = kv.first;
|
||||
foundB = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundA || !foundB) return;
|
||||
|
||||
const PointerState& a = activePointers[pinchFingerA];
|
||||
const PointerState& b = activePointers[pinchFingerB];
|
||||
float dx = static_cast<float>(a.eventX - b.eventX);
|
||||
float dy = static_cast<float>(a.eventY - b.eventY);
|
||||
pinchStartDistance = std::sqrt(dx * dx + dy * dy);
|
||||
pinchStartZoom = Environment::zoom;
|
||||
pinchActive = true;
|
||||
}
|
||||
|
||||
void Game::updatePinchZoom()
|
||||
{
|
||||
auto itA = activePointers.find(pinchFingerA);
|
||||
auto itB = activePointers.find(pinchFingerB);
|
||||
if (itA == activePointers.end() || itB == activePointers.end()) return;
|
||||
if (pinchStartDistance <= 1.0f) return;
|
||||
|
||||
float dx = static_cast<float>(itA->second.eventX - itB->second.eventX);
|
||||
float dy = static_cast<float>(itA->second.eventY - itB->second.eventY);
|
||||
float dist = std::sqrt(dx * dx + dy * dy);
|
||||
if (dist <= 1.0f) return;
|
||||
|
||||
float newZoom = pinchStartZoom * (pinchStartDistance / dist);
|
||||
// Match the wheel-zoom lower bound so both gestures clamp the same way.
|
||||
static const float zoomMin = 2.0f;
|
||||
if (newZoom < zoomMin) newZoom = zoomMin;
|
||||
Environment::zoom = newZoom;
|
||||
}
|
||||
|
||||
void Game::endPinch()
|
||||
{
|
||||
pinchActive = false;
|
||||
pinchStartDistance = 0.0f;
|
||||
pinchStartZoom = 0.0f;
|
||||
}
|
||||
|
||||
void Game::onPointerDown(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
||||
{
|
||||
PointerState st;
|
||||
st.eventX = st.downEventX = eventX;
|
||||
st.eventY = st.downEventY = eventY;
|
||||
st.mx = st.downMx = mx;
|
||||
st.my = st.downMy = my;
|
||||
|
||||
const int uiX = mx;
|
||||
const int uiY = Environment::projectionHeight - my;
|
||||
menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);
|
||||
st.capturedByUi = menuManager.uiManager.isUiInteractionForFinger(fingerId);
|
||||
|
||||
activePointers[fingerId] = st;
|
||||
|
||||
if (st.capturedByUi) {
|
||||
// UI button / slider / text-field grabbed this press; don't drive
|
||||
// gameplay or pinch from it.
|
||||
return;
|
||||
}
|
||||
|
||||
if (countNonUiPointers() >= 2) {
|
||||
// Second finger landed on the gameplay area: switch to pinch-zoom
|
||||
// and abandon any in-flight tap/camera-drag.
|
||||
if (cameraDragging) {
|
||||
exitCameraDragMode();
|
||||
}
|
||||
hasPrimaryPointer = false;
|
||||
if (!pinchActive) {
|
||||
startPinch();
|
||||
}
|
||||
}
|
||||
else {
|
||||
hasPrimaryPointer = true;
|
||||
primaryPointerId = fingerId;
|
||||
}
|
||||
}
|
||||
|
||||
void Game::onPointerUp(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
||||
{
|
||||
const int uiX = mx;
|
||||
const int uiY = Environment::projectionHeight - my;
|
||||
menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);
|
||||
|
||||
auto it = activePointers.find(fingerId);
|
||||
if (it == activePointers.end()) return;
|
||||
PointerState st = it->second;
|
||||
activePointers.erase(it);
|
||||
|
||||
// Pinch ends as soon as either anchor is lifted. The remaining finger,
|
||||
// if any, is *not* promoted back to a primary tap — the user was mid-
|
||||
// gesture, not starting a fresh press.
|
||||
if (pinchActive && (fingerId == pinchFingerA || fingerId == pinchFingerB)) {
|
||||
endPinch();
|
||||
return;
|
||||
}
|
||||
|
||||
void Game::handleMotion(int64_t fingerId, int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
if (!hasPrimaryPointer || fingerId != primaryPointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check before onTouchMove so the "started on UI" state is preserved
|
||||
// regardless of what onTouchMove does internally.
|
||||
bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
|
||||
const bool wasDragging = cameraDragging;
|
||||
if (cameraDragging) {
|
||||
exitCameraDragMode();
|
||||
}
|
||||
hasPrimaryPointer = false;
|
||||
|
||||
if (st.capturedByUi || wasDragging) {
|
||||
// UI handled the press, or it was a camera-rotation drag — no tap action.
|
||||
return;
|
||||
}
|
||||
|
||||
// Tap → walk-to / interact, using the original press coords so the target
|
||||
// isn't shifted by tiny finger drift before release.
|
||||
if (currentLocation) {
|
||||
currentLocation->handleDown(fingerId, st.downEventX, st.downEventY, st.downMx, st.downMy);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::onPointerMotion(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
||||
{
|
||||
const int uiX = mx;
|
||||
const int uiY = Environment::projectionHeight - my;
|
||||
menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);
|
||||
|
||||
auto it = activePointers.find(fingerId);
|
||||
if (it != activePointers.end()) {
|
||||
it->second.eventX = eventX;
|
||||
it->second.eventY = eventY;
|
||||
it->second.mx = mx;
|
||||
it->second.my = my;
|
||||
}
|
||||
|
||||
if (pinchActive) {
|
||||
updatePinchZoom();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only the primary, non-UI-captured pointer can promote itself into a
|
||||
// camera-rotation drag once it crosses the movement threshold.
|
||||
if (hasPrimaryPointer && fingerId == primaryPointerId && it != activePointers.end()
|
||||
&& !it->second.capturedByUi && !cameraDragging) {
|
||||
int dx = mx - it->second.downMx;
|
||||
int dy = my - it->second.downMy;
|
||||
if (dx * dx + dy * dy >= CAMERA_DRAG_PIXEL_THRESHOLD * CAMERA_DRAG_PIXEL_THRESHOLD) {
|
||||
enterCameraDragMode(eventX, eventY);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLocation) {
|
||||
// Forwarded for dialogue hover and (when cameraDragging) camera rotation.
|
||||
currentLocation->handleMotion(fingerId, eventX, eventY, mx, my);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZL
|
||||
|
||||
40
src/Game.h
40
src/Game.h
@ -54,9 +54,30 @@ namespace ZL {
|
||||
MenuManager menuManager;
|
||||
|
||||
private:
|
||||
bool rightMouseDown = false;
|
||||
int lastMouseX = 0;
|
||||
int lastMouseY = 0;
|
||||
// Unified pointer handling: mouse-left and a single touch share one path.
|
||||
// A press becomes a tap (interact / walk-to) on release if it never crossed
|
||||
// CAMERA_DRAG_PIXEL_THRESHOLD; otherwise it becomes a camera-rotation drag.
|
||||
// Two simultaneous touches enter pinch-zoom instead.
|
||||
static constexpr int CAMERA_DRAG_PIXEL_THRESHOLD = 12;
|
||||
|
||||
struct PointerState {
|
||||
int eventX = 0, eventY = 0; // current raw window-pixel coords
|
||||
int mx = 0, my = 0; // current projection-space coords
|
||||
int downEventX = 0, downEventY = 0;// where the press started (raw px)
|
||||
int downMx = 0, downMy = 0; // where the press started (proj)
|
||||
bool capturedByUi = false;
|
||||
};
|
||||
std::unordered_map<int64_t, PointerState> activePointers;
|
||||
bool hasPrimaryPointer = false;
|
||||
int64_t primaryPointerId = 0;
|
||||
bool cameraDragging = false;
|
||||
|
||||
bool pinchActive = false;
|
||||
int64_t pinchFingerA = 0;
|
||||
int64_t pinchFingerB = 0;
|
||||
float pinchStartDistance = 0.0f;
|
||||
float pinchStartZoom = 0.0f;
|
||||
|
||||
std::unique_ptr<AudioPlayerAsync> audioPlayer;
|
||||
|
||||
int64_t getSyncTimeMs();
|
||||
@ -66,9 +87,16 @@ namespace ZL {
|
||||
|
||||
void drawLoading();
|
||||
|
||||
void handleDown(int64_t fingerId, int mx, int my);
|
||||
void handleUp(int64_t fingerId, int mx, int my);
|
||||
void handleMotion(int64_t fingerId, int mx, int my);
|
||||
void onPointerDown(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
||||
void onPointerUp(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
||||
void onPointerMotion(int64_t fingerId, int eventX, int eventY, int mx, int my);
|
||||
|
||||
void enterCameraDragMode(int eventX, int eventY);
|
||||
void exitCameraDragMode();
|
||||
void startPinch();
|
||||
void updatePinchZoom();
|
||||
void endPinch();
|
||||
int countNonUiPointers() const;
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
static Game* s_instance;
|
||||
|
||||
126
src/Location.cpp
126
src/Location.cpp
@ -19,6 +19,9 @@ namespace ZL
|
||||
extern const char* CONST_ZIP_FILE;
|
||||
|
||||
static constexpr float CAMERA_FOV_Y = 1.0f / 1.5f;
|
||||
// How close the player needs to be to a peaceful NPC before the
|
||||
// on_npc_interact callback fires and the conversation begins.
|
||||
static constexpr float NPC_TALK_DISTANCE = 1.25f;
|
||||
|
||||
Location::Location(Renderer& iRenderer, Inventory& iInventory)
|
||||
: renderer(iRenderer)
|
||||
@ -293,24 +296,65 @@ namespace ZL
|
||||
}
|
||||
|
||||
Character* Location::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) {
|
||||
// Every NPC is treated as a vertical cylinder: radius 1.0m, height 1.85m,
|
||||
// base at npc->position (the model's foot). Intersection = circle hit in the
|
||||
// XZ plane, then clip the entry/exit t against the [yFoot, yFoot+height] slab.
|
||||
static constexpr float NPC_CLICK_RADIUS = 1.0f;
|
||||
static constexpr float NPC_CLICK_HEIGHT = 1.85f;
|
||||
|
||||
Character* closestNpc = nullptr;
|
||||
float closestDist = maxDistance;
|
||||
|
||||
std::cout << "[RAYCAST_NPC] Starting raycast with " << npcs.size() << " npcs" << std::endl;
|
||||
|
||||
for (auto& npc : npcs) {
|
||||
Eigen::Vector3f toNpc = npc->position - rayOrigin;
|
||||
float distAlongRay = toNpc.dot(rayDir);
|
||||
if (distAlongRay < 0.1f) continue;
|
||||
const float dx = rayOrigin.x() - npc->position.x();
|
||||
const float dz = rayOrigin.z() - npc->position.z();
|
||||
const float a = rayDir.x() * rayDir.x() + rayDir.z() * rayDir.z();
|
||||
if (a < 1e-6f) continue; // purely-vertical ray; not a real click case
|
||||
|
||||
Eigen::Vector3f closestPoint = rayOrigin + rayDir * distAlongRay;
|
||||
float distToNpc = (closestPoint - npc->position).norm();
|
||||
const float b = 2.0f * (dx * rayDir.x() + dz * rayDir.z());
|
||||
const float c = dx * dx + dz * dz - NPC_CLICK_RADIUS * NPC_CLICK_RADIUS;
|
||||
const float disc = b * b - 4.0f * a * c;
|
||||
if (disc < 0.0f) continue; // ray misses the infinite cylinder
|
||||
|
||||
float radius = npc->modelScale * 50.0f;
|
||||
const float sqrtDisc = std::sqrt(disc);
|
||||
const float tNear = (-b - sqrtDisc) / (2.0f * a);
|
||||
const float tFar = (-b + sqrtDisc) / (2.0f * a);
|
||||
|
||||
if (distToNpc <= radius && distAlongRay < closestDist) {
|
||||
closestDist = distAlongRay;
|
||||
float entryT = max(tNear, 0.1f);
|
||||
float exitT = tFar;
|
||||
|
||||
// Clip against the cylinder's vertical extent.
|
||||
const float yMin = npc->position.y();
|
||||
const float yMax = yMin + NPC_CLICK_HEIGHT;
|
||||
if (std::abs(rayDir.y()) > 1e-6f) {
|
||||
const float tYa = (yMin - rayOrigin.y()) / rayDir.y();
|
||||
const float tYb = (yMax - rayOrigin.y()) / rayDir.y();
|
||||
entryT = max(entryT, min(tYa, tYb));
|
||||
exitT = min(exitT, max(tYa, tYb));
|
||||
}
|
||||
else if (rayOrigin.y() < yMin || rayOrigin.y() > yMax) {
|
||||
continue; // horizontal ray passing above or below the cylinder
|
||||
}
|
||||
|
||||
if (entryT > exitT) continue;
|
||||
|
||||
std::cout << "[RAYCAST_NPC] " << npc->npcId << " hit at t=" << entryT << std::endl;
|
||||
|
||||
if (entryT < closestDist) {
|
||||
closestDist = entryT;
|
||||
closestNpc = npc.get();
|
||||
}
|
||||
}
|
||||
|
||||
if (closestNpc) {
|
||||
std::cout << "[RAYCAST_NPC] HIT: " << closestNpc->npcId << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cout << "[RAYCAST_NPC] No NPC hit" << std::endl;
|
||||
}
|
||||
|
||||
return closestNpc;
|
||||
}
|
||||
|
||||
@ -611,6 +655,26 @@ namespace ZL
|
||||
targetInteractiveObject = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if player reached target NPC for interaction.
|
||||
if (targetInteractNpc && targetInteractNpcIndex >= 0 && player) {
|
||||
float distToNpc = (player->position - targetInteractNpc->position).norm();
|
||||
if (distToNpc <= NPC_TALK_DISTANCE) {
|
||||
std::cout << "[NPC] Player reached NPC index " << targetInteractNpcIndex
|
||||
<< " (distance " << distToNpc << "); firing on_npc_interact" << std::endl;
|
||||
// Stop the player at the talk distance and have the NPC turn to face them.
|
||||
player->setTarget(player->position);
|
||||
targetInteractNpc->faceTarget = player.get();
|
||||
try {
|
||||
scriptEngine.callNpcInteractCallback(targetInteractNpcIndex);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
std::cerr << "[NPC] callback error: " << e.what() << std::endl;
|
||||
}
|
||||
targetInteractNpc = nullptr;
|
||||
targetInteractNpcIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Location::handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
||||
@ -653,6 +717,8 @@ namespace ZL
|
||||
<< player->position.y() << ", " << player->position.z() << ")" << std::endl;
|
||||
|
||||
targetInteractiveObject = clickedObject;
|
||||
targetInteractNpc = nullptr;
|
||||
targetInteractNpcIndex = -1;
|
||||
player->setTarget(clickedObject->position);
|
||||
player->attackTarget = nullptr;
|
||||
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
||||
@ -671,22 +737,40 @@ namespace ZL
|
||||
}
|
||||
}
|
||||
if (npcIndex != -1) {
|
||||
targetInteractiveObject = nullptr;
|
||||
|
||||
if (clickedNpc->canAttack) {
|
||||
// Hostile NPC: combat logic walks the player in via attackTarget;
|
||||
// don't queue a Lua interaction during a fight.
|
||||
player->attackTarget = clickedNpc;
|
||||
targetInteractNpc = nullptr;
|
||||
targetInteractNpcIndex = -1;
|
||||
|
||||
if (distance <= clickedNpc->interactionRadius) {
|
||||
std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl;
|
||||
std::cout << "[CLICK] Hostile NPC " << npcIndex << " in range; firing on_npc_interact" << std::endl;
|
||||
scriptEngine.callNpcInteractCallback(npcIndex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::cout << "[CLICK] Too far from NPC (distance " << distance
|
||||
<< " > " << clickedNpc->interactionRadius << ")" << std::endl;
|
||||
}
|
||||
|
||||
if (clickedNpc->canAttack)
|
||||
{
|
||||
player->attackTarget = clickedNpc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Peaceful NPC: walk to them, fire on_npc_interact when within talk distance.
|
||||
player->attackTarget = nullptr;
|
||||
|
||||
if (distance <= NPC_TALK_DISTANCE) {
|
||||
// Already in talk range — fire immediately, stop, and face the player.
|
||||
std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " (in range) ***" << std::endl;
|
||||
player->setTarget(player->position);
|
||||
clickedNpc->faceTarget = player.get();
|
||||
scriptEngine.callNpcInteractCallback(npcIndex);
|
||||
targetInteractNpc = nullptr;
|
||||
targetInteractNpcIndex = -1;
|
||||
}
|
||||
else {
|
||||
std::cout << "[CLICK] NPC " << npcIndex << " out of talk range (distance " << distance
|
||||
<< " > " << NPC_TALK_DISTANCE << "); walking to NPC..." << std::endl;
|
||||
player->setTarget(clickedNpc->position);
|
||||
targetInteractNpc = clickedNpc;
|
||||
targetInteractNpcIndex = npcIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -699,6 +783,8 @@ namespace ZL
|
||||
|
||||
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
|
||||
player->attackTarget = nullptr;
|
||||
targetInteractNpc = nullptr;
|
||||
targetInteractNpcIndex = -1;
|
||||
}
|
||||
else {
|
||||
std::cout << "[CLICK] No valid target found" << std::endl;
|
||||
@ -719,7 +805,7 @@ namespace ZL
|
||||
);
|
||||
}
|
||||
|
||||
if (rightMouseDown) {
|
||||
if (cameraDragging) {
|
||||
int dx = eventX - lastMouseX;
|
||||
int dy = eventY - lastMouseY;
|
||||
lastMouseX = eventX;
|
||||
|
||||
@ -40,6 +40,12 @@ namespace ZL
|
||||
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
|
||||
InteractiveObject* targetInteractiveObject = nullptr;
|
||||
|
||||
// "Walk to NPC, then fire on_npc_interact" — mirrors targetInteractiveObject
|
||||
// so a click from outside interactionRadius still leads to a Lua callback
|
||||
// once the player gets close enough.
|
||||
Character* targetInteractNpc = nullptr;
|
||||
int targetInteractNpcIndex = -1;
|
||||
|
||||
std::unique_ptr<TextRenderer> npcNameText;
|
||||
|
||||
ScriptEngine scriptEngine;
|
||||
@ -50,7 +56,9 @@ namespace ZL
|
||||
void buildDebugNavMeshes();
|
||||
void drawDebugNavigation();
|
||||
#endif
|
||||
bool rightMouseDown = false;
|
||||
// Set by Game when the user's primary pointer (left mouse / single touch)
|
||||
// has crossed the tap-vs-drag threshold and is now rotating the camera.
|
||||
bool cameraDragging = false;
|
||||
int lastMouseX = 0;
|
||||
int lastMouseY = 0;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user