Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ class W3DModelDrawModuleData : public ModuleData
mutable Vector3 m_attachToDrawableBoneOffset;
#endif
Int m_defaultState;
Int m_lastRealConditionStateIndex; ///< index of last DefaultConditionState/ConditionState, used by AutoConditionState
Int m_projectileBoneFeedbackEnabledSlots; ///< Hide and show the launch bone geometries according to clip status adjustments.
Real m_initialRecoil;
Real m_maxRecoil;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#define NO_DEBUG_CRC

#include <windows.h>
#include <map>

#include "Common/crc.h"
#include "Common/CRCDebug.h"
Expand Down Expand Up @@ -1056,7 +1057,8 @@ W3DModelDrawModuleData::W3DModelDrawModuleData() :
m_attachToDrawableBoneOffsetValid(false),
#endif
m_minLODRequired(STATIC_GAME_LOD_LOW),
m_defaultState(-1)
m_defaultState(-1),
m_lastRealConditionStateIndex(-1)
{
const Real MAX_SHIFT = 3.0f;
const Real INITIAL_RECOIL_RATE = 2.0f;
Expand Down Expand Up @@ -1216,7 +1218,8 @@ enum ParseCondStateType CPP_11(: Int)
PARSE_NORMAL,
PARSE_DEFAULT,
PARSE_TRANSITION,
PARSE_ALIAS
PARSE_ALIAS,
PARSE_AUTO
};

//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -1246,6 +1249,7 @@ void W3DModelDrawModuleData::buildFieldParse(MultiIniFieldParse& p)
{ "DefaultConditionState", W3DModelDrawModuleData::parseConditionState, (void*)PARSE_DEFAULT, 0 },
{ "ConditionState", W3DModelDrawModuleData::parseConditionState, (void*)PARSE_NORMAL, 0 },
{ "AliasConditionState", W3DModelDrawModuleData::parseConditionState, (void*)PARSE_ALIAS, 0 },
{ "AutoConditionState", W3DModelDrawModuleData::parseConditionState, (void*)PARSE_AUTO, 0 },
{ "TransitionState", W3DModelDrawModuleData::parseConditionState, (void*)PARSE_TRANSITION, 0 },
{ "TrackMarks", parseAsciiStringLC, nullptr, offsetof(W3DModelDrawModuleData, m_trackFile) },
{ "ExtraPublicBone", INI::parseAsciiStringVectorAppend, nullptr, offsetof(W3DModelDrawModuleData, m_extraPublicBones) },
Expand Down Expand Up @@ -1475,6 +1479,100 @@ static Bool doesStateExist(const ModelConditionVector& v, const ModelConditionFl
return false;
}

//-------------------------------------------------------------------------------------------------
// Like doesStateExist, but returns the index of the first state whose condition set matches 'f'
// exactly (or -1 if none). Used by AutoConditionState to locate its base state.
static Int findStateIndexMatching(const ModelConditionVector& v, const ModelConditionFlags& f)
{
for (Int idx = 0; idx < (Int)v.size(); ++idx)
{
const ModelConditionInfo& info = v[idx];
for (Int i = info.getConditionsYesCount()-1; i >= 0; --i)
{
if (f == info.getNthConditionsYes(i))
return idx;
}
}
return -1;
}

//-------------------------------------------------------------------------------------------------
// Overlay onto 'dst' the authored fields that 'prev' set differently from the default state 'def'.
// Used by AutoConditionState: 'dst' starts as the matched base state, 'prev' is the previously
// defined ConditionState whose explicit overrides we want applied on top of the base.
// Note: a normal ConditionState is built as (default-copy then block-fields), so a scalar field
// counts as "explicitly set" iff it differs from the default state's value. Animations are
// clear-on-first-write (signaled by the GOT_*_ANIMS read flags). The hide/show, particle-bone and
// FX vectors are append-based, so 'prev' = default's entries followed by the block's appended ones.
static void mergeConditionStateOverrides(ModelConditionInfo& dst, const ModelConditionInfo& prev, const ModelConditionInfo& def)
{
if (prev.m_modelName != def.m_modelName)
dst.m_modelName = prev.m_modelName;

if (prev.m_mode != def.m_mode)
dst.m_mode = prev.m_mode;
if (prev.m_animMinSpeedFactor != def.m_animMinSpeedFactor)
dst.m_animMinSpeedFactor = prev.m_animMinSpeedFactor;
if (prev.m_animMaxSpeedFactor != def.m_animMaxSpeedFactor)
dst.m_animMaxSpeedFactor = prev.m_animMaxSpeedFactor;
if (prev.m_animBlendTime != def.m_animBlendTime)
dst.m_animBlendTime = prev.m_animBlendTime;
if (prev.m_flags != def.m_flags)
dst.m_flags = prev.m_flags;
if (prev.m_transitionKey != def.m_transitionKey)
dst.m_transitionKey = prev.m_transitionKey;
if (prev.m_allowToFinishKey != def.m_allowToFinishKey)
dst.m_allowToFinishKey = prev.m_allowToFinishKey;

// turrets: only the 4 INI-read members; runtime bone members are recomputed in validateStuff
for (Int t = 0; t < MAX_TURRETS; ++t)
{
if (prev.m_turrets[t].m_turretAngleNameKey != def.m_turrets[t].m_turretAngleNameKey)
dst.m_turrets[t].m_turretAngleNameKey = prev.m_turrets[t].m_turretAngleNameKey;
if (prev.m_turrets[t].m_turretPitchNameKey != def.m_turrets[t].m_turretPitchNameKey)
dst.m_turrets[t].m_turretPitchNameKey = prev.m_turrets[t].m_turretPitchNameKey;
if (prev.m_turrets[t].m_turretArtAngle != def.m_turrets[t].m_turretArtAngle)
dst.m_turrets[t].m_turretArtAngle = prev.m_turrets[t].m_turretArtAngle;
if (prev.m_turrets[t].m_turretArtPitch != def.m_turrets[t].m_turretArtPitch)
dst.m_turrets[t].m_turretArtPitch = prev.m_turrets[t].m_turretArtPitch;
}

for (Int w = 0; w < WEAPONSLOT_COUNT; ++w)
{
if (prev.m_weaponFireFXBoneName[w] != def.m_weaponFireFXBoneName[w])
dst.m_weaponFireFXBoneName[w] = prev.m_weaponFireFXBoneName[w];
if (prev.m_weaponRecoilBoneName[w] != def.m_weaponRecoilBoneName[w])
dst.m_weaponRecoilBoneName[w] = prev.m_weaponRecoilBoneName[w];
if (prev.m_weaponMuzzleFlashName[w] != def.m_weaponMuzzleFlashName[w])
dst.m_weaponMuzzleFlashName[w] = prev.m_weaponMuzzleFlashName[w];
if (prev.m_weaponProjectileLaunchBoneName[w] != def.m_weaponProjectileLaunchBoneName[w])
dst.m_weaponProjectileLaunchBoneName[w] = prev.m_weaponProjectileLaunchBoneName[w];
if (prev.m_weaponProjectileHideShowName[w] != def.m_weaponProjectileHideShowName[w])
dst.m_weaponProjectileHideShowName[w] = prev.m_weaponProjectileHideShowName[w];
}

// animations: 'prev' replaced (not appended) its anims iff a GOT_*_ANIMS flag is set
const Int animBits = (1<<GOT_NONIDLE_ANIMS) | (1<<GOT_IDLE_ANIMS);
if (prev.m_iniReadFlags & animBits)
{
dst.m_animations = prev.m_animations;
dst.m_iniReadFlags &= ~animBits;
dst.m_iniReadFlags |= (prev.m_iniReadFlags & animBits);
}

// append-based vectors: take the entries 'prev' appended beyond the default's
for (size_t i = def.m_hideShowVec.size(); i < prev.m_hideShowVec.size(); ++i)
dst.m_hideShowVec.push_back(prev.m_hideShowVec[i]);
for (size_t i = def.m_particleSysBones.size(); i < prev.m_particleSysBones.size(); ++i)
dst.m_particleSysBones.push_back(prev.m_particleSysBones[i]);
for (size_t i = def.m_fxEvents.size(); i < prev.m_fxEvents.size(); ++i)
dst.m_fxEvents.push_back(prev.m_fxEvents[i]);

// public bones: union (addPublicBone dedups)
for (size_t i = 0; i < prev.m_publicBones.size(); ++i)
dst.addPublicBone(prev.m_publicBones[i]);
}

//-------------------------------------------------------------------------------------------------
void W3DModelDrawModuleData::parseConditionState(INI* ini, void *instance, void * /*store*/, const void* userData)
{
Expand Down Expand Up @@ -1667,6 +1765,86 @@ void W3DModelDrawModuleData::parseConditionState(INI* ini, void *instance, void
return;
}

case PARSE_AUTO:
{
if (self->m_lastRealConditionStateIndex < 0)
{
DEBUG_CRASH(("*** ASSET ERROR: AutoConditionState must refer to a previous ConditionState!"));
throw INI_INVALID_DATA;
}

// Take a COPY of the previously defined state; m_conditionStates may reallocate on push_back below.
const ModelConditionInfo prevState = self->m_conditionStates.at(self->m_lastRealConditionStateIndex);

ModelConditionFlags autoFlags;
#if defined(RTS_DEBUG)
AsciiString description;
autoFlags.parse(ini, &description);
#else
autoFlags.parse(ini, nullptr);
#endif

if (!autoFlags.any())
{
DEBUG_CRASH(("*** ASSET ERROR: AutoConditionState requires at least one condition flag"));
throw INI_INVALID_DATA;
}

if (autoFlags.anyIntersectionWith(self->m_ignoreConditionStates))
{
DEBUG_CRASH(("You should not specify bits in a state once they are used in IgnoreConditionStates (%s)", TheThingTemplateBeingParsedName.str()));
throw INI_INVALID_DATA;
}

// Base values come from the existing state matching the auto flags (e.g. the DAMAGED state).
// If none matches, fall back to a full copy of the previous state.
Int baseIdx = findStateIndexMatching(self->m_conditionStates, autoFlags);
if (baseIdx >= 0)
{
info = self->m_conditionStates.at(baseIdx);

// Overlay the fields the previous state set differently from the default state.
const ModelConditionInfo defState =
(self->m_defaultState >= 0) ? self->m_conditionStates.at(self->m_defaultState) : ModelConditionInfo();
mergeConditionStateOverrides(info, prevState, defState);
}
else
{
info = prevState;
}

// Combined conditions = each of the previous state's condition sets, OR'd with the auto flags.
info.m_conditionsYesVec.clear();
for (Int i = 0; i < prevState.getConditionsYesCount(); ++i)
{
ModelConditionFlags combined = prevState.getNthConditionsYes(i);
combined.set(autoFlags);

if (doesStateExist(self->m_conditionStates, combined))
{
DEBUG_CRASH(("*** ASSET ERROR: AutoConditionState produced a duplicate condition state (%s)", TheThingTemplateBeingParsedName.str()));
throw INI_INVALID_DATA;
}
info.m_conditionsYesVec.push_back(combined);
}

#if defined(RTS_DEBUG)
info.m_description.clear();
info.m_description.concat(TheThingTemplateBeingParsedName);
info.m_description.concat("\n AUTO: ");
info.m_description.concat(description);
#endif

// Normalize model name (the normal path does this after initFromINI, which we skip).
if (info.m_modelName.isNone())
info.m_modelName.clear();

self->m_conditionStates.push_back(info);

// yes, return, NOT break! (no body to parse for AutoConditionState)
return;
}

case PARSE_NORMAL:
{
if (self->m_defaultState >= 0 && cst != PARSE_ALIAS)
Expand Down Expand Up @@ -1786,6 +1964,10 @@ void W3DModelDrawModuleData::parseConditionState(INI* ini, void *instance, void
}
else
{
// Track the last *defined* condition state so AutoConditionState refers to it (and not to an
// earlier auto-generated state). size() == index of the element about to be pushed.
if (cst == PARSE_NORMAL || cst == PARSE_DEFAULT)
self->m_lastRealConditionStateIndex = (Int)self->m_conditionStates.size();
self->m_conditionStates.push_back(info);
}
}
Expand Down Expand Up @@ -2728,14 +2910,19 @@ void W3DModelDraw::handleClientRecoil()
return;
}

// do recoil, if any
// Multiple weapon slots may reference the same recoil / muzzle-flash bone name (hence the
// same bone index). Applying per-slot directly to the bone makes the last processed slot
// clobber the others, so a shared muzzle flash / recoil only ever shows for the last slot.
// Instead, aggregate across all slots that share a bone: the muzzle flash is visible if ANY
// sharing slot just fired, and the recoil bone uses the LARGEST shift among sharing slots.
std::map<Int, Bool> muzzleFlashVisible; // muzzle-flash bone -> any sharing slot wants flash shown
std::map<Int, Real> recoilShift; // recoil bone -> max shift among sharing slots

const Real TINY_RECOIL = 0.01f;

// Pass 1: advance every recoil state machine and accumulate the per-bone aggregates.
for (int wslot = 0; wslot < WEAPONSLOT_COUNT; ++wslot)
{
if (wslot == 0 && stricmp(m_curState->m_modelName.str(), "avjug_deploy") == 0) {
int i = 0;
i += 1;
}

if (!m_curState->m_hasRecoilBonesOrMuzzleFlashes[wslot])
continue;

Expand All @@ -2749,12 +2936,12 @@ void W3DModelDraw::handleClientRecoil()
{
if (barrels[i].m_muzzleFlashBone != 0)
{
Bool hidden = recoils[i].m_state != WeaponRecoilInfo::RECOIL_START;
//DEBUG_LOG(("adjust muzzleflash %08lx for Draw %08lx state %s to %d at frame %d",subObjToHide,this,m_curState->m_description.str(),hidden?1:0,TheGameLogic->getFrame()));
barrels[i].setMuzzleFlashHidden(m_renderObject, hidden);
// operator[] inserts a default (hidden) entry, so bones whose slots are all idle
// still get hidden in pass 2; OR-accumulate so any firing slot shows the flash.
Bool& visible = muzzleFlashVisible[barrels[i].m_muzzleFlashBone];
visible = visible || (recoils[i].m_state == WeaponRecoilInfo::RECOIL_START);
}

const Real TINY_RECOIL = 0.01f;
if (barrels[i].m_recoilBone != 0)
{
switch (recoils[i].m_state )
Expand Down Expand Up @@ -2788,16 +2975,10 @@ void W3DModelDraw::handleClientRecoil()
break;
}

Matrix3D gunXfrm;
gunXfrm.Make_Identity();
gunXfrm.Translate_X( -recoils[i].m_shift );
//DEBUG_ASSERTLOG(recoils[i].m_shift==0.0f,("adjust bone %d by %f",recoils[i].m_recoilBone,recoils[i].m_shift));

if (m_renderObject)
{
m_renderObject->Capture_Bone( barrels[i].m_recoilBone );
m_renderObject->Control_Bone( barrels[i].m_recoilBone, gunXfrm );
}
// keep the largest shift among slots sharing this bone (operator[] inserts 0.0f).
Real& shift = recoilShift[barrels[i].m_recoilBone];
if (recoils[i].m_shift > shift)
shift = recoils[i].m_shift;
}
else
{
Expand All @@ -2806,6 +2987,35 @@ void W3DModelDraw::handleClientRecoil()
}
}
}

// Pass 2: apply the aggregated state. Muzzle flashes go through the barrel helper (keeps the
// asset-error diagnostic); recoil bones are driven directly from the per-bone max shift.
if (m_renderObject)
{
for (int wslot = 0; wslot < WEAPONSLOT_COUNT; ++wslot)
{
if (!m_curState->m_hasRecoilBonesOrMuzzleFlashes[wslot])
continue;

const ModelConditionInfo::WeaponBarrelInfoVec& barrels = m_curState->m_weaponBarrelInfoVec[wslot];
for (ModelConditionInfo::WeaponBarrelInfoVec::const_iterator it = barrels.begin(); it != barrels.end(); ++it)
{
if (it->m_muzzleFlashBone != 0)
it->setMuzzleFlashHidden(m_renderObject, !muzzleFlashVisible[it->m_muzzleFlashBone]);
}
}

for (std::map<Int, Real>::const_iterator it = recoilShift.begin(); it != recoilShift.end(); ++it)
{
Matrix3D gunXfrm;
gunXfrm.Make_Identity();
gunXfrm.Translate_X( -it->second );
//DEBUG_ASSERTLOG(it->second==0.0f,("adjust bone %d by %f",it->first,it->second));

m_renderObject->Capture_Bone( it->first );
m_renderObject->Control_Bone( it->first, gunXfrm );
}
}
}

//-------------------------------------------------------------------------------------------------
Expand Down
Loading