diff --git a/assets/opensb/interface.config.patch b/assets/opensb/interface.config.patch index eb33cb8..c76c812 100644 --- a/assets/opensb/interface.config.patch +++ b/assets/opensb/interface.config.patch @@ -3,7 +3,8 @@ "showMasterNames" : true, "fontDirectives" : "?border=1;222;2220", "inspectOpacityRate" : 0.15, - "movementThreshold" : 0.5 + "movementThreshold" : 0.5, + "offset" : [0, 13] }, "font" : { "defaultDirectives" : "", diff --git a/assets/opensb/interface/windowconfig/chatbubbles.config.patch b/assets/opensb/interface/windowconfig/chatbubbles.config.patch index 5aed747..c3da49d 100644 --- a/assets/opensb/interface/windowconfig/chatbubbles.config.patch +++ b/assets/opensb/interface/windowconfig/chatbubbles.config.patch @@ -1,3 +1,4 @@ { - "movementThreshold" : 0.5 + "movementThreshold" : 0.5, + "bubbleOffset" : [0, 1.875] } \ No newline at end of file diff --git a/source/frontend/StarChatBubbleSeparation.cpp b/source/frontend/StarChatBubbleSeparation.cpp index 568ca37..23a34fe 100644 --- a/source/frontend/StarChatBubbleSeparation.cpp +++ b/source/frontend/StarChatBubbleSeparation.cpp @@ -1,4 +1,5 @@ #include "StarChatBubbleSeparation.hpp" +#include "StarLogging.hpp" namespace Star { @@ -21,7 +22,7 @@ bool compareOverlapRight(RectF const& newBox, RectF const& fixedBox) { template void appendHorizontalOverlaps(List& overlaps, List const& boxes, - List::iterator leftBound, + List::const_iterator leftBound, Compare compare, RectF const& box) { auto i = leftBound; @@ -36,7 +37,7 @@ void appendHorizontalOverlaps(List& overlaps, } } -RectF separateBubble(List& sortedLeftEdges, List& sortedRightEdges, RectF box) { +RectF separateBubble(List const& sortedLeftEdges, List const& sortedRightEdges, List& outLeftEdges, List& outRightEdges, RectF box) { // We have to maintain two lists of boxes: one sorted by the left edges and // one // by the right edges. This is because boxes can be different sizes, and @@ -54,19 +55,33 @@ RectF separateBubble(List& sortedLeftEdges, List& sortedRightEdges // overlap with 'box'. while (true) { - // While box is overlapping any other boxes, raise its Y value. - List overlappingBoxes = - horizontalOverlaps.filtered([&box](RectF const& overlapper) { return box.intersects(overlapper, false); }); + // While box is overlapping any other boxes, move it halfway away. + List overlappingBoxes = horizontalOverlaps.filtered([&box](RectF const& overlapper) { + if (overlapper.intersects(box, false)) { + Vec2F oSize = overlapper.size(), bSize = box.size(); + if (oSize[0] == bSize[0]) { + if (oSize[1] == bSize[1]) + return overlapper.center()[1] <= box.center()[1]; + else + return oSize[1] > bSize[1]; + } + else if (oSize[0] > bSize[0]) + return true; + } + return false; + }); if (overlappingBoxes.empty()) break; RectF overlapBoundBox = fold(overlappingBoxes, box, [](RectF const& a, RectF const& b) { return a.combined(b); }); + SpatialLogger::logPoly("screen", PolyF(box), { 255, 0, 0, 255 }); + SpatialLogger::logPoly("screen", PolyF(overlapBoundBox), { 0, 0, 255, 255 }); auto height = box.height(); box.setYMin(overlapBoundBox.yMax()); box.setYMax(box.yMin() + height); } - sortedLeftEdges.insertSorted(box, compareLeft); - sortedRightEdges.insertSorted(box, compareRight); + outLeftEdges.append(box); + outRightEdges.append(box); return box; } diff --git a/source/frontend/StarChatBubbleSeparation.hpp b/source/frontend/StarChatBubbleSeparation.hpp index 88a860c..756db27 100644 --- a/source/frontend/StarChatBubbleSeparation.hpp +++ b/source/frontend/StarChatBubbleSeparation.hpp @@ -20,13 +20,16 @@ struct BubbleState { Vec2F currentDestination; // The bound box of the nametag if it was at the destination. RectF boundBox; - // The position for the bubble chosen by the algorithm (which it may not + // The separation offset for the bubble chosen by the algorithm (which it may not // have fully moved to yet). - Vec2F separatedPosition; - // The bound box of the bubble around the separatedPosition. + Vec2F seperatedOffset; + // The bound box of the bubble around the separatedOffset. RectF separatedBox; - // Where the bubble is now, which could be anywhere en route to the - // separatedPosition. + // Where the bubble offset is now, which could be anywhere en route to the + // seperatedOffset. + Vec2F currentOffset; + + // The final position. Vec2F currentPosition; }; @@ -63,6 +66,11 @@ private: List m_sortedRightEdges; }; +bool compareLeft(RectF const& a, RectF const& b); +bool compareRight(RectF const& a, RectF const& b); +bool compareOverlapLeft(RectF const& newBox, RectF const& fixedBox); +bool compareOverlapRight(RectF const& newBox, RectF const& fixedBox); + // Shifts box upwards until it is not overlapping any of the boxes in // sortedLeftEdges // and sortedRightEdges. @@ -71,7 +79,7 @@ private: // The two lists contain all the chat bubbles that have been separated, sorted // by // the X positions of their left and right edges respectively. -RectF separateBubble(List& sortedLeftEdges, List& sortedRightEdges, RectF box); +RectF separateBubble(List const& sortedLeftEdges, List const& sortedRightEdges, List& outLeftEdges, List& outRightEdges, RectF box); template BubbleSeparator::BubbleSeparator(float tweenFactor, float movementThreshold) @@ -100,9 +108,10 @@ void BubbleSeparator::setMovementThreshold(float movementThreshold) { template void BubbleSeparator::addBubble(Vec2F position, RectF boundBox, T contents, unsigned margin) { boundBox.setYMax(boundBox.yMax() + margin); - RectF separated = separateBubble(m_sortedLeftEdges, m_sortedRightEdges, boundBox); - Vec2F separatedPosition = position + separated.min() - boundBox.min(); - Bubble bubble = Bubble{contents, position, position, boundBox, separatedPosition, separated, separatedPosition}; + RectF separated = separateBubble(m_sortedLeftEdges, m_sortedRightEdges, m_sortedLeftEdges, m_sortedRightEdges, boundBox); + Vec2F separatedOffset = separated.min() - boundBox.min(); + Vec2F separatedPosition = position + separatedOffset; + Bubble bubble = Bubble{ contents, position, position, boundBox, separatedOffset, separated, separatedOffset, separatedPosition }; m_bubbles.insertSorted(move(bubble), &BubbleSeparator::compareBubbleY); } @@ -126,26 +135,36 @@ List> BubbleSeparator::filtered(function void BubbleSeparator::forEach(function func) { bool anyMoved = false; - m_bubbles.exec([this, func, &anyMoved](Bubble& bubble) { + + List> leftEdges = move(m_sortedLeftEdges); + List> rightEdges = move(m_sortedRightEdges); + + m_bubbles.exec([this, func, &anyMoved, &leftEdges, &rightEdges](Bubble& bubble) { RectF oldBoundBox = bubble.boundBox; func(bubble, bubble.contents); + // Kae: I'm disabling the movement threshold check for now because it also + // stops bubble sorting on bubbles that haven't moved, which causes problems. + Vec2F sizeDelta = bubble.boundBox.size() - oldBoundBox.size(); - Vec2F positionDelta = bubble.idealDestination - bubble.currentDestination; - if (sizeDelta.magnitude() > m_movementThreshold || positionDelta.magnitude() > m_movementThreshold) { - m_sortedLeftEdges.remove(bubble.separatedBox); - m_sortedRightEdges.remove(bubble.separatedBox); - RectF boundBox = bubble.boundBox.translated(positionDelta); - RectF separated = separateBubble(m_sortedLeftEdges, m_sortedRightEdges, boundBox); - anyMoved = true; - bubble.currentDestination = bubble.idealDestination; - bubble.boundBox = boundBox; - bubble.separatedPosition = bubble.idealDestination + separated.min() - boundBox.min(); - bubble.separatedBox = separated; - bubble.currentPosition += positionDelta; - } + leftEdges.remove(bubble.separatedBox); + rightEdges.remove(bubble.separatedBox); + RectF boundBox = RectF::withCenter(bubble.idealDestination, bubble.boundBox.size()); + RectF separated = separateBubble(leftEdges, rightEdges, m_sortedLeftEdges, m_sortedRightEdges, boundBox); + leftEdges.insertSorted(bubble.separatedBox, compareLeft); + rightEdges.insertSorted(bubble.separatedBox, compareRight); + + anyMoved = true; + bubble.currentDestination = bubble.idealDestination; + bubble.boundBox = boundBox; + bubble.seperatedOffset = separated.min() - boundBox.min(); + bubble.separatedBox = separated; }); + + m_sortedLeftEdges.sort(compareLeft); + m_sortedRightEdges.sort(compareRight); + if (anyMoved) m_bubbles.sort(&BubbleSeparator::compareBubbleY); } @@ -153,8 +172,10 @@ void BubbleSeparator::forEach(function func) { template void BubbleSeparator::update() { m_bubbles.exec([this](Bubble& bubble) { - Vec2F delta = bubble.separatedPosition - bubble.currentPosition; - bubble.currentPosition += m_tweenFactor * delta; + Vec2F delta = bubble.seperatedOffset - bubble.currentOffset; + bubble.currentOffset += m_tweenFactor * delta; + + bubble.currentPosition = bubble.currentDestination + bubble.currentOffset; }); } diff --git a/source/frontend/StarNameplatePainter.cpp b/source/frontend/StarNameplatePainter.cpp index d7e589a..0bc96ff 100644 --- a/source/frontend/StarNameplatePainter.cpp +++ b/source/frontend/StarNameplatePainter.cpp @@ -53,7 +53,7 @@ void NameplatePainter::update(WorldClientPtr const& world, WorldCamera const& ca m_nametags.forEach([&world, &camera, this, inspectionMode](BubbleState& bubbleState, Nametag& nametag) { if (auto entity = as(world->entity(nametag.entityId))) { - bubbleState.idealDestination = camera.worldToScreen(entity->position()) + m_offset * camera.pixelRatio(); + bubbleState.idealDestination = camera.worldToScreen(entity->nametagOrigin()) + m_offset * camera.pixelRatio(); bubbleState.boundBox = determineBoundBox(bubbleState.idealDestination, nametag); nametag.statusText = entity->statusText(); diff --git a/source/game/StarMonster.cpp b/source/game/StarMonster.cpp index 2b994d9..c8b69bf 100644 --- a/source/game/StarMonster.cpp +++ b/source/game/StarMonster.cpp @@ -810,6 +810,10 @@ Vec3B Monster::nametagColor() const { return m_monsterVariant.nametagColor; } +Vec2F Monster::nametagOrigin() const { + return mouthPosition(false); +} + bool Monster::aggressive() const { return m_aggressive; } @@ -826,6 +830,10 @@ Vec2F Monster::mouthPosition() const { return mouthOffset() + position(); } +Vec2F Monster::mouthPosition(bool) const { + return mouthPosition(); +} + List Monster::pullPendingChatActions() { return std::move(m_pendingChatActions); } diff --git a/source/game/StarMonster.hpp b/source/game/StarMonster.hpp index 891c12d..ea95521 100644 --- a/source/game/StarMonster.hpp +++ b/source/game/StarMonster.hpp @@ -111,6 +111,7 @@ public: Maybe statusText() const override; bool displayNametag() const override; Vec3B nametagColor() const override; + Vec2F nametagOrigin() const override; bool aggressive() const override; @@ -118,6 +119,7 @@ public: Maybe evalScript(String const& code) override; virtual Vec2F mouthPosition() const override; + virtual Vec2F mouthPosition(bool ignoreAdjustments) const override; virtual List pullPendingChatActions() override; List forceRegions() const override; diff --git a/source/game/StarNpc.cpp b/source/game/StarNpc.cpp index b7a07ae..bdeb7b5 100644 --- a/source/game/StarNpc.cpp +++ b/source/game/StarNpc.cpp @@ -216,9 +216,9 @@ RectF Npc::metaBoundBox() const { return RectF(-4, -4, 4, 4); } -Vec2F Npc::mouthOffset() const { - return Vec2F{m_humanoid.mouthOffset(true)[0] * numericalDirection(m_humanoid.facingDirection()), - m_humanoid.mouthOffset(true)[1]}; +Vec2F Npc::mouthOffset(bool ignoreAdjustments) const { + return Vec2F{m_humanoid.mouthOffset(ignoreAdjustments)[0] * numericalDirection(m_humanoid.facingDirection()), + m_humanoid.mouthOffset(ignoreAdjustments)[1]}; } Vec2F Npc::feetOffset() const { @@ -508,6 +508,10 @@ Vec3B Npc::nametagColor() const { return m_npcVariant.nametagColor; } +Vec2F Npc::nametagOrigin() const { + return mouthPosition(false); +} + bool Npc::aggressive() const { return m_aggressive.get(); } @@ -804,7 +808,11 @@ void Npc::getNetStates(bool initial) { } Vec2F Npc::mouthPosition() const { - return mouthOffset() + position(); + return mouthOffset(true) + position(); +} + +Vec2F Npc::mouthPosition(bool ignoreAdjustments) const { + return mouthOffset(ignoreAdjustments) + position(); } List Npc::pullPendingChatActions() { diff --git a/source/game/StarNpc.hpp b/source/game/StarNpc.hpp index 854eea6..193ffce 100644 --- a/source/game/StarNpc.hpp +++ b/source/game/StarNpc.hpp @@ -57,7 +57,7 @@ public: Vec2F position() const override; RectF metaBoundBox() const override; - Vec2F mouthOffset() const; + Vec2F mouthOffset(bool ignoreAdjustments = true) const; Vec2F feetOffset() const; Vec2F headArmorOffset() const; Vec2F chestArmorOffset() const; @@ -105,6 +105,7 @@ public: Maybe statusText() const override; bool displayNametag() const override; Vec3B nametagColor() const override; + Vec2F nametagOrigin() const override; bool aggressive() const; @@ -112,6 +113,7 @@ public: Maybe evalScript(String const& code) override; Vec2F mouthPosition() const override; + Vec2F mouthPosition(bool ignoreAdjustments) const override; List pullPendingChatActions() override; bool isInteractive() const override; diff --git a/source/game/StarObject.cpp b/source/game/StarObject.cpp index 43a8574..dc19d74 100644 --- a/source/game/StarObject.cpp +++ b/source/game/StarObject.cpp @@ -1193,6 +1193,10 @@ Vec2F Object::mouthPosition() const { } } +Vec2F Object::mouthPosition(bool) const { + return mouthPosition(); +} + List Object::pullPendingChatActions() { return std::move(m_pendingChatActions); } diff --git a/source/game/StarObject.hpp b/source/game/StarObject.hpp index eb039e8..de3f370 100644 --- a/source/game/StarObject.hpp +++ b/source/game/StarObject.hpp @@ -114,6 +114,7 @@ public: Maybe evalScript(String const& code) override; virtual Vec2F mouthPosition() const override; + virtual Vec2F mouthPosition(bool ignoreAdjustments) const override; virtual List pullPendingChatActions() override; void breakObject(bool smash = true); diff --git a/source/game/StarPlayer.cpp b/source/game/StarPlayer.cpp index bb47704..3eb464b 100644 --- a/source/game/StarPlayer.cpp +++ b/source/game/StarPlayer.cpp @@ -580,9 +580,9 @@ Vec2F Player::velocity() const { return m_movementController->velocity(); } -Vec2F Player::mouthOffset() const { +Vec2F Player::mouthOffset(bool ignoreAdjustments) const { return Vec2F( - m_humanoid->mouthOffset(true)[0] * numericalDirection(facingDirection()), m_humanoid->mouthOffset(true)[1]); + m_humanoid->mouthOffset(ignoreAdjustments)[0] * numericalDirection(facingDirection()), m_humanoid->mouthOffset(ignoreAdjustments)[1]); } Vec2F Player::feetOffset() const { @@ -610,7 +610,11 @@ Vec2F Player::legsArmorOffset() const { } Vec2F Player::mouthPosition() const { - return position() + mouthOffset(); + return position() + mouthOffset(true); +} + +Vec2F Player::mouthPosition(bool ignoreAdjustments) const { + return position() + mouthOffset(ignoreAdjustments); } RectF Player::collisionArea() const { @@ -1933,6 +1937,10 @@ Vec3B Player::nametagColor() const { return jsonToVec3B(assets->json("/player.config:nametagColor")); } +Vec2F Player::nametagOrigin() const { + return mouthPosition(false); +} + void Player::setBodyDirectives(String const& directives) { m_identity.bodyDirectives = directives; m_identityUpdated = true; diff --git a/source/game/StarPlayer.hpp b/source/game/StarPlayer.hpp index 30238c4..b4fe623 100644 --- a/source/game/StarPlayer.hpp +++ b/source/game/StarPlayer.hpp @@ -86,7 +86,8 @@ public: Vec2F velocity() const override; Vec2F mouthPosition() const override; - Vec2F mouthOffset() const; + Vec2F mouthPosition(bool ignoreAdjustments) const override; + Vec2F mouthOffset(bool ignoreAdjustments = true) const; Vec2F feetOffset() const; Vec2F headArmorOffset() const; Vec2F chestArmorOffset() const; @@ -284,6 +285,7 @@ public: Maybe statusText() const override; bool displayNametag() const override; Vec3B nametagColor() const override; + Vec2F nametagOrigin() const override; void setBodyDirectives(String const& directives); void setHairType(String const& group, String const& type); diff --git a/source/game/interfaces/StarChattyEntity.hpp b/source/game/interfaces/StarChattyEntity.hpp index 57c590a..03c1ac9 100644 --- a/source/game/interfaces/StarChattyEntity.hpp +++ b/source/game/interfaces/StarChattyEntity.hpp @@ -10,7 +10,8 @@ STAR_CLASS(ChattyEntity); class ChattyEntity : public virtual Entity { public: - virtual Vec2F mouthPosition() const = 0; + virtual Vec2F mouthPosition() const { return mouthPosition(true); }; + virtual Vec2F mouthPosition(bool) const = 0; virtual List pullPendingChatActions() = 0; }; diff --git a/source/game/interfaces/StarNametagEntity.hpp b/source/game/interfaces/StarNametagEntity.hpp index c9a9008..02aa643 100644 --- a/source/game/interfaces/StarNametagEntity.hpp +++ b/source/game/interfaces/StarNametagEntity.hpp @@ -13,6 +13,7 @@ public: virtual Maybe statusText() const = 0; virtual bool displayNametag() const = 0; virtual Vec3B nametagColor() const = 0; + virtual Vec2F nametagOrigin() const = 0; }; }