osb/source/game/StarHumanoid.cpp
Kai Blaschke 431a9c00a5
Fixed a huge amount of Clang warnings
On Linux and macOS, using Clang to compile OpenStarbound produces about 400 MB worth of warnings during the build, making the compiler output unreadable and slowing the build down considerably.

99% of the warnings were unqualified uses of std::move and std::forward, which are now all properly qualified.

Fixed a few other minor warnings about non-virtual destructors and some uses of std::move preventing copy elision on temporary objects.

Most remaining warnings are now unused parameters.
2024-02-19 16:55:19 +01:00

1378 lines
48 KiB
C++

#include "StarHumanoid.hpp"
#include "StarRoot.hpp"
#include "StarJsonExtra.hpp"
#include "StarDataStreamExtra.hpp"
#include "StarArmors.hpp"
#include "StarParticleDatabase.hpp"
#include "StarAssets.hpp"
#include "StarSpeciesDatabase.hpp"
#include "StarDanceDatabase.hpp"
namespace Star {
extern EnumMap<HumanoidEmote> const HumanoidEmoteNames{
{HumanoidEmote::Idle, "Idle"},
{HumanoidEmote::Blabbering, "Blabbering"},
{HumanoidEmote::Shouting, "Shouting"},
{HumanoidEmote::Happy, "Happy"},
{HumanoidEmote::Sad, "Sad"},
{HumanoidEmote::NEUTRAL, "NEUTRAL"},
{HumanoidEmote::Laugh, "Laugh"},
{HumanoidEmote::Annoyed, "Annoyed"},
{HumanoidEmote::Oh, "Oh"},
{HumanoidEmote::OOOH, "OOOH"},
{HumanoidEmote::Blink, "Blink"},
{HumanoidEmote::Wink, "Wink"},
{HumanoidEmote::Eat, "Eat"},
{HumanoidEmote::Sleep, "Sleep"}
};
Personality parsePersonalityArray(Json const& config) {
return Personality{config.getString(0), config.getString(1), jsonToVec2F(config.get(2)), jsonToVec2F(config.get(3))};
}
Personality& parsePersonality(Personality& personality, Json const& config) {
if (auto idle = config.get("idle"))
personality.idle = idle.toString();
if (auto armIdle = config.get("armIdle"))
personality.armIdle = armIdle.toString();
if (auto headOffset = config.get("headOffset"))
personality.headOffset = jsonToVec2F(headOffset);
if (auto armOffset = config.get("armOffset"))
personality.armOffset = jsonToVec2F(armOffset);
return personality;
}
Personality parsePersonality(Json const& config) {
Personality personality;
return parsePersonality(personality, config);
}
Json jsonFromPersonality(Personality const& personality) {
return JsonObject{
{ "idle", personality.idle },
{ "armIdle", personality.armIdle },
{ "headOffset", jsonFromVec2F(personality.headOffset) },
{ "armOffset", jsonFromVec2F(personality.armOffset) }
};
}
HumanoidIdentity::HumanoidIdentity(Json config) {
if (config.isNull())
config = JsonObject();
name = config.getString("name", "Humanoid");
species = config.getString("species", "human");
gender = GenderNames.getLeft(config.getString("gender", "male"));
hairGroup = config.getString("hairGroup", "hair");
hairType = config.getString("hairType", "male1");
hairDirectives = config.getString("hairDirectives", "");
bodyDirectives = config.getString("bodyDirectives", "");
if (auto jEmoteDirectives = config.optString("emoteDirectives")) // Passing Directives as a default arg would be inefficient
emoteDirectives = jEmoteDirectives.take();
else
emoteDirectives = bodyDirectives;
facialHairGroup = config.getString("facialHairGroup", "");
facialHairType = config.getString("facialHairType", "");
facialHairDirectives = config.getString("facialHairDirectives", "");
facialMaskGroup = config.getString("facialMaskGroup", "");
facialMaskType = config.getString("facialMaskType", "");
facialMaskDirectives = config.getString("facialMaskDirectives", "");
personality.idle = config.getString("personalityIdle", "idle.1");
personality.armIdle = config.getString("personalityArmIdle", "idle.1");
personality.headOffset = jsonToVec2F(config.get("personalityHeadOffset", JsonArray{0, 0}));
personality.armOffset = jsonToVec2F(config.get("personalityArmOffset", JsonArray{0, 0}));
color = jsonToColor(config.get("color", "white")).toRgba();
imagePath = config.optString("imagePath");
}
Json HumanoidIdentity::toJson() const {
auto result = JsonObject{
{"name", name},
{"species", species},
{"gender", GenderNames.getRight(gender)},
{"hairGroup", hairGroup},
{"hairType", hairType},
{"hairDirectives", hairDirectives.string()},
{"bodyDirectives", bodyDirectives.string()},
{"emoteDirectives", emoteDirectives.string()},
{"facialHairGroup", facialHairGroup},
{"facialHairType", facialHairType},
{"facialHairDirectives", facialHairDirectives.string()},
{"facialMaskGroup", facialMaskGroup},
{"facialMaskType", facialMaskType},
{"facialMaskDirectives", facialMaskDirectives.string()},
{"personalityIdle", personality.idle},
{"personalityArmIdle", personality.armIdle},
{"personalityHeadOffset", jsonFromVec2F(personality.headOffset)},
{"personalityArmOffset", jsonFromVec2F(personality.armOffset)},
{"color", jsonFromColor(Color::rgba(color))}
};
if (imagePath)
result["imagePath"] = *imagePath;
return result;
}
DataStream& operator>>(DataStream& ds, HumanoidIdentity& identity) {
ds.read(identity.name);
ds.read(identity.species);
ds.read(identity.gender);
ds.read(identity.hairGroup);
ds.read(identity.hairType);
ds.read(identity.hairDirectives);
ds.read(identity.bodyDirectives);
ds.read(identity.emoteDirectives);
ds.read(identity.facialHairGroup);
ds.read(identity.facialHairType);
ds.read(identity.facialHairDirectives);
ds.read(identity.facialMaskGroup);
ds.read(identity.facialMaskType);
ds.read(identity.facialMaskDirectives);
ds.read(identity.personality.idle);
ds.read(identity.personality.armIdle);
ds.read(identity.personality.headOffset);
ds.read(identity.personality.armOffset);
ds.read(identity.color);
ds.read(identity.imagePath);
return ds;
}
DataStream& operator<<(DataStream& ds, HumanoidIdentity const& identity) {
ds.write(identity.name);
ds.write(identity.species);
ds.write(identity.gender);
ds.write(identity.hairGroup);
ds.write(identity.hairType);
ds.write(identity.hairDirectives);
ds.write(identity.bodyDirectives);
ds.write(identity.emoteDirectives);
ds.write(identity.facialHairGroup);
ds.write(identity.facialHairType);
ds.write(identity.facialHairDirectives);
ds.write(identity.facialMaskGroup);
ds.write(identity.facialMaskType);
ds.write(identity.facialMaskDirectives);
ds.write(identity.personality.idle);
ds.write(identity.personality.armIdle);
ds.write(identity.personality.headOffset);
ds.write(identity.personality.armOffset);
ds.write(identity.color);
ds.write(identity.imagePath);
return ds;
}
Humanoid::HumanoidTiming::HumanoidTiming(Json config) {
if (config.type() != Json::Type::Object) {
auto assets = Root::singleton().assets();
config = assets->json("/humanoid.config:humanoidTiming");
}
if (config.contains("stateCycle"))
stateCycle = jsonToArrayF<STATESIZE>(config.get("stateCycle"));
if (config.contains("stateFrames"))
stateFrames = jsonToArrayU<STATESIZE>(config.get("stateFrames"));
if (config.contains("emoteCycle"))
emoteCycle = jsonToArrayF<EmoteSize>(config.get("emoteCycle"));
if (config.contains("emoteFrames"))
emoteFrames = jsonToArrayU<EmoteSize>(config.get("emoteFrames"));
}
bool Humanoid::HumanoidTiming::cyclicState(State state) {
switch (state) {
case State::Walk:
case State::Run:
case State::Swim:
return true;
default:
return false;
}
}
bool Humanoid::HumanoidTiming::cyclicEmoteState(HumanoidEmote state) {
switch (state) {
case HumanoidEmote::Blabbering:
case HumanoidEmote::Shouting:
case HumanoidEmote::Sad:
case HumanoidEmote::Laugh:
case HumanoidEmote::Eat:
case HumanoidEmote::Sleep:
return true;
default:
return false;
}
}
int Humanoid::HumanoidTiming::stateSeq(float timer, State state) const {
return genericSeq(timer, stateCycle[state], stateFrames[state], cyclicState(state));
}
int Humanoid::HumanoidTiming::emoteStateSeq(float timer, HumanoidEmote state) const {
return genericSeq(timer, emoteCycle[(size_t)state], emoteFrames[(size_t)state], cyclicEmoteState(state));
}
int Humanoid::HumanoidTiming::danceSeq(float timer, DancePtr dance) const {
return genericSeq(timer, dance->cycle, dance->steps.size(), dance->cyclic) - 1;
}
int Humanoid::HumanoidTiming::genericSeq(float timer, float cycle, unsigned frames, bool cyclic) const {
if (cyclic) {
timer = fmod(timer, cycle);
}
return clamp<int>(timer * frames / cycle, 0, frames - 1) + 1;
}
EnumMap<Humanoid::State> const Humanoid::StateNames{
{Humanoid::State::Idle, "idle"},
{Humanoid::State::Walk, "walk"},
{Humanoid::State::Run, "run"},
{Humanoid::State::Jump, "jump"},
{Humanoid::State::Fall, "fall"},
{Humanoid::State::Swim, "swim"},
{Humanoid::State::SwimIdle, "swimIdle"},
{Humanoid::State::Duck, "duck"},
{Humanoid::State::Sit, "sit"},
{Humanoid::State::Lay, "lay"},
};
Humanoid::Humanoid(Json const& config) {
m_timing = HumanoidTiming(config.getObject("humanoidTiming"));
m_globalOffset = jsonToVec2F(config.get("globalOffset")) / TilePixels;
m_headRunOffset = jsonToVec2F(config.get("headRunOffset")) / TilePixels;
m_headSwimOffset = jsonToVec2F(config.get("headSwimOffset")) / TilePixels;
m_runFallOffset = config.get("runFallOffset").toDouble() / TilePixels;
m_duckOffset = config.get("duckOffset").toDouble() / TilePixels;
m_headDuckOffset = jsonToVec2F(config.get("headDuckOffset")) / TilePixels;
m_sitOffset = config.get("sitOffset").toDouble() / TilePixels;
m_layOffset = config.get("layOffset").toDouble() / TilePixels;
m_headSitOffset = jsonToVec2F(config.get("headSitOffset")) / TilePixels;
m_headLayOffset = jsonToVec2F(config.get("headLayOffset")) / TilePixels;
m_recoilOffset = jsonToVec2F(config.get("recoilOffset")) / TilePixels;
m_mouthOffset = jsonToVec2F(config.get("mouthOffset")) / TilePixels;
m_feetOffset = jsonToVec2F(config.get("feetOffset")) / TilePixels;
m_bodyFullbright = config.getBool("bodyFullbright", false);
m_headArmorOffset = jsonToVec2F(config.get("headArmorOffset")) / TilePixels;
m_chestArmorOffset = jsonToVec2F(config.get("chestArmorOffset")) / TilePixels;
m_legsArmorOffset = jsonToVec2F(config.get("legsArmorOffset")) / TilePixels;
m_backArmorOffset = jsonToVec2F(config.get("backArmorOffset")) / TilePixels;
m_bodyHidden = config.getBool("bodyHidden", false);
m_armWalkSeq = jsonToIntList(config.get("armWalkSeq"));
m_armRunSeq = jsonToIntList(config.get("armRunSeq"));
for (auto const& v : config.get("walkBob").toArray())
m_walkBob.append(v.toDouble() / TilePixels);
for (auto const& v : config.get("runBob").toArray())
m_runBob.append(v.toDouble() / TilePixels);
for (auto const& v : config.get("swimBob").toArray())
m_swimBob.append(v.toDouble() / TilePixels);
m_jumpBob = config.get("jumpBob").toDouble() / TilePixels;
m_frontArmRotationCenter = jsonToVec2F(config.get("frontArmRotationCenter")) / TilePixels;
m_backArmRotationCenter = jsonToVec2F(config.get("backArmRotationCenter")) / TilePixels;
m_frontHandPosition = jsonToVec2F(config.get("frontHandPosition")) / TilePixels;
m_backArmOffset = jsonToVec2F(config.get("backArmOffset")) / TilePixels;
m_vaporTrailFrames = config.get("vaporTrailFrames").toInt();
m_vaporTrailCycle = config.get("vaporTrailCycle").toFloat();
m_defaultDeathParticles = config.getString("deathParticles");
m_particleEmitters = config.get("particleEmitters");
m_defaultMovementParameters = config.get("movementParameters");
m_twoHanded = false;
m_primaryHand.holdingItem = false;
m_altHand.holdingItem = false;
m_movingBackwards = false;
m_altHand.angle = 0;
m_facingDirection = Direction::Left;
m_rotation = 0;
m_drawVaporTrail = false;
m_state = State::Idle;
m_emoteState = HumanoidEmote::Idle;
m_dance = {};
m_emoteAnimationTimer = 0;
m_primaryHand.angle = 0;
m_animationTimer = 0.0f;
}
Humanoid::Humanoid(HumanoidIdentity const& identity)
: Humanoid(Root::singleton().speciesDatabase()->species(identity.species)->humanoidConfig()) {
setIdentity(identity);
}
void Humanoid::setIdentity(HumanoidIdentity const& identity) {
m_identity = identity;
m_headFrameset = getHeadFromIdentity();
m_bodyFrameset = getBodyFromIdentity();
m_emoteFrameset = getFacialEmotesFromIdentity();
m_hairFrameset = getHairFromIdentity();
m_facialHairFrameset = getFacialHairFromIdentity();
m_facialMaskFrameset = getFacialMaskFromIdentity();
m_backArmFrameset = getBackArmFromIdentity();
m_frontArmFrameset = getFrontArmFromIdentity();
m_vaporTrailFrameset = getVaporTrailFrameset();
}
HumanoidIdentity const& Humanoid::identity() const {
return m_identity;
}
void Humanoid::setHeadArmorDirectives(Directives directives) {
m_headArmorDirectives = std::move(directives);
}
void Humanoid::setHeadArmorFrameset(String headFrameset) {
m_headArmorFrameset = std::move(headFrameset);
}
void Humanoid::setChestArmorDirectives(Directives directives) {
m_chestArmorDirectives = std::move(directives);
}
void Humanoid::setChestArmorFrameset(String chest) {
m_chestArmorFrameset = std::move(chest);
}
void Humanoid::setBackSleeveFrameset(String backSleeveFrameset) {
m_backSleeveFrameset = std::move(backSleeveFrameset);
}
void Humanoid::setFrontSleeveFrameset(String frontSleeveFrameset) {
m_frontSleeveFrameset = std::move(frontSleeveFrameset);
}
void Humanoid::setLegsArmorDirectives(Directives directives) {
m_legsArmorDirectives = std::move(directives);
}
void Humanoid::setLegsArmorFrameset(String legsFrameset) {
m_legsArmorFrameset = std::move(legsFrameset);
}
void Humanoid::setBackArmorDirectives(Directives directives) {
m_backArmorDirectives = std::move(directives);
}
void Humanoid::setBackArmorFrameset(String backFrameset) {
m_backArmorFrameset = std::move(backFrameset);
}
void Humanoid::setHelmetMaskDirectives(Directives helmetMaskDirectives) {
m_helmetMaskDirectives = std::move(helmetMaskDirectives);
}
void Humanoid::setBodyHidden(bool hidden) {
m_bodyHidden = hidden;
}
void Humanoid::setState(State state) {
if (m_state != state)
m_animationTimer = 0.0f;
m_state = state;
}
void Humanoid::setEmoteState(HumanoidEmote state) {
if (m_emoteState != state)
m_emoteAnimationTimer = 0.0f;
m_emoteState = state;
}
void Humanoid::setDance(Maybe<String> const& dance) {
if (m_dance != dance)
m_danceTimer = 0.0f;
m_dance = dance;
}
void Humanoid::setFacingDirection(Direction facingDirection) {
m_facingDirection = facingDirection;
}
void Humanoid::setMovingBackwards(bool movingBackwards) {
m_movingBackwards = movingBackwards;
}
void Humanoid::setRotation(float rotation) {
m_rotation = rotation;
}
void Humanoid::setVaporTrail(bool enabled) {
m_drawVaporTrail = enabled;
}
Humanoid::State Humanoid::state() const {
return m_state;
}
HumanoidEmote Humanoid::emoteState() const {
return m_emoteState;
}
Maybe<String> Humanoid::dance() const {
return m_dance;
}
bool Humanoid::danceCyclicOrEnded() const {
if (!m_dance)
return false;
auto danceDatabase = Root::singleton().danceDatabase();
auto dance = danceDatabase->getDance(*m_dance);
return dance->cyclic || m_danceTimer > dance->duration;
}
Direction Humanoid::facingDirection() const {
return m_facingDirection;
}
bool Humanoid::movingBackwards() const {
return m_movingBackwards;
}
void Humanoid::setPrimaryHandParameters(bool holdingItem, float angle, float itemAngle, bool twoHanded,
bool recoil, bool outsideOfHand) {
m_primaryHand.holdingItem = holdingItem;
m_primaryHand.angle = angle;
m_primaryHand.itemAngle = itemAngle;
m_twoHanded = twoHanded;
m_primaryHand.recoil = recoil;
m_primaryHand.outsideOfHand = outsideOfHand;
}
void Humanoid::setPrimaryHandFrameOverrides(String backFrameOverride, String frontFrameOverride) {
m_primaryHand.backFrame = !backFrameOverride.empty() ? std::move(backFrameOverride) : "rotation";
m_primaryHand.frontFrame = !frontFrameOverride.empty() ? std::move(frontFrameOverride) : "rotation";
}
void Humanoid::setPrimaryHandDrawables(List<Drawable> drawables) {
m_primaryHand.itemDrawables = std::move(drawables);
}
void Humanoid::setPrimaryHandNonRotatedDrawables(List<Drawable> drawables) {
m_primaryHand.nonRotatedDrawables = std::move(drawables);
}
void Humanoid::setAltHandParameters(bool holdingItem, float angle, float itemAngle, bool recoil,
bool outsideOfHand) {
m_altHand.holdingItem = holdingItem;
m_altHand.angle = angle;
m_altHand.itemAngle = itemAngle;
m_altHand.recoil = recoil;
m_altHand.outsideOfHand = outsideOfHand;
}
void Humanoid::setAltHandFrameOverrides(String backFrameOverride, String frontFrameOverride) {
m_altHand.backFrame = !backFrameOverride.empty() ? std::move(backFrameOverride) : "rotation";
m_altHand.frontFrame = !frontFrameOverride.empty() ? std::move(frontFrameOverride) : "rotation";
}
void Humanoid::setAltHandDrawables(List<Drawable> drawables) {
m_altHand.itemDrawables = std::move(drawables);
}
void Humanoid::setAltHandNonRotatedDrawables(List<Drawable> drawables) {
m_altHand.nonRotatedDrawables = std::move(drawables);
}
void Humanoid::animate(float dt) {
m_animationTimer += dt;
m_emoteAnimationTimer += dt;
m_danceTimer += dt;
}
void Humanoid::resetAnimation() {
m_animationTimer = 0.0f;
m_emoteAnimationTimer = 0.0f;
m_danceTimer = 0.0f;
}
List<Drawable> Humanoid::render(bool withItems, bool withRotation) {
List<Drawable> drawables;
int armStateSeq = getArmStateSequence();
int bodyStateSeq = getBodyStateSequence();
int emoteStateSeq = getEmoteStateSequence();
float bobYOffset = getBobYOffset();
Maybe<DancePtr> dance = getDance();
Maybe<DanceStep> danceStep = {};
if (dance.isValid()) {
if (!(*dance)->states.contains(StateNames.getRight(m_state)))
dance = {};
else
danceStep = (*dance)->steps[m_timing.danceSeq(m_danceTimer, *dance)];
}
auto frontHand = (m_facingDirection == Direction::Left || m_twoHanded) ? m_primaryHand : m_altHand;
auto backHand = (m_facingDirection == Direction::Right || m_twoHanded) ? m_primaryHand : m_altHand;
Vec2F frontArmFrameOffset = Vec2F(0, bobYOffset);
if (frontHand.recoil)
frontArmFrameOffset += m_recoilOffset;
Vec2F backArmFrameOffset = Vec2F(0, bobYOffset);
if (backHand.recoil)
backArmFrameOffset += m_recoilOffset;
auto addDrawable = [&](Drawable drawable, bool forceFullbright = false) {
if (m_facingDirection == Direction::Left)
drawable.scale(Vec2F(-1, 1));
drawable.fullbright = drawable.fullbright || forceFullbright;
drawables.append(std::move(drawable));
};
auto backArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable {
String image = strf("{}:{}", frameSet, backHand.backFrame);
Drawable backArm = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, backArmFrameOffset);
backArm.imagePart().addDirectives(directives, true);
backArm.rotate(backHand.angle, backArmFrameOffset + m_backArmRotationCenter + m_backArmOffset);
return backArm;
};
if (!m_backArmorFrameset.empty()) {
auto frameGroup = frameBase(m_state);
if (m_movingBackwards && (m_state == State::Run))
frameGroup = "runbackwards";
String image;
if (dance.isValid() && danceStep->bodyFrame)
image = strf("{}:{}", m_backArmorFrameset, *danceStep->bodyFrame);
else if (m_state == Idle)
image = strf("{}:{}", m_backArmorFrameset, m_identity.personality.idle);
else
image = strf("{}:{}.{}", m_backArmorFrameset, frameGroup, bodyStateSeq);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, Vec2F());
drawable.imagePart().addDirectives(getBackDirectives(), true);
addDrawable(std::move(drawable));
}
if (backHand.holdingItem && !dance.isValid() && withItems) {
auto drawItem = [&]() {
for (auto& backHandItem : backHand.itemDrawables) {
backHandItem.translate(m_frontHandPosition + backArmFrameOffset + m_backArmOffset);
backHandItem.rotate(backHand.itemAngle, backArmFrameOffset + m_backArmRotationCenter + m_backArmOffset);
addDrawable(std::move(backHandItem));
}
};
if (!m_twoHanded && backHand.outsideOfHand)
drawItem();
if (!m_backArmFrameset.empty() && !m_bodyHidden)
addDrawable(backArmDrawable(m_backArmFrameset, getBodyDirectives()), m_bodyFullbright);
if (!m_backSleeveFrameset.empty())
addDrawable(backArmDrawable(m_backSleeveFrameset, getChestDirectives()));
if (!m_twoHanded && !backHand.outsideOfHand)
drawItem();
} else {
if (!m_backArmFrameset.empty() && !m_bodyHidden) {
String image;
Vec2F position;
if (dance.isValid() && danceStep->backArmFrame) {
image = strf("{}:{}", m_backArmFrameset, *danceStep->backArmFrame);
position = danceStep->backArmOffset / TilePixels;
} else if (m_state == Idle) {
image = strf("{}:{}", m_backArmFrameset, m_identity.personality.armIdle);
position = m_identity.personality.armOffset / TilePixels;
} else
image = strf("{}:{}.{}", m_backArmFrameset, frameBase(m_state), armStateSeq);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position);
drawable.imagePart().addDirectives(getBodyDirectives(), true);
if (dance.isValid())
drawable.rotate(danceStep->backArmRotation);
addDrawable(std::move(drawable), m_bodyFullbright);
}
if (!m_backSleeveFrameset.empty()) {
String image;
Vec2F position;
if (dance.isValid() && danceStep->backArmFrame) {
image = strf("{}:{}", m_backSleeveFrameset, *danceStep->backArmFrame);
position = danceStep->backArmOffset / TilePixels;
} else if (m_state == Idle) {
image = strf("{}:{}", m_backSleeveFrameset, m_identity.personality.armIdle);
position = m_identity.personality.armOffset / TilePixels;
} else
image = strf("{}:{}.{}", m_backSleeveFrameset, frameBase(m_state), armStateSeq);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position);
drawable.imagePart().addDirectives(getChestDirectives(), true);
if (dance.isValid())
drawable.rotate(danceStep->backArmRotation);
addDrawable(std::move(drawable));
}
}
Vec2F headPosition(0, bobYOffset);
if (dance.isValid())
headPosition += danceStep->headOffset / TilePixels;
else if (m_state == Idle)
headPosition += m_identity.personality.headOffset / TilePixels;
else if (m_state == Run)
headPosition += m_headRunOffset;
else if (m_state == Swim || m_state == SwimIdle)
headPosition += m_headSwimOffset;
else if (m_state == Duck)
headPosition += m_headDuckOffset;
else if (m_state == Sit)
headPosition += m_headSitOffset;
else if (m_state == Lay)
headPosition += m_headLayOffset;
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);
}
if (!m_emoteFrameset.empty() && !m_bodyHidden) {
String image = strf("{}:{}.{}", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
drawable.imagePart().addDirectives(getEmoteDirectives(), true);
addDrawable(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);
}
if (!m_bodyFrameset.empty() && !m_bodyHidden) {
String image;
if (dance.isValid() && danceStep->bodyFrame)
image = strf("{}:{}", m_bodyFrameset, *danceStep->bodyFrame);
else if (m_state == Idle)
image = strf("{}:{}", m_bodyFrameset, m_identity.personality.idle);
else
image = strf("{}:{}.{}", m_bodyFrameset, frameBase(m_state), bodyStateSeq);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, {});
drawable.imagePart().addDirectives(getBodyDirectives(), true);
addDrawable(std::move(drawable), m_bodyFullbright);
}
if (!m_legsArmorFrameset.empty()) {
String image;
if (dance.isValid() && danceStep->bodyFrame)
image = strf("{}:{}", m_legsArmorFrameset, *danceStep->bodyFrame);
else if (m_state == Idle)
image = strf("{}:{}", m_legsArmorFrameset, m_identity.personality.idle);
else
image = strf("{}:{}.{}", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, {});
drawable.imagePart().addDirectives(getLegsDirectives(), true);
addDrawable(std::move(drawable));
}
if (!m_chestArmorFrameset.empty()) {
String image;
Vec2F position;
if (dance.isValid() && danceStep->bodyFrame)
image = strf("{}:{}", m_chestArmorFrameset, *danceStep->bodyFrame);
else if (m_state == Run)
image = strf("{}:run", m_chestArmorFrameset);
else if (m_state == Idle)
image = strf("{}:{}", m_chestArmorFrameset, m_identity.personality.idle);
else if (m_state == Duck)
image = strf("{}:duck", m_chestArmorFrameset);
else if ((m_state == Swim) || (m_state == SwimIdle))
image = strf("{}:swim", m_chestArmorFrameset);
else
image = strf("{}:chest.1", m_chestArmorFrameset);
if (m_state != Duck)
position[1] += bobYOffset;
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position);
drawable.imagePart().addDirectives(getChestDirectives(), true);
addDrawable(std::move(drawable));
}
if (!m_facialHairFrameset.empty() && !m_bodyHidden) {
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);
}
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));
}
if (!m_headArmorFrameset.empty()) {
String image = strf("{}:normal", m_headArmorFrameset);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
drawable.imagePart().addDirectives(getHeadDirectives(), true);
addDrawable(std::move(drawable));
}
auto frontArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable {
String image = strf("{}:{}", frameSet, frontHand.frontFrame);
Drawable frontArm = Drawable::makeImage(image, 1.0f / TilePixels, true, frontArmFrameOffset);
frontArm.imagePart().addDirectives(directives, true);
frontArm.rotate(frontHand.angle, frontArmFrameOffset + m_frontArmRotationCenter);
return frontArm;
};
if (frontHand.holdingItem && !dance.isValid() && withItems) {
auto drawItem = [&]() {
for (auto& frontHandItem : frontHand.itemDrawables) {
frontHandItem.translate(m_frontHandPosition + frontArmFrameOffset);
frontHandItem.rotate(frontHand.itemAngle, frontArmFrameOffset + m_frontArmRotationCenter);
addDrawable(frontHandItem);
}
};
if (!frontHand.outsideOfHand)
drawItem();
if (!m_frontArmFrameset.empty() && !m_bodyHidden)
addDrawable(frontArmDrawable(m_frontArmFrameset, getBodyDirectives()), m_bodyFullbright);
if (!m_frontSleeveFrameset.empty())
addDrawable(frontArmDrawable(m_frontSleeveFrameset, getChestDirectives()));
if (frontHand.outsideOfHand)
drawItem();
} else {
if (!m_frontArmFrameset.empty() && !m_bodyHidden) {
String image;
Vec2F position;
if (dance.isValid() && danceStep->frontArmFrame) {
image = strf("{}:{}", m_frontArmFrameset, *danceStep->frontArmFrame);
position = danceStep->frontArmOffset / TilePixels;
} else if (m_state == Idle) {
image = strf("{}:{}", m_frontArmFrameset, m_identity.personality.armIdle);
position = m_identity.personality.armOffset / TilePixels;
} else
image = strf("{}:{}.{}", m_frontArmFrameset, frameBase(m_state), armStateSeq);
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position);
drawable.imagePart().addDirectives(getBodyDirectives(), true);
if (dance.isValid())
drawable.rotate(danceStep->frontArmRotation);
addDrawable(drawable, m_bodyFullbright);
}
if (!m_frontSleeveFrameset.empty()) {
String image;
Vec2F position;
if (dance.isValid() && danceStep->frontArmFrame) {
image = strf("{}:{}", m_frontSleeveFrameset, *danceStep->frontArmFrame);
position = danceStep->frontArmOffset / TilePixels;
} else if (m_state == Idle) {
image = strf("{}:{}", m_frontSleeveFrameset, m_identity.personality.armIdle);
position = m_identity.personality.armOffset / TilePixels;
} else
image = strf("{}:{}.{}", m_frontSleeveFrameset, frameBase(m_state), armStateSeq);
auto drawable = Drawable::makeImage(image, 1.0f / TilePixels, true, position);
drawable.imagePart().addDirectives(getChestDirectives(), true);
if (dance.isValid())
drawable.rotate(danceStep->frontArmRotation);
addDrawable(drawable);
}
}
if (m_drawVaporTrail) {
auto image = strf("{}:{}",
m_vaporTrailFrameset,
m_timing.genericSeq(m_animationTimer, m_vaporTrailCycle, m_vaporTrailFrames, true));
addDrawable(Drawable::makeImage(AssetPath::split(image), 1.0f / TilePixels, true, {}));
}
if (withItems) {
if (m_primaryHand.nonRotatedDrawables.size())
drawables.insertAllAt(0, m_primaryHand.nonRotatedDrawables);
if (m_altHand.nonRotatedDrawables.size())
drawables.insertAllAt(0, m_altHand.nonRotatedDrawables);
}
if (withRotation)
Drawable::rotateAll(drawables, m_rotation);
Drawable::translateAll(drawables, m_globalOffset);
Drawable::rebaseAll(drawables);
return drawables;
}
List<Drawable> Humanoid::renderPortrait(PortraitMode mode) const {
List<Drawable> drawables;
int emoteStateSeq = m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState);
auto addDrawable = [&](Drawable&& drawable) -> Drawable& {
if (mode != PortraitMode::Full && mode != PortraitMode::FullNeutral
&& mode != PortraitMode::FullNude && mode != PortraitMode::FullNeutralNude) {
// TODO: make this configurable
drawable.imagePart().addDirectives(String("addmask=/humanoid/portraitMask.png;0;0"), false);
}
drawables.append(std::move(drawable));
return drawables.back();
};
bool dressed = !(mode == PortraitMode::FullNude || mode == PortraitMode::FullNeutralNude);
Directives helmetMaskDirective = dressed ? getHelmetMaskDirectives() : Directives();
auto personality = m_identity.personality;
if (mode == PortraitMode::FullNeutral || mode == PortraitMode::FullNeutralNude)
personality = Root::singleton().speciesDatabase()->species(m_identity.species)->personalities()[0];
if (mode != PortraitMode::Head) {
if (!m_backArmFrameset.empty()) {
String image = strf("{}:{}", m_backArmFrameset, personality.armIdle);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset);
drawable.imagePart().addDirectives(getBodyDirectives(), true);
addDrawable(std::move(drawable));
}
if (dressed && !m_backSleeveFrameset.empty()) {
String image = strf("{}:{}", m_backSleeveFrameset, personality.armIdle);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset);
drawable.imagePart().addDirectives(getChestDirectives(), true);
addDrawable(std::move(drawable));
}
if (mode != PortraitMode::Bust) {
if (dressed && !m_backArmorFrameset.empty()) {
String image = strf("{}:{}", m_backArmorFrameset, personality.idle);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {});
drawable.imagePart().addDirectives(getBackDirectives(), true);
addDrawable(std::move(drawable));
}
}
}
if (!m_headFrameset.empty()) {
String image = strf("{}:normal", m_headFrameset);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getBodyDirectives(), true);
addDrawable(std::move(drawable));
}
if (!m_emoteFrameset.empty()) {
String image = strf("{}:{}.{}", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getEmoteDirectives(), true);
addDrawable(std::move(drawable));
}
if (!m_hairFrameset.empty()) {
String image = strf("{}:normal", m_hairFrameset);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getHairDirectives(), true).addDirectives(helmetMaskDirective, true);
addDrawable(std::move(drawable));
}
if (!m_bodyFrameset.empty()) {
String image = strf("{}:{}", m_bodyFrameset, personality.idle);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {});
drawable.imagePart().addDirectives(getBodyDirectives(), true);
addDrawable(std::move(drawable));
}
if (mode != PortraitMode::Head) {
if (dressed && !m_legsArmorFrameset.empty()) {
String image = strf("{}:{}", m_legsArmorFrameset, personality.idle);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {});
drawable.imagePart().addDirectives(getLegsDirectives(), true);
addDrawable(std::move(drawable));
}
if (dressed && !m_chestArmorFrameset.empty()) {
String image = strf("{}:{}", m_chestArmorFrameset, personality.idle);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {});
drawable.imagePart().addDirectives(getChestDirectives(), true);
addDrawable(std::move(drawable));
}
}
if (!m_facialHairFrameset.empty()) {
String image = strf("{}:normal", m_facialHairFrameset);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getFacialHairDirectives(), true).addDirectives(helmetMaskDirective, true);
addDrawable(std::move(drawable));
}
if (!m_facialMaskFrameset.empty()) {
String image = strf("{}:normal", m_facialMaskFrameset);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getFacialMaskDirectives(), true).addDirectives(helmetMaskDirective, true);
addDrawable(std::move(drawable));
}
if (dressed && !m_headArmorFrameset.empty()) {
String image = strf("{}:normal", m_headArmorFrameset);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
drawable.imagePart().addDirectives(getHeadDirectives(), true);
addDrawable(std::move(drawable));
}
if (mode != PortraitMode::Head) {
if (!m_frontArmFrameset.empty()) {
String image = strf("{}:{}", m_frontArmFrameset, personality.armIdle);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset);
drawable.imagePart().addDirectives(getBodyDirectives(), true);
addDrawable(std::move(drawable));
}
if (dressed && !m_frontSleeveFrameset.empty()) {
String image = strf("{}:{}", m_frontSleeveFrameset, personality.armIdle);
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset);
drawable.imagePart().addDirectives(getChestDirectives(), true);
addDrawable(std::move(drawable));
}
}
return drawables;
}
List<Drawable> Humanoid::renderSkull() const {
return {Drawable::makeImage(
Root::singleton().speciesDatabase()->species(m_identity.species)->skull(), 1.0f, true, Vec2F())};
}
Humanoid Humanoid::makeDummy(Gender gender) {
auto assets = Root::singleton().assets();
Humanoid humanoid(assets->json("/humanoid.config"));
humanoid.m_headFrameset = assets->json("/humanoid/any/dummy.config:head").toString();
humanoid.m_bodyFrameset = assets->json("/humanoid/any/dummy.config:body").toString();
humanoid.m_frontArmFrameset = assets->json("/humanoid/any/dummy.config:frontArm").toString();
humanoid.m_backArmFrameset = assets->json("/humanoid/any/dummy.config:backArm").toString();
humanoid.setFacingDirection(DirectionNames.getLeft(assets->json("/humanoid/any/dummy.config:direction").toString()));
return humanoid;
}
List<Drawable> Humanoid::renderDummy(Gender gender, Maybe<HeadArmor const*> head, Maybe<ChestArmor const*> chest, Maybe<LegsArmor const*> legs, Maybe<BackArmor const*> back) {
if (head) {
if (auto headPtr = *head) {
setHeadArmorFrameset(headPtr->frameset(gender));
setHeadArmorDirectives(headPtr->directives());
setHelmetMaskDirectives(headPtr->maskDirectives());
}
else {
setHeadArmorFrameset("");
setHeadArmorDirectives("");
setHelmetMaskDirectives("");
}
}
if (chest) {
if (auto chestPtr = *chest) {
setBackSleeveFrameset(chestPtr->backSleeveFrameset(gender));
setFrontSleeveFrameset(chestPtr->frontSleeveFrameset(gender));
setChestArmorFrameset(chestPtr->bodyFrameset(gender));
setChestArmorDirectives(chestPtr->directives());
}
else {
setBackSleeveFrameset("");
setFrontSleeveFrameset("");
setChestArmorFrameset("");
setChestArmorDirectives("");
}
}
if (legs) {
if (auto legsPtr = *legs) {
setLegsArmorFrameset(legsPtr->frameset(gender));
setLegsArmorDirectives(legsPtr->directives());
}
else {
setLegsArmorFrameset("");
setLegsArmorDirectives("");
}
}
if (back) {
if (auto backPtr = *back) {
setBackArmorFrameset(backPtr->frameset(gender));
setBackArmorDirectives(backPtr->directives());
}
else {
setBackArmorFrameset("");
setBackArmorDirectives("");
}
}
auto drawables = render();
Drawable::scaleAll(drawables, TilePixels);
return drawables;
}
Vec2F Humanoid::primaryHandPosition(Vec2F const& offset) const {
return primaryArmPosition(m_facingDirection, m_primaryHand.angle, primaryHandOffset(m_facingDirection) + offset);
}
Vec2F Humanoid::altHandPosition(Vec2F const& offset) const {
return altArmPosition(m_facingDirection, m_altHand.angle, altHandOffset(m_facingDirection) + offset);
}
Vec2F Humanoid::primaryArmPosition(Direction facingDirection, float armAngle, Vec2F const& offset) const {
float bobYOffset = getBobYOffset();
if (m_primaryHand.holdingItem) {
Vec2F rotationCenter;
if (facingDirection == Direction::Left || m_twoHanded)
rotationCenter = m_frontArmRotationCenter + Vec2F(0, bobYOffset);
else
rotationCenter = m_backArmRotationCenter + Vec2F(0, bobYOffset) + m_backArmOffset;
Vec2F position = offset.rotate(armAngle) + rotationCenter;
if (facingDirection == Direction::Left)
position[0] *= -1;
return position;
} else {
// TODO: non-aiming item holding not supported yet
return Vec2F();
}
}
Vec2F Humanoid::altArmPosition(Direction facingDirection, float armAngle, Vec2F const& offset) const {
float bobYOffset = getBobYOffset();
if (m_altHand.holdingItem) {
Vec2F rotationCenter;
if (facingDirection == Direction::Right)
rotationCenter = m_frontArmRotationCenter + Vec2F(0, bobYOffset);
else
rotationCenter = m_backArmRotationCenter + Vec2F(0, bobYOffset) + m_backArmOffset;
Vec2F position = Vec2F(offset).rotate(armAngle) + rotationCenter;
if (facingDirection == Direction::Left)
position[0] *= -1;
return position;
} else {
// TODO: non-aiming item holding not supported yet
return Vec2F();
}
}
Vec2F Humanoid::primaryHandOffset(Direction facingDirection) const {
if (facingDirection == Direction::Left || m_twoHanded)
return m_frontHandPosition - m_frontArmRotationCenter;
else
return m_frontHandPosition - m_backArmRotationCenter;
}
Vec2F Humanoid::altHandOffset(Direction facingDirection) const {
if (facingDirection == Direction::Left || m_twoHanded)
return m_frontHandPosition - m_backArmRotationCenter;
else
return m_frontHandPosition - m_frontArmRotationCenter;
}
String Humanoid::frameBase(State state) const {
switch (state) {
case State::Idle:
return "idle";
case State::Walk:
return "walk";
case State::Run:
return "run";
case State::Jump:
return "jump";
case State::Swim:
return "swim";
case State::SwimIdle:
return "swimIdle";
case State::Duck:
return "duck";
case State::Fall:
return "fall";
case State::Sit:
return "sit";
case State::Lay:
return "lay";
default:
throw StarException(strf("No such state '{}'", StateNames.getRight(state)));
}
}
String Humanoid::emoteFrameBase(HumanoidEmote state) const {
switch (state) {
case HumanoidEmote::Idle:
return "idle";
case HumanoidEmote::Blabbering:
return "blabber";
case HumanoidEmote::Shouting:
return "shout";
case HumanoidEmote::Happy:
return "happy";
case HumanoidEmote::Sad:
return "sad";
case HumanoidEmote::NEUTRAL:
return "neutral";
case HumanoidEmote::Laugh:
return "laugh";
case HumanoidEmote::Annoyed:
return "annoyed";
case HumanoidEmote::Oh:
return "oh";
case HumanoidEmote::OOOH:
return "oooh";
case HumanoidEmote::Blink:
return "blink";
case HumanoidEmote::Wink:
return "wink";
case HumanoidEmote::Eat:
return "eat";
case HumanoidEmote::Sleep:
return "sleep";
default:
throw StarException(strf("No such emote state '{}'", HumanoidEmoteNames.getRight(state)));
}
}
String Humanoid::getHeadFromIdentity() const {
return strf("/humanoid/{}/{}head.png",
m_identity.imagePath ? *m_identity.imagePath : m_identity.species,
GenderNames.getRight(m_identity.gender));
}
String Humanoid::getBodyFromIdentity() const {
return strf("/humanoid/{}/{}body.png",
m_identity.imagePath ? *m_identity.imagePath : m_identity.species,
GenderNames.getRight(m_identity.gender));
}
String Humanoid::getFacialEmotesFromIdentity() const {
return strf("/humanoid/{}/emote.png", m_identity.imagePath ? *m_identity.imagePath : m_identity.species);
}
String Humanoid::getHairFromIdentity() const {
if (m_identity.hairType.empty())
return "";
return strf("/humanoid/{}/{}/{}.png",
m_identity.imagePath ? *m_identity.imagePath : m_identity.species,
m_identity.hairGroup,
m_identity.hairType);
}
String Humanoid::getFacialHairFromIdentity() const {
if (m_identity.facialHairType.empty())
return "";
return strf("/humanoid/{}/{}/{}.png",
m_identity.imagePath ? *m_identity.imagePath : m_identity.species,
m_identity.facialHairGroup,
m_identity.facialHairType);
}
String Humanoid::getFacialMaskFromIdentity() const {
if (m_identity.facialMaskType.empty())
return "";
return strf("/humanoid/{}/{}/{}.png",
m_identity.imagePath ? *m_identity.imagePath : m_identity.species,
m_identity.facialMaskGroup,
m_identity.facialMaskType);
}
String Humanoid::getBackArmFromIdentity() const {
return strf("/humanoid/{}/backarm.png", m_identity.imagePath ? *m_identity.imagePath : m_identity.species);
}
String Humanoid::getFrontArmFromIdentity() const {
return strf("/humanoid/{}/frontarm.png", m_identity.imagePath ? *m_identity.imagePath : m_identity.species);
}
String Humanoid::getVaporTrailFrameset() const {
return "/humanoid/any/flames.png";
}
Directives Humanoid::getBodyDirectives() const {
return m_identity.bodyDirectives;
}
Directives Humanoid::getHairDirectives() const {
return m_identity.hairDirectives;
}
Directives Humanoid::getEmoteDirectives() const {
return m_identity.emoteDirectives;
}
Directives Humanoid::getFacialHairDirectives() const {
return m_identity.facialHairDirectives;
}
Directives Humanoid::getFacialMaskDirectives() const {
return m_identity.facialMaskDirectives;
}
Directives Humanoid::getHelmetMaskDirectives() const {
return m_helmetMaskDirectives;
}
Directives Humanoid::getHeadDirectives() const {
return m_headArmorDirectives;
}
Directives Humanoid::getChestDirectives() const {
return m_chestArmorDirectives;
}
Directives Humanoid::getLegsDirectives() const {
return m_legsArmorDirectives;
}
Directives Humanoid::getBackDirectives() const {
return m_backArmorDirectives;
}
int Humanoid::getEmoteStateSequence() const {
return m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState);
}
int Humanoid::getArmStateSequence() const {
int stateSeq = m_timing.stateSeq(m_animationTimer, m_state);
int armStateSeq;
if (m_state == Walk) {
armStateSeq = m_movingBackwards ? m_armWalkSeq.at(m_armWalkSeq.size() - stateSeq) : m_armWalkSeq.at(stateSeq - 1);
} else if (m_state == Run) {
armStateSeq = m_movingBackwards ? m_armRunSeq.at(m_armRunSeq.size() - stateSeq) : m_armRunSeq.at(stateSeq - 1);
} else {
armStateSeq = stateSeq;
}
return armStateSeq;
}
int Humanoid::getBodyStateSequence() const {
int stateSeq = m_timing.stateSeq(m_animationTimer, m_state);
int bodyStateSeq;
if (m_movingBackwards && (m_state == Walk || m_state == Run))
bodyStateSeq = m_timing.stateFrames[m_state] - stateSeq + 1;
else
bodyStateSeq = stateSeq;
return bodyStateSeq;
}
Maybe<DancePtr> Humanoid::getDance() const {
if (m_dance.isNothing())
return {};
auto danceDatabase = Root::singleton().danceDatabase();
return danceDatabase->getDance(*m_dance);
}
float Humanoid::getBobYOffset() const {
int bodyStateSeq = getBodyStateSequence();
float bobYOffset = 0.0f;
if (m_state == Run) {
bobYOffset = m_runFallOffset + m_runBob.at(bodyStateSeq - 1);
} else if (m_state == Fall) {
bobYOffset = m_runFallOffset;
} else if (m_state == Jump) {
bobYOffset = m_jumpBob;
} else if (m_state == Walk) {
bobYOffset = m_walkBob.at(bodyStateSeq - 1);
} else if (m_state == Swim) {
bobYOffset = m_swimBob.at(bodyStateSeq - 1);
} else if (m_state == Duck) {
bobYOffset = m_duckOffset;
} else if (m_state == Sit) {
bobYOffset = m_sitOffset;
} else if (m_state == Lay) {
bobYOffset = m_layOffset;
}
return bobYOffset;
}
Vec2F Humanoid::armAdjustment() const {
return Vec2F(0, getBobYOffset());
}
Vec2F Humanoid::mouthOffset(bool ignoreAdjustments) const {
if (ignoreAdjustments) {
return (m_mouthOffset).rotate(m_rotation);
} else {
Vec2F headPosition(0, getBobYOffset());
if (m_state == Idle)
headPosition += m_identity.personality.headOffset / TilePixels;
else if (m_state == Run)
headPosition += m_headRunOffset;
else if (m_state == Swim || m_state == SwimIdle)
headPosition += m_headSwimOffset;
else if (m_state == Duck)
headPosition += m_headDuckOffset;
else if (m_state == Sit)
headPosition += m_headSitOffset;
else if (m_state == Lay)
headPosition += m_headLayOffset;
return (m_mouthOffset + headPosition).rotate(m_rotation);
}
}
Vec2F Humanoid::feetOffset() const {
return m_feetOffset.rotate(m_rotation);
}
Vec2F Humanoid::headArmorOffset() const {
Vec2F headPosition(0, getBobYOffset());
if (m_state == Idle)
headPosition += m_identity.personality.headOffset / TilePixels;
else if (m_state == Run)
headPosition += m_headRunOffset;
else if (m_state == Swim || m_state == SwimIdle)
headPosition += m_headSwimOffset;
else if (m_state == Duck)
headPosition += m_headDuckOffset;
else if (m_state == Sit)
headPosition += m_headSitOffset;
else if (m_state == Lay)
headPosition += m_headLayOffset;
return (m_headArmorOffset + headPosition).rotate(m_rotation);
}
Vec2F Humanoid::chestArmorOffset() const {
Vec2F position(0, getBobYOffset());
return (m_chestArmorOffset + position).rotate(m_rotation);
}
Vec2F Humanoid::legsArmorOffset() const {
return m_legsArmorOffset.rotate(m_rotation);
}
Vec2F Humanoid::backArmorOffset() const {
Vec2F position(0, getBobYOffset());
return (m_backArmorOffset + position).rotate(m_rotation);
}
String Humanoid::defaultDeathParticles() const {
return m_defaultDeathParticles;
}
List<Particle> Humanoid::particles(String const& name) const {
auto particleDatabase = Root::singleton().particleDatabase();
List<Particle> res;
Json particles = m_particleEmitters.get(name).get("particles", {});
for (auto& particle : particles.toArray()) {
auto particleSpec = particle.get("particle", {});
res.push_back(particleDatabase->particle(particleSpec));
}
return res;
}
Json const& Humanoid::defaultMovementParameters() const {
return m_defaultMovementParameters;
}
}