2023-06-20 04:33:09 +00:00
|
|
|
#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"}
|
|
|
|
};
|
|
|
|
|
2023-06-28 19:58:24 +00:00
|
|
|
Personality parsePersonalityArray(Json const& config) {
|
2023-06-20 04:33:09 +00:00
|
|
|
return Personality{config.getString(0), config.getString(1), jsonToVec2F(config.get(2)), jsonToVec2F(config.get(3))};
|
|
|
|
}
|
|
|
|
|
2023-06-28 19:58:24 +00:00
|
|
|
Personality& parsePersonality(Personality& personality, Json const& config) {
|
|
|
|
if (auto idle = config.get("idle"))
|
|
|
|
personality.idle = idle.toString();
|
|
|
|
if (auto armIdle = config.get("armIdle"))
|
2023-08-04 04:28:43 +00:00
|
|
|
personality.armIdle = armIdle.toString();
|
2023-06-28 19:58:24 +00:00
|
|
|
if (auto headOffset = config.get("headOffset"))
|
|
|
|
personality.headOffset = jsonToVec2F(headOffset);
|
|
|
|
if (auto armOffset = config.get("armOffset"))
|
|
|
|
personality.armOffset = jsonToVec2F(armOffset);
|
2023-06-29 16:31:41 +00:00
|
|
|
|
|
|
|
return personality;
|
2023-06-28 19:58:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Personality parsePersonality(Json const& config) {
|
2023-06-28 20:38:48 +00:00
|
|
|
Personality personality;
|
|
|
|
return parsePersonality(personality, config);
|
2023-06-28 19:58:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Json jsonFromPersonality(Personality const& personality) {
|
|
|
|
return JsonObject{
|
|
|
|
{ "idle", personality.idle },
|
|
|
|
{ "armIdle", personality.armIdle },
|
|
|
|
{ "headOffset", jsonFromVec2F(personality.headOffset) },
|
|
|
|
{ "armOffset", jsonFromVec2F(personality.armOffset) }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
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", "");
|
2023-06-24 12:49:47 +00:00
|
|
|
if (auto jEmoteDirectives = config.optString("emoteDirectives")) // Passing Directives as a default arg would be inefficient
|
|
|
|
emoteDirectives = jEmoteDirectives.take();
|
|
|
|
else
|
|
|
|
emoteDirectives = bodyDirectives;
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
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},
|
2023-06-25 15:42:18 +00:00
|
|
|
{"hairDirectives", hairDirectives.string()},
|
|
|
|
{"bodyDirectives", bodyDirectives.string()},
|
|
|
|
{"emoteDirectives", emoteDirectives.string()},
|
2023-06-20 04:33:09 +00:00
|
|
|
{"facialHairGroup", facialHairGroup},
|
|
|
|
{"facialHairType", facialHairType},
|
2023-06-25 15:42:18 +00:00
|
|
|
{"facialHairDirectives", facialHairDirectives.string()},
|
2023-06-20 04:33:09 +00:00
|
|
|
{"facialMaskGroup", facialMaskGroup},
|
|
|
|
{"facialMaskType", facialMaskType},
|
2023-06-25 15:42:18 +00:00
|
|
|
{"facialMaskDirectives", facialMaskDirectives.string()},
|
2023-06-20 04:33:09 +00:00
|
|
|
{"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;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
void Humanoid::setHeadArmorDirectives(Directives directives) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_headArmorDirectives = std::move(directives);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setHeadArmorFrameset(String headFrameset) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_headArmorFrameset = std::move(headFrameset);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
void Humanoid::setChestArmorDirectives(Directives directives) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_chestArmorDirectives = std::move(directives);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setChestArmorFrameset(String chest) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_chestArmorFrameset = std::move(chest);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setBackSleeveFrameset(String backSleeveFrameset) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_backSleeveFrameset = std::move(backSleeveFrameset);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setFrontSleeveFrameset(String frontSleeveFrameset) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_frontSleeveFrameset = std::move(frontSleeveFrameset);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
void Humanoid::setLegsArmorDirectives(Directives directives) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_legsArmorDirectives = std::move(directives);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setLegsArmorFrameset(String legsFrameset) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_legsArmorFrameset = std::move(legsFrameset);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
void Humanoid::setBackArmorDirectives(Directives directives) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_backArmorDirectives = std::move(directives);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setBackArmorFrameset(String backFrameset) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_backArmorFrameset = std::move(backFrameset);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
void Humanoid::setHelmetMaskDirectives(Directives helmetMaskDirectives) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_helmetMaskDirectives = std::move(helmetMaskDirectives);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-08-18 10:03:06 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
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) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_primaryHand.backFrame = !backFrameOverride.empty() ? std::move(backFrameOverride) : "rotation";
|
|
|
|
m_primaryHand.frontFrame = !frontFrameOverride.empty() ? std::move(frontFrameOverride) : "rotation";
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setPrimaryHandDrawables(List<Drawable> drawables) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_primaryHand.itemDrawables = std::move(drawables);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setPrimaryHandNonRotatedDrawables(List<Drawable> drawables) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_primaryHand.nonRotatedDrawables = std::move(drawables);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_altHand.backFrame = !backFrameOverride.empty() ? std::move(backFrameOverride) : "rotation";
|
|
|
|
m_altHand.frontFrame = !frontFrameOverride.empty() ? std::move(frontFrameOverride) : "rotation";
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setAltHandDrawables(List<Drawable> drawables) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_altHand.itemDrawables = std::move(drawables);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Humanoid::setAltHandNonRotatedDrawables(List<Drawable> drawables) {
|
2024-02-19 15:55:19 +00:00
|
|
|
m_altHand.nonRotatedDrawables = std::move(drawables);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-06-28 10:22:25 +00:00
|
|
|
List<Drawable> Humanoid::render(bool withItems, bool withRotation) {
|
2023-06-20 04:33:09 +00:00
|
|
|
List<Drawable> drawables;
|
|
|
|
|
|
|
|
int armStateSeq = getArmStateSequence();
|
|
|
|
int bodyStateSeq = getBodyStateSequence();
|
2023-06-24 12:49:47 +00:00
|
|
|
int emoteStateSeq = getEmoteStateSequence();
|
2023-06-20 04:33:09 +00:00
|
|
|
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));
|
2024-04-11 06:27:46 +00:00
|
|
|
drawable.fullbright |= forceFullbright;
|
2024-02-19 15:55:19 +00:00
|
|
|
drawables.append(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
};
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
auto backArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", frameSet, backHand.backFrame);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable backArm = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, backArmFrameOffset);
|
2023-08-02 11:28:37 +00:00
|
|
|
backArm.imagePart().addDirectives(directives, true);
|
2023-06-20 04:33:09 +00:00
|
|
|
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)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_backArmorFrameset, *danceStep->bodyFrame);
|
2023-06-20 04:33:09 +00:00
|
|
|
else if (m_state == Idle)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_backArmorFrameset, m_identity.personality.idle);
|
2023-06-20 04:33:09 +00:00
|
|
|
else
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}.{}", m_backArmorFrameset, frameGroup, bodyStateSeq);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, Vec2F());
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBackDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-28 10:22:25 +00:00
|
|
|
if (backHand.holdingItem && !dance.isValid() && withItems) {
|
2023-06-20 04:33:09 +00:00
|
|
|
auto drawItem = [&]() {
|
2023-06-28 10:22:25 +00:00
|
|
|
for (auto& backHandItem : backHand.itemDrawables) {
|
2023-06-20 04:33:09 +00:00
|
|
|
backHandItem.translate(m_frontHandPosition + backArmFrameOffset + m_backArmOffset);
|
|
|
|
backHandItem.rotate(backHand.itemAngle, backArmFrameOffset + m_backArmRotationCenter + m_backArmOffset);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(backHandItem));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
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) {
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_backArmFrameset, *danceStep->backArmFrame);
|
2023-06-20 04:33:09 +00:00
|
|
|
position = danceStep->backArmOffset / TilePixels;
|
|
|
|
} else if (m_state == Idle) {
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_backArmFrameset, m_identity.personality.armIdle);
|
2023-06-20 04:33:09 +00:00
|
|
|
position = m_identity.personality.armOffset / TilePixels;
|
|
|
|
} else
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}.{}", m_backArmFrameset, frameBase(m_state), armStateSeq);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBodyDirectives(), true);
|
2023-06-20 04:33:09 +00:00
|
|
|
if (dance.isValid())
|
|
|
|
drawable.rotate(danceStep->backArmRotation);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable), m_bodyFullbright);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
if (!m_backSleeveFrameset.empty()) {
|
|
|
|
String image;
|
|
|
|
Vec2F position;
|
|
|
|
if (dance.isValid() && danceStep->backArmFrame) {
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_backSleeveFrameset, *danceStep->backArmFrame);
|
2023-06-20 04:33:09 +00:00
|
|
|
position = danceStep->backArmOffset / TilePixels;
|
|
|
|
} else if (m_state == Idle) {
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_backSleeveFrameset, m_identity.personality.armIdle);
|
2023-06-20 04:33:09 +00:00
|
|
|
position = m_identity.personality.armOffset / TilePixels;
|
|
|
|
} else
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}.{}", m_backSleeveFrameset, frameBase(m_state), armStateSeq);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getChestDirectives(), true);
|
2023-06-20 04:33:09 +00:00
|
|
|
if (dance.isValid())
|
|
|
|
drawable.rotate(danceStep->backArmRotation);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_headFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBodyDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable), m_bodyFullbright);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_emoteFrameset.empty() && !m_bodyHidden) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}.{}", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getEmoteDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable), m_bodyFullbright);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_hairFrameset.empty() && !m_bodyHidden) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_hairFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getHairDirectives(), true).addDirectives(getHelmetMaskDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable), m_bodyFullbright);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_bodyFrameset.empty() && !m_bodyHidden) {
|
|
|
|
String image;
|
|
|
|
if (dance.isValid() && danceStep->bodyFrame)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_bodyFrameset, *danceStep->bodyFrame);
|
2023-06-20 04:33:09 +00:00
|
|
|
else if (m_state == Idle)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_bodyFrameset, m_identity.personality.idle);
|
2023-06-20 04:33:09 +00:00
|
|
|
else
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}.{}", m_bodyFrameset, frameBase(m_state), bodyStateSeq);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, {});
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBodyDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable), m_bodyFullbright);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_legsArmorFrameset.empty()) {
|
|
|
|
String image;
|
|
|
|
if (dance.isValid() && danceStep->bodyFrame)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_legsArmorFrameset, *danceStep->bodyFrame);
|
2023-06-20 04:33:09 +00:00
|
|
|
else if (m_state == Idle)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_legsArmorFrameset, m_identity.personality.idle);
|
2023-06-20 04:33:09 +00:00
|
|
|
else
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}.{}", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, {});
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getLegsDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_chestArmorFrameset.empty()) {
|
|
|
|
String image;
|
|
|
|
Vec2F position;
|
|
|
|
if (dance.isValid() && danceStep->bodyFrame)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_chestArmorFrameset, *danceStep->bodyFrame);
|
2023-06-20 04:33:09 +00:00
|
|
|
else if (m_state == Run)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:run", m_chestArmorFrameset);
|
2023-06-20 04:33:09 +00:00
|
|
|
else if (m_state == Idle)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_chestArmorFrameset, m_identity.personality.idle);
|
2023-06-20 04:33:09 +00:00
|
|
|
else if (m_state == Duck)
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:duck", m_chestArmorFrameset);
|
2023-06-20 04:33:09 +00:00
|
|
|
else if ((m_state == Swim) || (m_state == SwimIdle))
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:swim", m_chestArmorFrameset);
|
2023-06-20 04:33:09 +00:00
|
|
|
else
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:chest.1", m_chestArmorFrameset);
|
2023-06-20 04:33:09 +00:00
|
|
|
if (m_state != Duck)
|
|
|
|
position[1] += bobYOffset;
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getChestDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_facialHairFrameset.empty() && !m_bodyHidden) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_facialHairFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getFacialHairDirectives(), true).addDirectives(getHelmetMaskDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable), m_bodyFullbright);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_facialMaskFrameset.empty() && !m_bodyHidden) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_facialMaskFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getFacialMaskDirectives(), true).addDirectives(getHelmetMaskDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_headArmorFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_headArmorFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, headPosition);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getHeadDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
auto frontArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", frameSet, frontHand.frontFrame);
|
2023-06-20 04:33:09 +00:00
|
|
|
Drawable frontArm = Drawable::makeImage(image, 1.0f / TilePixels, true, frontArmFrameOffset);
|
2023-08-02 11:28:37 +00:00
|
|
|
frontArm.imagePart().addDirectives(directives, true);
|
2023-06-20 04:33:09 +00:00
|
|
|
frontArm.rotate(frontHand.angle, frontArmFrameOffset + m_frontArmRotationCenter);
|
|
|
|
return frontArm;
|
|
|
|
};
|
|
|
|
|
2023-06-28 10:22:25 +00:00
|
|
|
if (frontHand.holdingItem && !dance.isValid() && withItems) {
|
2023-06-20 04:33:09 +00:00
|
|
|
auto drawItem = [&]() {
|
2023-06-28 10:22:25 +00:00
|
|
|
for (auto& frontHandItem : frontHand.itemDrawables) {
|
2023-06-20 04:33:09 +00:00
|
|
|
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) {
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_frontArmFrameset, *danceStep->frontArmFrame);
|
2023-06-20 04:33:09 +00:00
|
|
|
position = danceStep->frontArmOffset / TilePixels;
|
|
|
|
} else if (m_state == Idle) {
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_frontArmFrameset, m_identity.personality.armIdle);
|
2023-06-20 04:33:09 +00:00
|
|
|
position = m_identity.personality.armOffset / TilePixels;
|
|
|
|
} else
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}.{}", m_frontArmFrameset, frameBase(m_state), armStateSeq);
|
2024-02-19 15:55:19 +00:00
|
|
|
auto drawable = Drawable::makeImage(std::move(image), 1.0f / TilePixels, true, position);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBodyDirectives(), true);
|
2023-06-20 04:33:09 +00:00
|
|
|
if (dance.isValid())
|
|
|
|
drawable.rotate(danceStep->frontArmRotation);
|
|
|
|
addDrawable(drawable, m_bodyFullbright);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_frontSleeveFrameset.empty()) {
|
|
|
|
String image;
|
|
|
|
Vec2F position;
|
|
|
|
if (dance.isValid() && danceStep->frontArmFrame) {
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_frontSleeveFrameset, *danceStep->frontArmFrame);
|
2023-06-20 04:33:09 +00:00
|
|
|
position = danceStep->frontArmOffset / TilePixels;
|
|
|
|
} else if (m_state == Idle) {
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}", m_frontSleeveFrameset, m_identity.personality.armIdle);
|
2023-06-20 04:33:09 +00:00
|
|
|
position = m_identity.personality.armOffset / TilePixels;
|
|
|
|
} else
|
2023-06-27 10:23:44 +00:00
|
|
|
image = strf("{}:{}.{}", m_frontSleeveFrameset, frameBase(m_state), armStateSeq);
|
2023-06-24 12:49:47 +00:00
|
|
|
auto drawable = Drawable::makeImage(image, 1.0f / TilePixels, true, position);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getChestDirectives(), true);
|
2023-06-20 04:33:09 +00:00
|
|
|
if (dance.isValid())
|
|
|
|
drawable.rotate(danceStep->frontArmRotation);
|
|
|
|
addDrawable(drawable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_drawVaporTrail) {
|
2023-06-27 10:23:44 +00:00
|
|
|
auto image = strf("{}:{}",
|
2023-06-20 04:33:09 +00:00
|
|
|
m_vaporTrailFrameset,
|
|
|
|
m_timing.genericSeq(m_animationTimer, m_vaporTrailCycle, m_vaporTrailFrames, true));
|
2023-06-24 12:49:47 +00:00
|
|
|
addDrawable(Drawable::makeImage(AssetPath::split(image), 1.0f / TilePixels, true, {}));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-28 10:29:05 +00:00
|
|
|
if (withItems) {
|
|
|
|
if (m_primaryHand.nonRotatedDrawables.size())
|
|
|
|
drawables.insertAllAt(0, m_primaryHand.nonRotatedDrawables);
|
|
|
|
if (m_altHand.nonRotatedDrawables.size())
|
|
|
|
drawables.insertAllAt(0, m_altHand.nonRotatedDrawables);
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
|
2024-04-11 06:27:46 +00:00
|
|
|
for (auto& drawable : drawables) {
|
|
|
|
drawable.translate(m_globalOffset);
|
|
|
|
if (withRotation && m_rotation != 0)
|
|
|
|
drawable.rotate(m_rotation);
|
|
|
|
drawable.rebase();
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
return drawables;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Drawable> Humanoid::renderPortrait(PortraitMode mode) const {
|
|
|
|
List<Drawable> drawables;
|
|
|
|
int emoteStateSeq = m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState);
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
auto addDrawable = [&](Drawable&& drawable) -> Drawable& {
|
2023-06-20 04:33:09 +00:00
|
|
|
if (mode != PortraitMode::Full && mode != PortraitMode::FullNeutral
|
|
|
|
&& mode != PortraitMode::FullNude && mode != PortraitMode::FullNeutralNude) {
|
|
|
|
// TODO: make this configurable
|
2023-06-24 12:49:47 +00:00
|
|
|
drawable.imagePart().addDirectives(String("addmask=/humanoid/portraitMask.png;0;0"), false);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
drawables.append(std::move(drawable));
|
2023-06-24 12:49:47 +00:00
|
|
|
return drawables.back();
|
2023-06-20 04:33:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
bool dressed = !(mode == PortraitMode::FullNude || mode == PortraitMode::FullNeutralNude);
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives helmetMaskDirective = dressed ? getHelmetMaskDirectives() : Directives();
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
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()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", m_backArmFrameset, personality.armIdle);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBodyDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
if (dressed && !m_backSleeveFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", m_backSleeveFrameset, personality.armIdle);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getChestDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
if (mode != PortraitMode::Bust) {
|
|
|
|
if (dressed && !m_backArmorFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", m_backArmorFrameset, personality.idle);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {});
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBackDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_headFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_headFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBodyDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_emoteFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}.{}", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getEmoteDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_hairFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_hairFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getHairDirectives(), true).addDirectives(helmetMaskDirective, true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_bodyFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", m_bodyFrameset, personality.idle);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {});
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBodyDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mode != PortraitMode::Head) {
|
|
|
|
if (dressed && !m_legsArmorFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", m_legsArmorFrameset, personality.idle);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {});
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getLegsDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dressed && !m_chestArmorFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", m_chestArmorFrameset, personality.idle);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, {});
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getChestDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_facialHairFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_facialHairFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getFacialHairDirectives(), true).addDirectives(helmetMaskDirective, true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_facialMaskFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_facialMaskFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getFacialMaskDirectives(), true).addDirectives(helmetMaskDirective, true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dressed && !m_headArmorFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:normal", m_headArmorFrameset);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.headOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getHeadDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mode != PortraitMode::Head) {
|
|
|
|
if (!m_frontArmFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", m_frontArmFrameset, personality.armIdle);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getBodyDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dressed && !m_frontSleeveFrameset.empty()) {
|
2023-06-27 10:23:44 +00:00
|
|
|
String image = strf("{}:{}", m_frontSleeveFrameset, personality.armIdle);
|
2024-02-19 15:55:19 +00:00
|
|
|
Drawable drawable = Drawable::makeImage(std::move(image), 1.0f, true, personality.armOffset);
|
2023-06-26 17:38:57 +00:00
|
|
|
drawable.imagePart().addDirectives(getChestDirectives(), true);
|
2024-02-19 15:55:19 +00:00
|
|
|
addDrawable(std::move(drawable));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return drawables;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Drawable> Humanoid::renderSkull() const {
|
|
|
|
return {Drawable::makeImage(
|
|
|
|
Root::singleton().speciesDatabase()->species(m_identity.species)->skull(), 1.0f, true, Vec2F())};
|
|
|
|
}
|
|
|
|
|
2024-02-28 17:11:55 +00:00
|
|
|
Humanoid Humanoid::makeDummy(Gender) {
|
2023-06-20 04:33:09 +00:00
|
|
|
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()));
|
|
|
|
|
2023-06-27 15:34:37 +00:00
|
|
|
return humanoid;
|
|
|
|
}
|
|
|
|
|
2023-08-18 08:06:00 +00:00
|
|
|
List<Drawable> Humanoid::renderDummy(Gender gender, Maybe<HeadArmor const*> head, Maybe<ChestArmor const*> chest, Maybe<LegsArmor const*> legs, Maybe<BackArmor const*> back) {
|
2023-06-20 04:33:09 +00:00
|
|
|
if (head) {
|
2023-08-18 08:06:00 +00:00
|
|
|
if (auto headPtr = *head) {
|
|
|
|
setHeadArmorFrameset(headPtr->frameset(gender));
|
|
|
|
setHeadArmorDirectives(headPtr->directives());
|
|
|
|
setHelmetMaskDirectives(headPtr->maskDirectives());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
setHeadArmorFrameset("");
|
|
|
|
setHeadArmorDirectives("");
|
|
|
|
setHelmetMaskDirectives("");
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (chest) {
|
2023-08-18 08:06:00 +00:00
|
|
|
if (auto chestPtr = *chest) {
|
|
|
|
setBackSleeveFrameset(chestPtr->backSleeveFrameset(gender));
|
|
|
|
setFrontSleeveFrameset(chestPtr->frontSleeveFrameset(gender));
|
|
|
|
setChestArmorFrameset(chestPtr->bodyFrameset(gender));
|
|
|
|
setChestArmorDirectives(chestPtr->directives());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
setBackSleeveFrameset("");
|
|
|
|
setFrontSleeveFrameset("");
|
|
|
|
setChestArmorFrameset("");
|
|
|
|
setChestArmorDirectives("");
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (legs) {
|
2023-08-18 08:06:00 +00:00
|
|
|
if (auto legsPtr = *legs) {
|
|
|
|
setLegsArmorFrameset(legsPtr->frameset(gender));
|
|
|
|
setLegsArmorDirectives(legsPtr->directives());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
setLegsArmorFrameset("");
|
|
|
|
setLegsArmorDirectives("");
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (back) {
|
2023-08-18 08:06:00 +00:00
|
|
|
if (auto backPtr = *back) {
|
|
|
|
setBackArmorFrameset(backPtr->frameset(gender));
|
|
|
|
setBackArmorDirectives(backPtr->directives());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
setBackArmorFrameset("");
|
|
|
|
setBackArmorDirectives("");
|
|
|
|
}
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-27 15:34:37 +00:00
|
|
|
auto drawables = render();
|
2023-06-20 04:33:09 +00:00
|
|
|
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:
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StarException(strf("No such state '{}'", StateNames.getRight(state)));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2023-06-27 10:23:44 +00:00
|
|
|
throw StarException(strf("No such emote state '{}'", HumanoidEmoteNames.getRight(state)));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String Humanoid::getHeadFromIdentity() const {
|
2023-06-27 10:23:44 +00:00
|
|
|
return strf("/humanoid/{}/{}head.png",
|
2023-06-20 04:33:09 +00:00
|
|
|
m_identity.imagePath ? *m_identity.imagePath : m_identity.species,
|
|
|
|
GenderNames.getRight(m_identity.gender));
|
|
|
|
}
|
|
|
|
|
|
|
|
String Humanoid::getBodyFromIdentity() const {
|
2023-06-27 10:23:44 +00:00
|
|
|
return strf("/humanoid/{}/{}body.png",
|
2023-06-20 04:33:09 +00:00
|
|
|
m_identity.imagePath ? *m_identity.imagePath : m_identity.species,
|
|
|
|
GenderNames.getRight(m_identity.gender));
|
|
|
|
}
|
|
|
|
|
|
|
|
String Humanoid::getFacialEmotesFromIdentity() const {
|
2023-06-27 10:23:44 +00:00
|
|
|
return strf("/humanoid/{}/emote.png", m_identity.imagePath ? *m_identity.imagePath : m_identity.species);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String Humanoid::getHairFromIdentity() const {
|
|
|
|
if (m_identity.hairType.empty())
|
|
|
|
return "";
|
2023-06-27 10:23:44 +00:00
|
|
|
return strf("/humanoid/{}/{}/{}.png",
|
2023-06-20 04:33:09 +00:00
|
|
|
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 "";
|
2023-06-27 10:23:44 +00:00
|
|
|
return strf("/humanoid/{}/{}/{}.png",
|
2023-06-20 04:33:09 +00:00
|
|
|
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 "";
|
2023-06-27 10:23:44 +00:00
|
|
|
return strf("/humanoid/{}/{}/{}.png",
|
2023-06-20 04:33:09 +00:00
|
|
|
m_identity.imagePath ? *m_identity.imagePath : m_identity.species,
|
|
|
|
m_identity.facialMaskGroup,
|
|
|
|
m_identity.facialMaskType);
|
|
|
|
}
|
|
|
|
|
|
|
|
String Humanoid::getBackArmFromIdentity() const {
|
2023-06-27 10:23:44 +00:00
|
|
|
return strf("/humanoid/{}/backarm.png", m_identity.imagePath ? *m_identity.imagePath : m_identity.species);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String Humanoid::getFrontArmFromIdentity() const {
|
2023-06-27 10:23:44 +00:00
|
|
|
return strf("/humanoid/{}/frontarm.png", m_identity.imagePath ? *m_identity.imagePath : m_identity.species);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
String Humanoid::getVaporTrailFrameset() const {
|
|
|
|
return "/humanoid/any/flames.png";
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getBodyDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_identity.bodyDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getHairDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_identity.hairDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getEmoteDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_identity.emoteDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getFacialHairDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_identity.facialHairDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getFacialMaskDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_identity.facialMaskDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getHelmetMaskDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_helmetMaskDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getHeadDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_headArmorDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getChestDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_chestArmorDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getLegsDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_legsArmorDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
Directives Humanoid::getBackDirectives() const {
|
2023-06-20 04:33:09 +00:00
|
|
|
return m_backArmorDirectives;
|
|
|
|
}
|
|
|
|
|
2023-06-24 12:49:47 +00:00
|
|
|
int Humanoid::getEmoteStateSequence() const {
|
|
|
|
return m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState);
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
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", {});
|
2023-06-28 10:22:25 +00:00
|
|
|
for (auto& particle : particles.toArray()) {
|
2023-06-20 04:33:09 +00:00
|
|
|
auto particleSpec = particle.get("particle", {});
|
|
|
|
res.push_back(particleDatabase->particle(particleSpec));
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
Json const& Humanoid::defaultMovementParameters() const {
|
|
|
|
return m_defaultMovementParameters;
|
|
|
|
}
|
|
|
|
|
2024-04-14 00:32:11 +00:00
|
|
|
pair<Vec2F, Directives> Humanoid::extractScaleFromDirectives(Directives const& directives) {
|
|
|
|
if (!directives)
|
|
|
|
return make_pair(Vec2F::filled(1.f), Directives());
|
|
|
|
|
|
|
|
List<StringView> operations;
|
|
|
|
size_t totalLength = 0;
|
|
|
|
Maybe<Vec2F> scale;
|
|
|
|
|
|
|
|
for (auto& entry : directives.shared->entries) {
|
|
|
|
auto string = entry.string(*directives.shared);
|
|
|
|
const ScaleImageOperation* op = nullptr;
|
|
|
|
if (string.beginsWith("scalenearest") && string.utf8().find("skip") == NPos)
|
|
|
|
op = entry.loadOperation(*directives.shared).ptr<ScaleImageOperation>();
|
|
|
|
|
|
|
|
if (op)
|
|
|
|
scale = scale.value(Vec2F::filled(1.f)).piecewiseMultiply(op->scale);
|
|
|
|
else
|
|
|
|
totalLength += operations.emplace_back(string).utf8Size();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!scale)
|
|
|
|
return make_pair(Vec2F::filled(1.f), directives);
|
|
|
|
|
|
|
|
String mergedDirectives;
|
|
|
|
mergedDirectives.reserve(totalLength);
|
|
|
|
for (auto& directive : operations)
|
|
|
|
mergedDirectives.append(directive.utf8Ptr(), directive.utf8Size());
|
|
|
|
|
|
|
|
return make_pair(*scale, Directives(mergedDirectives));
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|