mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-25 16:30:15 +00:00
Improve targeting, minimap, and bridge collisions
This commit is contained in:
parent
a7c0b4320b
commit
38c9fdad6b
10 changed files with 142 additions and 24 deletions
|
|
@ -529,6 +529,8 @@ public:
|
|||
void update(float deltaTime);
|
||||
|
||||
private:
|
||||
void autoTargetAttacker(uint64_t attackerGuid);
|
||||
|
||||
/**
|
||||
* Handle incoming packet from world server
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ struct M2ModelGPU {
|
|||
bool collisionSteppedFountain = false;
|
||||
bool collisionSteppedLowPlatform = false;
|
||||
bool collisionPlanter = false;
|
||||
bool collisionBridge = false;
|
||||
bool collisionSmallSolidProp = false;
|
||||
bool collisionNarrowVerticalProp = false;
|
||||
bool collisionTreeTrunk = false;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ public:
|
|||
void toggle() { enabled = !enabled; }
|
||||
|
||||
void setViewRadius(float radius) { viewRadius = radius; }
|
||||
void setRotateWithCamera(bool rotate) { rotateWithCamera = rotate; }
|
||||
bool isRotateWithCamera() const { return rotateWithCamera; }
|
||||
|
||||
// Public accessors for WorldMap
|
||||
GLuint getOrLoadTileTexture(int tileX, int tileY);
|
||||
|
|
@ -76,6 +78,7 @@ private:
|
|||
int mapSize = 200;
|
||||
float viewRadius = 400.0f; // world units visible in minimap radius
|
||||
bool enabled = true;
|
||||
bool rotateWithCamera = true;
|
||||
|
||||
// Throttling
|
||||
float updateIntervalSec = 0.25f;
|
||||
|
|
|
|||
|
|
@ -321,6 +321,7 @@ private:
|
|||
std::vector<GroupResources> groups;
|
||||
glm::vec3 boundingBoxMin;
|
||||
glm::vec3 boundingBoxMax;
|
||||
bool isLowPlatform = false;
|
||||
|
||||
// Texture handles for this model (indexed by texture path order)
|
||||
std::vector<GLuint> textures;
|
||||
|
|
|
|||
|
|
@ -70,9 +70,11 @@ private:
|
|||
float pendingMouseSensitivity = 0.2f;
|
||||
bool pendingInvertMouse = false;
|
||||
int pendingUiOpacity = 65;
|
||||
bool pendingMinimapRotate = true;
|
||||
|
||||
// UI element transparency (0.0 = fully transparent, 1.0 = fully opaque)
|
||||
float uiOpacity_ = 0.65f;
|
||||
bool minimapRotate_ = true;
|
||||
|
||||
/**
|
||||
* Render player info window
|
||||
|
|
|
|||
|
|
@ -3068,6 +3068,13 @@ void GameHandler::updateCombatText(float deltaTime) {
|
|||
combatText.end());
|
||||
}
|
||||
|
||||
void GameHandler::autoTargetAttacker(uint64_t attackerGuid) {
|
||||
if (attackerGuid == 0 || attackerGuid == playerGuid) return;
|
||||
if (targetGuid != 0) return;
|
||||
if (!entityManager.hasEntity(attackerGuid)) return;
|
||||
setTarget(attackerGuid);
|
||||
}
|
||||
|
||||
void GameHandler::handleAttackStart(network::Packet& packet) {
|
||||
AttackStartData data;
|
||||
if (!AttackStartParser::parse(packet, data)) return;
|
||||
|
|
@ -3075,6 +3082,9 @@ void GameHandler::handleAttackStart(network::Packet& packet) {
|
|||
if (data.attackerGuid == playerGuid) {
|
||||
autoAttacking = true;
|
||||
autoAttackTarget = data.victimGuid;
|
||||
} else if (data.victimGuid == playerGuid && data.attackerGuid != 0) {
|
||||
hostileAttackers_.insert(data.attackerGuid);
|
||||
autoTargetAttacker(data.attackerGuid);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3221,6 +3231,7 @@ void GameHandler::handleAttackerStateUpdate(network::Packet& packet) {
|
|||
|
||||
if (isPlayerTarget && data.attackerGuid != 0) {
|
||||
hostileAttackers_.insert(data.attackerGuid);
|
||||
autoTargetAttacker(data.attackerGuid);
|
||||
}
|
||||
|
||||
if (data.isMiss()) {
|
||||
|
|
@ -3241,6 +3252,11 @@ void GameHandler::handleSpellDamageLog(network::Packet& packet) {
|
|||
SpellDamageLogData data;
|
||||
if (!SpellDamageLogParser::parse(packet, data)) return;
|
||||
|
||||
if (data.targetGuid == playerGuid && data.attackerGuid != 0) {
|
||||
hostileAttackers_.insert(data.attackerGuid);
|
||||
autoTargetAttacker(data.attackerGuid);
|
||||
}
|
||||
|
||||
bool isPlayerSource = (data.attackerGuid == playerGuid);
|
||||
auto type = data.isCrit ? CombatTextEntry::CRIT_DAMAGE : CombatTextEntry::SPELL_DAMAGE;
|
||||
addCombatText(type, static_cast<int32_t>(data.damage), data.spellId, isPlayerSource);
|
||||
|
|
|
|||
|
|
@ -668,9 +668,15 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
(lowerName.find("stormwindplanter") != std::string::npos) ||
|
||||
(lowerName.find("stormwindwindowplanter") != std::string::npos);
|
||||
bool lowPlatformShape = (horiz > 1.8f && vert > 0.2f && vert < 1.8f);
|
||||
bool bridgeName =
|
||||
(lowerName.find("bridge") != std::string::npos) ||
|
||||
(lowerName.find("plank") != std::string::npos) ||
|
||||
(lowerName.find("walkway") != std::string::npos);
|
||||
gpuModel.collisionSteppedLowPlatform = (!gpuModel.collisionSteppedFountain) &&
|
||||
(knownStormwindPlanter ||
|
||||
bridgeName ||
|
||||
(likelyCurbName && (lowPlatformShape || lowWideShape)));
|
||||
gpuModel.collisionBridge = bridgeName;
|
||||
|
||||
bool isPlanter = (lowerName.find("planter") != std::string::npos);
|
||||
gpuModel.collisionPlanter = isPlanter;
|
||||
|
|
@ -702,6 +708,7 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
(lowerName.find("vine") != std::string::npos) ||
|
||||
(lowerName.find("lily") != std::string::npos) ||
|
||||
(lowerName.find("weed") != std::string::npos) ||
|
||||
(lowerName.find("wheat") != std::string::npos) ||
|
||||
(lowerName.find("pumpkin") != std::string::npos) ||
|
||||
(lowerName.find("firefly") != std::string::npos) ||
|
||||
(lowerName.find("fireflies") != std::string::npos) ||
|
||||
|
|
@ -2329,18 +2336,18 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ)
|
|||
continue;
|
||||
}
|
||||
|
||||
if (glX < instance.worldBoundsMin.x || glX > instance.worldBoundsMax.x ||
|
||||
glY < instance.worldBoundsMin.y || glY > instance.worldBoundsMax.y ||
|
||||
glZ < instance.worldBoundsMin.z - 2.0f || glZ > instance.worldBoundsMax.z + 2.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = models.find(instance.modelId);
|
||||
if (it == models.end()) continue;
|
||||
if (instance.scale <= 0.001f) continue;
|
||||
|
||||
const M2ModelGPU& model = it->second;
|
||||
if (model.collisionNoBlock) continue;
|
||||
float zMargin = model.collisionBridge ? 25.0f : 2.0f;
|
||||
if (glX < instance.worldBoundsMin.x || glX > instance.worldBoundsMax.x ||
|
||||
glY < instance.worldBoundsMin.y || glY > instance.worldBoundsMax.y ||
|
||||
glZ < instance.worldBoundsMin.z - zMargin || glZ > instance.worldBoundsMax.z + zMargin) {
|
||||
continue;
|
||||
}
|
||||
glm::vec3 localMin, localMax;
|
||||
getTightCollisionBounds(model, localMin, localMax);
|
||||
|
||||
|
|
@ -2351,6 +2358,9 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ)
|
|||
float footprintPad = 0.0f;
|
||||
if (model.collisionSteppedLowPlatform) {
|
||||
footprintPad = model.collisionPlanter ? 0.22f : 0.16f;
|
||||
if (model.collisionBridge) {
|
||||
footprintPad = 0.35f;
|
||||
}
|
||||
}
|
||||
if (localPos.x < localMin.x - footprintPad || localPos.x > localMax.x + footprintPad ||
|
||||
localPos.y < localMin.y - footprintPad || localPos.y > localMax.y + footprintPad) {
|
||||
|
|
@ -2372,6 +2382,9 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ)
|
|||
maxStepUp = 2.5f;
|
||||
} else if (model.collisionSteppedLowPlatform) {
|
||||
maxStepUp = model.collisionPlanter ? 3.0f : 2.4f;
|
||||
if (model.collisionBridge) {
|
||||
maxStepUp = 25.0f;
|
||||
}
|
||||
}
|
||||
if (worldTop.z > glZ + maxStepUp) continue;
|
||||
|
||||
|
|
@ -2455,6 +2468,9 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
maxStepUp = 2.5f;
|
||||
} else if (model.collisionSteppedLowPlatform) {
|
||||
maxStepUp = model.collisionPlanter ? 2.8f : 2.4f;
|
||||
if (model.collisionBridge) {
|
||||
maxStepUp = 25.0f;
|
||||
}
|
||||
}
|
||||
bool stepableLowObject = (effectiveTop <= localFrom.z + maxStepUp);
|
||||
bool climbingAttempt = (localPos.z > localFrom.z + 0.18f);
|
||||
|
|
@ -2464,6 +2480,9 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
// Let low curb/planter blocks be stepable without sticky side shoves.
|
||||
climbAllowance = 1.00f;
|
||||
}
|
||||
if (model.collisionBridge) {
|
||||
climbAllowance = 3.0f;
|
||||
}
|
||||
if (model.collisionSmallSolidProp) {
|
||||
climbAllowance = 1.05f;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ bool Minimap::initialize(int size) {
|
|||
uniform sampler2D uComposite;
|
||||
uniform vec2 uPlayerUV;
|
||||
uniform float uRotation;
|
||||
uniform float uArrowRotation;
|
||||
uniform float uZoomRadius;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
|
@ -158,6 +159,12 @@ bool Minimap::initialize(int size) {
|
|||
return (u >= 0.0) && (v >= 0.0) && (u + v <= 1.0);
|
||||
}
|
||||
|
||||
vec2 rot2(vec2 v, float ang) {
|
||||
float c = cos(ang);
|
||||
float s = sin(ang);
|
||||
return vec2(v.x * c - v.y * s, v.x * s + v.y * c);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 centered = TexCoord - 0.5;
|
||||
float dist = length(centered);
|
||||
|
|
@ -185,7 +192,7 @@ bool Minimap::initialize(int size) {
|
|||
}
|
||||
|
||||
// Player arrow at center (always points up = forward)
|
||||
vec2 ap = centered;
|
||||
vec2 ap = rot2(centered, -uArrowRotation);
|
||||
vec2 tip = vec2(0.0, 0.035);
|
||||
vec2 lt = vec2(-0.018, -0.016);
|
||||
vec2 rt = vec2(0.018, -0.016);
|
||||
|
|
@ -490,9 +497,18 @@ void Minimap::renderQuad(const Camera& playerCamera, const glm::vec3& centerWorl
|
|||
// renderX = wowY (west), renderY = wowX (north)
|
||||
// Facing north: fwd=(0,1,0) → bearing=0
|
||||
// Facing east: fwd=(-1,0,0) → bearing=π/2
|
||||
glm::vec3 fwd = playerCamera.getForward();
|
||||
float rotation = std::atan2(-fwd.x, fwd.y);
|
||||
float rotation = 0.0f;
|
||||
if (rotateWithCamera) {
|
||||
glm::vec3 fwd = playerCamera.getForward();
|
||||
rotation = std::atan2(-fwd.x, fwd.y);
|
||||
}
|
||||
quadShader->setUniform("uRotation", rotation);
|
||||
float arrowRotation = 0.0f;
|
||||
if (!rotateWithCamera) {
|
||||
glm::vec3 fwd = playerCamera.getForward();
|
||||
arrowRotation = std::atan2(-fwd.x, fwd.y);
|
||||
}
|
||||
quadShader->setUniform("uArrowRotation", arrowRotation);
|
||||
|
||||
quadShader->setUniform("uComposite", 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
|
|
|||
|
|
@ -272,6 +272,12 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) {
|
|||
modelData.id = id;
|
||||
modelData.boundingBoxMin = model.boundingBoxMin;
|
||||
modelData.boundingBoxMax = model.boundingBoxMax;
|
||||
{
|
||||
glm::vec3 ext = model.boundingBoxMax - model.boundingBoxMin;
|
||||
float horiz = std::max(ext.x, ext.y);
|
||||
float vert = ext.z;
|
||||
modelData.isLowPlatform = (vert < 6.0f && horiz > 20.0f);
|
||||
}
|
||||
|
||||
core::Logger::getInstance().info(" WMO bounds: min=(", model.boundingBoxMin.x, ", ", model.boundingBoxMin.y, ", ", model.boundingBoxMin.z,
|
||||
") max=(", model.boundingBoxMax.x, ", ", model.boundingBoxMax.y, ", ", model.boundingBoxMax.z, ")");
|
||||
|
|
@ -1637,6 +1643,7 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
|||
|
||||
QueryTimer timer(&queryTimeMs, &queryCallCount);
|
||||
std::optional<float> bestFloor;
|
||||
bool bestFromLowPlatform = false;
|
||||
|
||||
// World-space ray: from high above, pointing straight down
|
||||
glm::vec3 worldOrigin(glX, glY, glZ + 500.0f);
|
||||
|
|
@ -1653,17 +1660,19 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
|||
continue;
|
||||
}
|
||||
|
||||
// Broad-phase reject in world space to avoid expensive matrix transforms.
|
||||
if (glX < instance.worldBoundsMin.x || glX > instance.worldBoundsMax.x ||
|
||||
glY < instance.worldBoundsMin.y || glY > instance.worldBoundsMax.y ||
|
||||
glZ < instance.worldBoundsMin.z - 2.0f || glZ > instance.worldBoundsMax.z + 4.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = loadedModels.find(instance.modelId);
|
||||
if (it == loadedModels.end()) continue;
|
||||
|
||||
const ModelData& model = it->second;
|
||||
float zMarginDown = model.isLowPlatform ? 20.0f : 2.0f;
|
||||
float zMarginUp = model.isLowPlatform ? 20.0f : 4.0f;
|
||||
|
||||
// Broad-phase reject in world space to avoid expensive matrix transforms.
|
||||
if (glX < instance.worldBoundsMin.x || glX > instance.worldBoundsMax.x ||
|
||||
glY < instance.worldBoundsMin.y || glY > instance.worldBoundsMax.y ||
|
||||
glZ < instance.worldBoundsMin.z - zMarginDown || glZ > instance.worldBoundsMax.z + zMarginUp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// World-space pre-pass: check which groups' world XY bounds contain
|
||||
// the query point. For a vertical ray this eliminates most groups
|
||||
|
|
@ -1720,9 +1729,11 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
|||
glm::vec3 hitLocal = localOrigin + localDir * t;
|
||||
glm::vec3 hitWorld = glm::vec3(instance.modelMatrix * glm::vec4(hitLocal, 1.0f));
|
||||
|
||||
if (hitWorld.z <= glZ + 0.5f) {
|
||||
float allowAbove = model.isLowPlatform ? 12.0f : 0.5f;
|
||||
if (hitWorld.z <= glZ + allowAbove) {
|
||||
if (!bestFloor || hitWorld.z > *bestFloor) {
|
||||
bestFloor = hitWorld.z;
|
||||
bestFromLowPlatform = model.isLowPlatform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1735,7 +1746,10 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
|||
// Only update cache if we found a floor that's close to query height,
|
||||
// to avoid caching wrong floors when player is on different stories.
|
||||
if (bestFloor && *bestFloor >= glZ - 6.0f) {
|
||||
precomputedFloorGrid[gridKey] = *bestFloor;
|
||||
float cacheAbove = bestFromLowPlatform ? 12.0f : 2.0f;
|
||||
if (*bestFloor <= glZ + cacheAbove) {
|
||||
precomputedFloorGrid[gridKey] = *bestFloor;
|
||||
}
|
||||
}
|
||||
|
||||
return bestFloor;
|
||||
|
|
|
|||
|
|
@ -3591,6 +3591,12 @@ void GameScreen::renderSettingsWindow() {
|
|||
}
|
||||
}
|
||||
pendingUiOpacity = static_cast<int>(uiOpacity_ * 100.0f + 0.5f);
|
||||
pendingMinimapRotate = minimapRotate_;
|
||||
if (renderer) {
|
||||
if (auto* minimap = renderer->getMinimap()) {
|
||||
minimap->setRotateWithCamera(minimapRotate_);
|
||||
}
|
||||
}
|
||||
settingsInit = true;
|
||||
}
|
||||
|
||||
|
|
@ -3659,8 +3665,10 @@ void GameScreen::renderSettingsWindow() {
|
|||
|
||||
ImGui::Text("Interface");
|
||||
ImGui::SliderInt("UI Opacity", &pendingUiOpacity, 20, 100, "%d%%");
|
||||
ImGui::Checkbox("Rotate Minimap", &pendingMinimapRotate);
|
||||
if (ImGui::Button("Restore Interface Defaults", ImVec2(-1, 0))) {
|
||||
pendingUiOpacity = 65;
|
||||
pendingMinimapRotate = true;
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
|
@ -3669,12 +3677,16 @@ void GameScreen::renderSettingsWindow() {
|
|||
|
||||
if (ImGui::Button("Apply", ImVec2(-1, 0))) {
|
||||
uiOpacity_ = static_cast<float>(pendingUiOpacity) / 100.0f;
|
||||
minimapRotate_ = pendingMinimapRotate;
|
||||
saveSettings();
|
||||
window->setVsync(pendingVsync);
|
||||
window->setFullscreen(pendingFullscreen);
|
||||
window->applyResolution(kResolutions[pendingResIndex][0], kResolutions[pendingResIndex][1]);
|
||||
if (renderer) {
|
||||
renderer->setShadowsEnabled(pendingShadows);
|
||||
if (auto* minimap = renderer->getMinimap()) {
|
||||
minimap->setRotateWithCamera(minimapRotate_);
|
||||
}
|
||||
if (auto* music = renderer->getMusicManager()) {
|
||||
music->setVolume(pendingMusicVolume);
|
||||
}
|
||||
|
|
@ -3782,8 +3794,6 @@ void GameScreen::renderQuestMarkers(game::GameHandler& gameHandler) {
|
|||
|
||||
void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
|
||||
const auto& statuses = gameHandler.getNpcQuestStatuses();
|
||||
if (statuses.empty()) return;
|
||||
|
||||
auto* renderer = core::Application::getInstance().getRenderer();
|
||||
auto* camera = renderer ? renderer->getCamera() : nullptr;
|
||||
auto* minimap = renderer ? renderer->getMinimap() : nullptr;
|
||||
|
|
@ -3805,10 +3815,37 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
|
|||
glm::vec3 playerRender = core::coords::canonicalToRender(glm::vec3(mi.x, mi.y, mi.z));
|
||||
|
||||
// Camera bearing for minimap rotation
|
||||
glm::vec3 fwd = camera->getForward();
|
||||
float bearing = std::atan2(-fwd.x, fwd.y);
|
||||
float cosB = std::cos(bearing);
|
||||
float sinB = std::sin(bearing);
|
||||
float bearing = 0.0f;
|
||||
float cosB = 1.0f;
|
||||
float sinB = 0.0f;
|
||||
if (minimapRotate_) {
|
||||
glm::vec3 fwd = camera->getForward();
|
||||
bearing = std::atan2(-fwd.x, fwd.y);
|
||||
cosB = std::cos(bearing);
|
||||
sinB = std::sin(bearing);
|
||||
}
|
||||
|
||||
// Draw north indicator when rotating (points to world north on screen).
|
||||
if (minimapRotate_) {
|
||||
auto* drawList = ImGui::GetForegroundDrawList();
|
||||
ImU32 fill = IM_COL32(0, 0, 0, 230);
|
||||
ImU32 outline = IM_COL32(0, 0, 0, 220);
|
||||
float tipDist = mapRadius - 8.0f;
|
||||
float baseDist = tipDist - 10.0f;
|
||||
float nAng = -bearing; // map rotated by bearing; north rotates opposite
|
||||
float cN = std::cos(nAng);
|
||||
float sN = std::sin(nAng);
|
||||
|
||||
auto rot = [&](float x, float y) -> ImVec2 {
|
||||
return ImVec2(centerX + x * cN - y * sN, centerY + x * sN + y * cN);
|
||||
};
|
||||
|
||||
ImVec2 textPos = rot(0.0f, -(baseDist + 9.0f));
|
||||
drawList->AddText(ImVec2(textPos.x - 4.0f, textPos.y), outline, "N");
|
||||
drawList->AddText(ImVec2(textPos.x - 4.0f, textPos.y), fill, "N");
|
||||
}
|
||||
|
||||
if (statuses.empty()) return;
|
||||
|
||||
auto* drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
|
|
@ -3895,6 +3932,7 @@ void GameScreen::saveSettings() {
|
|||
}
|
||||
|
||||
out << "ui_opacity=" << pendingUiOpacity << "\n";
|
||||
out << "minimap_rotate=" << (pendingMinimapRotate ? 1 : 0) << "\n";
|
||||
LOG_INFO("Settings saved to ", path);
|
||||
}
|
||||
|
||||
|
|
@ -3918,6 +3956,12 @@ void GameScreen::loadSettings() {
|
|||
uiOpacity_ = static_cast<float>(v) / 100.0f;
|
||||
}
|
||||
} catch (...) {}
|
||||
} else if (key == "minimap_rotate") {
|
||||
try {
|
||||
int v = std::stoi(val);
|
||||
minimapRotate_ = (v != 0);
|
||||
pendingMinimapRotate = minimapRotate_;
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
LOG_INFO("Settings loaded from ", path);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue