Head Rotation

(way too hardcoded, not ideal but it will do in the meantime as many have been asking for it)
This commit is contained in:
Kae 2024-12-26 20:53:06 +11:00
parent 3df5cb78da
commit 0a1a82b18b
6 changed files with 126 additions and 15 deletions

View File

@ -54,6 +54,24 @@
"instrumentSlider" : { "type" : "slider", "position" : [62, 158], "gridImage" : "/interface/optionsmenu/largeselection.png" },
"instrumentLabel" : { "type" : "label", "position" : [32, 158], "value" : "Tunes" },
"instrumentValueLabel" : { "type" : "label", "position" : [192, 158], "hAnchor" : "mid", "value" : "Replace Me" }
"instrumentValueLabel" : { "type" : "label", "position" : [192, 158], "hAnchor" : "mid", "value" : "Replace Me" },
"headRotationLabel" : {
"type" : "label",
"position" : [25, 51],
"hAnchor" : "left",
"value" : "HEAD ROTATION"
},
"headRotationCheckbox" : {
"type" : "button",
"pressedOffset" : [0, 0],
"position" : [104, 51],
"base" : "/interface/optionsmenu/checkboxnocheck.png",
"hover" : "/interface/optionsmenu/checkboxnocheckhover.png",
"baseImageChecked" : "/interface/optionsmenu/checkboxcheck.png",
"hoverImageChecked" : "/interface/optionsmenu/checkboxcheckhover.png",
"checkable" : true,
"checked" : true
}
}
}

View File

@ -10,6 +10,7 @@
#include "StarVoiceSettingsMenu.hpp"
#include "StarBindingsMenu.hpp"
#include "StarGraphicsMenu.hpp"
#include "StarHumanoid.hpp"
namespace Star {
@ -47,6 +48,9 @@ OptionsMenu::OptionsMenu(PaneManager* manager, UniverseClientPtr client)
reader.registerCallback("allowAssetsMismatchCheckbox", [=](Widget*) {
updateAllowAssetsMismatch();
});
reader.registerCallback("headRotationCheckbox", [=](Widget*) {
updateHeadRotation();
});
reader.registerCallback("backButton", [=](Widget*) {
dismiss();
});
@ -77,6 +81,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager, UniverseClientPtr client)
m_clientIPJoinableButton = fetchChild<ButtonWidget>("clientIPJoinableCheckbox");
m_clientP2PJoinableButton = fetchChild<ButtonWidget>("clientP2PJoinableCheckbox");
m_allowAssetsMismatchButton = fetchChild<ButtonWidget>("allowAssetsMismatchCheckbox");
m_headRotationButton = fetchChild<ButtonWidget>("headRotationCheckbox");
m_instrumentLabel = fetchChild<LabelWidget>("instrumentValueLabel");
m_sfxLabel = fetchChild<LabelWidget>("sfxValueLabel");
@ -115,7 +120,8 @@ StringList const OptionsMenu::ConfigKeys = {
"tutorialMessages",
"clientIPJoinable",
"clientP2PJoinable",
"allowAssetsMismatch"
"allowAssetsMismatch",
"humanoidHeadRotation"
};
void OptionsMenu::initConfig() {
@ -166,6 +172,12 @@ void OptionsMenu::updateAllowAssetsMismatch() {
Root::singleton().configuration()->set("allowAssetsMismatch", m_allowAssetsMismatchButton->isChecked());
}
void OptionsMenu::updateHeadRotation() {
m_localChanges.set("humanoidHeadRotation", m_headRotationButton->isChecked());
Root::singleton().configuration()->set("humanoidHeadRotation", m_headRotationButton->isChecked());
Humanoid::globalHeadRotation() = m_headRotationButton->isChecked();
}
void OptionsMenu::syncGuiToConf() {
m_instrumentSlider->setVal(m_localChanges.get("instrumentVol").toInt(), false);
m_instrumentLabel->setText(toString(m_instrumentSlider->val()));
@ -180,6 +192,7 @@ void OptionsMenu::syncGuiToConf() {
m_clientIPJoinableButton->setChecked(m_localChanges.get("clientIPJoinable").toBool());
m_clientP2PJoinableButton->setChecked(m_localChanges.get("clientP2PJoinable").toBool());
m_allowAssetsMismatchButton->setChecked(m_localChanges.get("allowAssetsMismatch").toBool());
m_headRotationButton->setChecked(m_localChanges.get("humanoidHeadRotation").optBool().value(true));
auto appController = GuiContext::singleton().applicationController();
if (!appController->p2pNetworkingService()) {

View File

@ -36,6 +36,7 @@ private:
void updateClientIPJoinable();
void updateClientP2PJoinable();
void updateAllowAssetsMismatch();
void updateHeadRotation();
void syncGuiToConf();
@ -52,6 +53,7 @@ private:
ButtonWidgetPtr m_clientIPJoinableButton;
ButtonWidgetPtr m_clientP2PJoinableButton;
ButtonWidgetPtr m_allowAssetsMismatchButton;
ButtonWidgetPtr m_headRotationButton;
LabelWidgetPtr m_instrumentLabel;
LabelWidgetPtr m_sfxLabel;

View File

@ -242,6 +242,14 @@ EnumMap<Humanoid::State> const Humanoid::StateNames{
{Humanoid::State::Lay, "lay"},
};
// gross, but I don't want to make config calls more than I need to
bool& Humanoid::globalHeadRotation(Maybe<bool> default) {
static Maybe<bool> s_headRotation;
if (!s_headRotation)
s_headRotation = Root::singleton().configuration()->get("humanoidHeadRotation").optBool().value(true);
return *s_headRotation;
};
Humanoid::Humanoid(Json const& config) {
loadConfig(config);
@ -252,7 +260,7 @@ Humanoid::Humanoid(Json const& config) {
m_movingBackwards = false;
m_altHand.angle = 0;
m_facingDirection = Direction::Left;
m_rotation = 0;
m_headRotationTarget = m_headRotation = m_rotation = 0;
m_scale = Vec2F::filled(1.f);
m_drawVaporTrail = false;
m_state = State::Idle;
@ -412,6 +420,10 @@ void Humanoid::setMovingBackwards(bool movingBackwards) {
m_movingBackwards = movingBackwards;
}
void Humanoid::setHeadRotation(float headRotation) {
m_headRotationTarget = headRotation;
}
void Humanoid::setRotation(float rotation) {
m_rotation = rotation;
}
@ -476,6 +488,10 @@ void Humanoid::setPrimaryHandNonRotatedDrawables(List<Drawable> drawables) {
m_primaryHand.nonRotatedDrawables = std::move(drawables);
}
bool Humanoid::primaryHandHoldingItem() const {
return m_primaryHand.holdingItem;
}
void Humanoid::setAltHandParameters(bool holdingItem, float angle, float itemAngle, bool recoil,
bool outsideOfHand) {
m_altHand.holdingItem = holdingItem;
@ -498,16 +514,24 @@ void Humanoid::setAltHandNonRotatedDrawables(List<Drawable> drawables) {
m_altHand.nonRotatedDrawables = std::move(drawables);
}
bool Humanoid::altHandHoldingItem() const {
return m_altHand.holdingItem;
}
void Humanoid::animate(float dt) {
m_animationTimer += dt;
m_emoteAnimationTimer += dt;
m_danceTimer += dt;
float headRotationTarget = globalHeadRotation() ? m_headRotationTarget : 0.f;
float diff = angleDiff(m_headRotation, headRotationTarget);
m_headRotation = (headRotationTarget - (headRotationTarget - m_headRotation) * powf(.333333f, dt * 60.f));
}
void Humanoid::resetAnimation() {
m_animationTimer = 0.0f;
m_emoteAnimationTimer = 0.0f;
m_danceTimer = 0.0f;
m_headRotation = globalHeadRotation() ? 0.f : m_headRotationTarget;
}
List<Drawable> Humanoid::render(bool withItems, bool withRotationAndScale) {
@ -641,11 +665,28 @@ List<Drawable> Humanoid::render(bool withItems, bool withRotationAndScale) {
else if (m_state == Lay)
headPosition += m_headLayOffset;
auto addHeadDrawable = [&](Drawable drawable, bool forceFullbright = false) {
if (m_facingDirection == Direction::Left)
drawable.scale(Vec2F(-1, 1));
drawable.fullbright |= forceFullbright;
if (m_headRotation != 0.f) {
float dir = numericalDirection(m_facingDirection);
Vec2F rotationPoint = headPosition;
rotationPoint[0] *= dir;
rotationPoint[1] -= .25f;
float headX = (m_headRotation / ((float)Constants::pi * 2.f));
drawable.rotate(m_headRotation, rotationPoint);
drawable.position[0] -= state() == State::Run ? (fmaxf(headX * dir, 0.f) * 2.f) * dir : headX;
drawable.position[1] -= fabsf(m_headRotation / ((float)Constants::pi * 4.f));
}
drawables.append(std::move(drawable));
};
if (!m_headFrameset.empty() && !m_bodyHidden) {
String image = strf("{}:normal", m_headFrameset);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
drawable.imagePart().addDirectives(getBodyDirectives(), true);
addDrawable(std::move(drawable), m_bodyFullbright);
addHeadDrawable(std::move(drawable), m_bodyFullbright);
}
if (!m_emoteFrameset.empty() && !m_bodyHidden) {
@ -653,14 +694,14 @@ List<Drawable> Humanoid::render(bool withItems, bool withRotationAndScale) {
String image = strf("{}:{}.{}{}", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq, emoteDirectives.prefix());
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
drawable.imagePart().addDirectives(emoteDirectives, true);
addDrawable(std::move(drawable), m_bodyFullbright);
addHeadDrawable(std::move(drawable), m_bodyFullbright);
}
if (!m_hairFrameset.empty() && !m_bodyHidden) {
String image = strf("{}:normal", m_hairFrameset);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
drawable.imagePart().addDirectives(getHairDirectives(), true).addDirectives(getHelmetMaskDirectives(), true);
addDrawable(std::move(drawable), m_bodyFullbright);
addHeadDrawable(std::move(drawable), m_bodyFullbright);
}
if (!m_bodyFrameset.empty() && !m_bodyHidden) {
@ -719,21 +760,21 @@ List<Drawable> Humanoid::render(bool withItems, bool withRotationAndScale) {
String image = strf("{}:normal", m_facialHairFrameset);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
drawable.imagePart().addDirectives(getFacialHairDirectives(), true).addDirectives(getHelmetMaskDirectives(), true);
addDrawable(std::move(drawable), m_bodyFullbright);
addHeadDrawable(std::move(drawable), m_bodyFullbright);
}
if (!m_facialMaskFrameset.empty() && !m_bodyHidden) {
String image = strf("{}:normal", m_facialMaskFrameset);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
drawable.imagePart().addDirectives(getFacialMaskDirectives(), true).addDirectives(getHelmetMaskDirectives(), true);
addDrawable(std::move(drawable));
addHeadDrawable(std::move(drawable));
}
if (!m_headArmorFrameset.empty()) {
String image = strf("{}:normal{}", m_headArmorFrameset, m_headArmorDirectives.prefix());
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
drawable.imagePart().addDirectives(getHeadDirectives(), true);
addDrawable(std::move(drawable));
addHeadDrawable(std::move(drawable));
}
auto frontArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable {

View File

@ -100,6 +100,8 @@ public:
};
static EnumMap<State> const StateNames;
static bool& globalHeadRotation(Maybe<bool> default = {});
Humanoid(Json const& config);
Humanoid(HumanoidIdentity const& identity);
Humanoid(Humanoid const&) = default;
@ -163,6 +165,7 @@ public:
void setDance(Maybe<String> const& dance);
void setFacingDirection(Direction facingDirection);
void setMovingBackwards(bool movingBackwards);
void setHeadRotation(float headRotation);
void setRotation(float rotation);
void setScale(Vec2F scale);
@ -183,6 +186,7 @@ public:
void setPrimaryHandFrameOverrides(String backFrameOverride, String frontFrameOverride);
void setPrimaryHandDrawables(List<Drawable> drawables);
void setPrimaryHandNonRotatedDrawables(List<Drawable> drawables);
bool primaryHandHoldingItem() const;
// Same as primary hand.
void setAltHandParameters(bool holdingItem, float angle, float itemAngle, bool recoil,
@ -190,6 +194,7 @@ public:
void setAltHandFrameOverrides(String backFrameOverride, String frontFrameOverride);
void setAltHandDrawables(List<Drawable> drawables);
void setAltHandNonRotatedDrawables(List<Drawable> drawables);
bool altHandHoldingItem() const;
// Updates the animation based on whatever the current animation state is,
// wrapping or clamping animation time as appropriate.
@ -355,6 +360,8 @@ private:
Maybe<String> m_dance;
Direction m_facingDirection;
bool m_movingBackwards;
float m_headRotation;
float m_headRotationTarget;
float m_rotation;
Vec2F m_scale;
bool m_drawVaporTrail;

View File

@ -1021,16 +1021,16 @@ void Player::update(float dt, uint64_t) {
|| m_humanoid->danceCyclicOrEnded() || m_movementController->running())
m_humanoid->setDance({});
m_tools->suppressItems(suppressedItems);
m_tools->tick(dt, m_shifting, m_pendingMoves);
if (auto overrideFacingDirection = m_tools->setupHumanoidHandItems(*m_humanoid, position(), aimPosition()))
m_movementController->controlFace(*overrideFacingDirection);
bool isClient = world()->isClient();
if (isClient)
m_armor->setupHumanoidClothingDrawables(*m_humanoid, forceNude());
m_tools->suppressItems(suppressedItems);
m_tools->tick(dt, m_shifting, m_pendingMoves);
if (auto overrideFacingDirection = m_tools->setupHumanoidHandItems(*m_humanoid, position(), aimPosition()))
m_movementController->controlFace(*overrideFacingDirection);
m_effectsAnimator->resetTransformationGroup("flip");
if (m_movementController->facingDirection() == Direction::Left)
m_effectsAnimator->scaleTransformationGroup("flip", Vec2F(-1, 1));
@ -1075,6 +1075,35 @@ void Player::update(float dt, uint64_t) {
m_effectEmitter->tick(dt, *entityMode());
if (isClient) {
bool calculateHeadRotation = isMaster();
if (!calculateHeadRotation) {
auto headRotationProperty = getSecretProperty("humanoid.headRotation");
if (headRotationProperty.isType(Json::Type::Float)) {
m_humanoid->setHeadRotation(headRotationProperty.toFloat());
} else
calculateHeadRotation = true;
}
if (calculateHeadRotation) { // master or not an OpenStarbound player
float headRotation = 0.f;
if (m_humanoid->primaryHandHoldingItem() || m_humanoid->altHandHoldingItem()) {
auto primary = m_tools->primaryHandItem();
auto alt = m_tools->altHandItem();
String const disableFlag = "disableHeadRotation";
auto statusFlag = m_statusController->statusProperty(disableFlag);
if (!(statusFlag.isType(Json::Type::Bool) && statusFlag.toBool())
&& !(primary && primary->instanceValue(disableFlag))
&& !(alt && alt->instanceValue(disableFlag))) {
auto diff = world()->geometry().diff(aimPosition(), mouthPosition());
diff.setX(fabsf(diff.x()));
headRotation = diff.angle() * .25f * numericalDirection(m_humanoid->facingDirection());
}
}
m_humanoid->setHeadRotation(headRotation);
setSecretProperty("humanoid.headRotation", headRotation);
}
}
m_humanoid->setFacingDirection(m_movementController->facingDirection());
m_humanoid->setMovingBackwards(m_movementController->facingDirection() != m_movementController->movingDirection());
@ -2610,6 +2639,7 @@ Maybe<StringView> Player::getSecretPropertyView(String const& name) const {
return {};
}
Json Player::getSecretProperty(String const& name, Json defaultValue) const {
if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) {
DataStreamExternalBuffer buffer(tag->utf8Ptr(), tag->utf8Size());