From 82fa904d16d40d86073f5de1fd870df78370d8a2 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 30 Aug 2026 04:18:08 +0800 Subject: [PATCH 1/3] initial --- docs/New-or-Enhanced-Logics.md | 118 +++++++++++++++++++++++++++++++++ src/Ext/Infantry/Hooks.cpp | 66 ++++++++++++++++++ src/Ext/InfantryType/Body.cpp | 22 ++++++ src/Ext/InfantryType/Body.h | 8 +++ src/Ext/Rules/Body.cpp | 22 +++++- src/Ext/Rules/Body.h | 9 +++ src/Utilities/SequenceRates.h | 60 +++++++++++++++++ 7 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 src/Utilities/SequenceRates.h diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 2147d0c7ef..923921a380 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -758,6 +758,124 @@ DeployedPrimaryFireFLH= ; integer - Forward,Lateral,Height DeployedSecondaryFireFLH= ; integer - Forward,Lateral,Height ``` +### Customizable infantry sequence rates + +In vanilla, the number of game frames each animation frame of the infantry sequence stays (i.e., its playback rate) is hardcoded. Now it can be customized for each sequence in each infantry's sequence section. +- `Rate` defines the number of game logic frames that 1 animation frame stays; 0 means still, for example, the `Ready` and `Guard` sequences in vanilla are still. +- `Normalized` defines whether the playback rate is affected by game speed, for example, the `Idle1`, `Idle2`, `WetIdle1`, `WetIdle2`, `Hover`, and `Cheer` sequences in vanilla are affected by game speed. + +```{note} +The effective rate of a normalized sequence is `rate` adjusted by the game speed: + +- `rate < 5`: from the table below (`rate` row, `GameSpeed` column). +- `rate >= 5`: `rate * 8 / (GameSpeed + 1)`. + +| Rate \ GameSpeed | 0 | 1 | 2 | 3 | 4 | 5 | 6 | +| ---------------- | - | - | - | - | - | - | - | +| 1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | +| 2 | 3 | 3 | 3 | 2 | 2 | 2 | 1 | +| 3 | 5 | 4 | 4 | 3 | 3 | 2 | 2 | +| 4 | 7 | 6 | 5 | 4 | 4 | 4 | 3 | + +`GameSpeed` goes from `0` (fastest) to `6` (slowest). +``` + +In `rulesmd.ini`: +```ini +[AudioVisual] +Sequence.Ready.DefaultRate=0 ; integer +Sequence.Ready.DefaultNormalized=no ; boolean +Sequence.Guard.DefaultRate=0 ; integer +Sequence.Guard.DefaultNormalized=no ; boolean +Sequence.Prone.DefaultRate=6 ; integer +Sequence.Prone.DefaultNormalized=no ; boolean +Sequence.Walk.DefaultRate=3 ; integer +Sequence.Walk.DefaultNormalized=no ; boolean +Sequence.FireUp.DefaultRate=1 ; integer +Sequence.FireUp.DefaultNormalized=no ; boolean +Sequence.Down.DefaultRate=1 ; integer +Sequence.Down.DefaultNormalized=no ; boolean +Sequence.Crawl.DefaultRate=1 ; integer +Sequence.Crawl.DefaultNormalized=no ; boolean +Sequence.Up.DefaultRate=1 ; integer +Sequence.Up.DefaultNormalized=no ; boolean +Sequence.FireProne.DefaultRate=1 ; integer +Sequence.FireProne.DefaultNormalized=no ; boolean +Sequence.Idle1.DefaultRate=3 ; integer +Sequence.Idle1.DefaultNormalized=yes ; boolean +Sequence.Idle2.DefaultRate=3 ; integer +Sequence.Idle2.DefaultNormalized=yes ; boolean +Sequence.Die1.DefaultRate=1 ; integer +Sequence.Die1.DefaultNormalized=no ; boolean +Sequence.Die2.DefaultRate=1 ; integer +Sequence.Die2.DefaultNormalized=no ; boolean +Sequence.Die3.DefaultRate=1 ; integer +Sequence.Die3.DefaultNormalized=no ; boolean +Sequence.Die4.DefaultRate=1 ; integer +Sequence.Die4.DefaultNormalized=no ; boolean +Sequence.Die5.DefaultRate=1 ; integer +Sequence.Die5.DefaultNormalized=no ; boolean +Sequence.Tread.DefaultRate=3 ; integer +Sequence.Tread.DefaultNormalized=no ; boolean +Sequence.Swim.DefaultRate=1 ; integer +Sequence.Swim.DefaultNormalized=no ; boolean +Sequence.WetIdle1.DefaultRate=3 ; integer +Sequence.WetIdle1.DefaultNormalized=yes ; boolean +Sequence.WetIdle2.DefaultRate=3 ; integer +Sequence.WetIdle2.DefaultNormalized=yes ; boolean +Sequence.WetDie1.DefaultRate=1 ; integer +Sequence.WetDie1.DefaultNormalized=no ; boolean +Sequence.WetDie2.DefaultRate=1 ; integer +Sequence.WetDie2.DefaultNormalized=no ; boolean +Sequence.WetAttack.DefaultRate=1 ; integer +Sequence.WetAttack.DefaultNormalized=no ; boolean +Sequence.Hover.DefaultRate=2 ; integer +Sequence.Hover.DefaultNormalized=yes ; boolean +Sequence.Fly.DefaultRate=1 ; integer +Sequence.Fly.DefaultNormalized=no ; boolean +Sequence.Tumble.DefaultRate=1 ; integer +Sequence.Tumble.DefaultNormalized=no ; boolean +Sequence.FireFly.DefaultRate=1 ; integer +Sequence.FireFly.DefaultNormalized=no ; boolean +Sequence.Deploy.DefaultRate=1 ; integer +Sequence.Deploy.DefaultNormalized=no ; boolean +Sequence.Deployed.DefaultRate=1 ; integer +Sequence.Deployed.DefaultNormalized=no ; boolean +Sequence.DeployedFire.DefaultRate=1 ; integer +Sequence.DeployedFire.DefaultNormalized=no ; boolean +Sequence.DeployedIdle.DefaultRate=1 ; integer +Sequence.DeployedIdle.DefaultNormalized=no ; boolean +Sequence.Undeploy.DefaultRate=1 ; integer +Sequence.Undeploy.DefaultNormalized=no ; boolean +Sequence.Cheer.DefaultRate=3 ; integer +Sequence.Cheer.DefaultNormalized=yes ; boolean +Sequence.Paradrop.DefaultRate=1 ; integer +Sequence.Paradrop.DefaultNormalized=no ; boolean +Sequence.AirDeathStart.DefaultRate=3 ; integer +Sequence.AirDeathStart.DefaultNormalized=no ; boolean +Sequence.AirDeathFalling.DefaultRate=1 ; integer +Sequence.AirDeathFalling.DefaultNormalized=no ; boolean +Sequence.AirDeathFinish.DefaultRate=3 ; integer +Sequence.AirDeathFinish.DefaultNormalized=no ; boolean +Sequence.Panic.DefaultRate=4 ; integer +Sequence.Panic.DefaultNormalized=no ; boolean +Sequence.Shovel.DefaultRate=6 ; integer +Sequence.Shovel.DefaultNormalized=no ; boolean +Sequence.Carry.DefaultRate=3 ; integer +Sequence.Carry.DefaultNormalized=no ; boolean +Sequence.SecondaryFire.DefaultRate=1 ; integer +Sequence.SecondaryFire.DefaultNormalized=no ; boolean +Sequence.SecondaryProne.DefaultRate=1 ; integer +Sequence.SecondaryProne.DefaultNormalized=no ; boolean +``` + +In `artmd.ini`: +```ini +[SEQUENCE] ; Sequence +Rate= ; integer, defaults to [AudioVisual] -> Sequence..DefaultRate +Normalized= ; boolean, defaults to [AudioVisual] -> Sequence..DefaultNormalized +``` + ### Customizable `SlavesFreeSound` - `SlavesFreeSound` can now be set individually for each enslavable infantry type. diff --git a/src/Ext/Infantry/Hooks.cpp b/src/Ext/Infantry/Hooks.cpp index 481c7f42c7..0944c90063 100644 --- a/src/Ext/Infantry/Hooks.cpp +++ b/src/Ext/Infantry/Hooks.cpp @@ -1,7 +1,11 @@ #include #include +#include +#include #include +#include +#include DEFINE_HOOK(0x51B2BD, InfantryClass_UpdateTarget_IsControlledByHuman, 0x6) { @@ -209,3 +213,65 @@ DEFINE_HOOK(0x51A002, InfantryClass_UpdatePosition_InfiltrateBuilding, 0x6) return 0; } + +#pragma region CustomInfantrySequenceRates + +// The vanilla hardcoded per-sequence rates used as the final fallback live in SequenceRates::Entries (see Utilities/SequenceRates.h). + +namespace SequenceRateHooks +{ + int GetCustomRate(InfantryClass* pThis, int sequence) + { + if (sequence < 0 || sequence >= static_cast(SequenceRates::Entries.size())) + return -1; + + const int typeRate = InfantryTypeExt::Fetch(pThis->Type)->CustomSequenceRates[sequence]; + const int customRate = typeRate >= 0 ? typeRate : RulesExt::Global()->CustomSequenceRates[sequence]; + + return customRate; + } + + bool IsNormalized(InfantryClass* pThis, int sequence) + { + if (sequence < 0 || sequence >= static_cast(SequenceRates::Entries.size())) + return false; + + const int typeFlag = InfantryTypeExt::Fetch(pThis->Type)->CustomSequenceNormalized[sequence]; + if (typeFlag >= 0) + return typeFlag != 0; + + const int globalFlag = RulesExt::Global()->CustomSequenceNormalized[sequence]; + if (globalFlag >= 0) + return globalFlag != 0; + + return SequenceRates::Entries[sequence].Normalized; + } + + int GetFinalRate(InfantryClass* pThis, int sequence) + { + const int customRate = GetCustomRate(pThis, sequence); + const int rate = customRate >= 0 ? customRate : SequenceRates::Entries[sequence].DefaultRate; + + return IsNormalized(pThis, sequence) + ? GameOptionsClass::Instance.GetAnimSpeed(rate) + : rate; + } +} + +DEFINE_HOOK(0x51DA44, InfantryClass_DoAction_SequenceRate_Store, 0x6) +{ + GET(InfantryClass*, pThis, ESI); + GET(int, sequence, EDI); + + if (sequence >= 0 && sequence < static_cast(SequenceRates::Entries.size())) + { + const int finalRate = SequenceRateHooks::GetFinalRate(pThis, sequence); + + pThis->Animation.Timer.TimeLeft = finalRate; + pThis->Animation.Rate = finalRate; + } + + return 0x51DA4A; +} + +#pragma endregion diff --git a/src/Ext/InfantryType/Body.cpp b/src/Ext/InfantryType/Body.cpp index 48c94999a1..83a25474cf 100644 --- a/src/Ext/InfantryType/Body.cpp +++ b/src/Ext/InfantryType/Body.cpp @@ -1,5 +1,7 @@ #include "Body.h" +#include + InfantryTypeExt::ExtContainer InfantryTypeExt::ExtMap; // ============================= @@ -33,6 +35,24 @@ void InfantryTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->ProneSecondaryFireFLH.Read(exArtINI, pArtSection, "ProneSecondaryFireFLH"); this->DeployedPrimaryFireFLH.Read(exArtINI, pArtSection, "DeployedPrimaryFireFLH"); this->DeployedSecondaryFireFLH.Read(exArtINI, pArtSection, "DeployedSecondaryFireFLH"); + + // Per-sequence animation rates + char sequenceSection[0x20]; + const char* pRateSection = pArtSection; + if (pArtINI->ReadString(pArtSection, "Sequence", "", sequenceSection, sizeof(sequenceSection)) > 0) + pRateSection = sequenceSection; + + for (size_t i = 0; i < SequenceRates::Entries.size(); ++i) + { + char key[64]; + std::snprintf(key, sizeof(key), "%sRate", SequenceRates::Entries[i].Name); + exArtINI.ReadInteger(pRateSection, key, &this->CustomSequenceRates[i]); + + bool normalized; + std::snprintf(key, sizeof(key), "%sNormalized", SequenceRates::Entries[i].Name); + if (exArtINI.ReadBool(pRateSection, key, &normalized)) + this->CustomSequenceNormalized[i] = normalized ? 1 : 0; + } } template @@ -55,6 +75,8 @@ void InfantryTypeExt::Serialize(T& Stm) .Process(this->DeployedWeaponBurstFLHs) .Process(this->EliteDeployedWeaponBurstFLHs) .Process(this->InfantryAutoDeploy) + .Process(this->CustomSequenceRates) + .Process(this->CustomSequenceNormalized) ; } diff --git a/src/Ext/InfantryType/Body.h b/src/Ext/InfantryType/Body.h index 205d35bf67..ed9a85a440 100644 --- a/src/Ext/InfantryType/Body.h +++ b/src/Ext/InfantryType/Body.h @@ -28,6 +28,12 @@ class InfantryTypeExt final : public TechnoTypeExt std::vector> EliteDeployedWeaponBurstFLHs; Nullable InfantryAutoDeploy; + // Per-sequence animation rates read from the infantry's art section + std::vector CustomSequenceRates; + + // Per-sequence game-speed normalization flags read from the infantry's art section + std::vector CustomSequenceNormalized; + explicit InfantryTypeExt(InfantryTypeClass* const OwnerObject) : TechnoTypeExt(OwnerObject) , Slaved_OwnerWhenMasterKilled { SlaveChangeOwnerType::Killer } , SlavesFreeSound {} @@ -41,6 +47,8 @@ class InfantryTypeExt final : public TechnoTypeExt , DeployedPrimaryFireFLH {} , DeployedSecondaryFireFLH {} , InfantryAutoDeploy {} + , CustomSequenceRates(42, -1) + , CustomSequenceNormalized(42, -1) { } InfantryTypeClass* OwnerObject() const diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 70a93234f9..c715f569ed 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -1,5 +1,9 @@ #include "Body.h" +#include + +#include + #include #include #include @@ -507,6 +511,20 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->AIScriptsLists.emplace_back(std::move(objectsList)); } + + // Global default per-sequence animation rates for infantry. + for (size_t i = 0; i < SequenceRates::Entries.size(); ++i) + { + char key[64]; + std::snprintf(key, sizeof(key), "Sequence.%s.DefaultRate", SequenceRates::Entries[i].Name); + exINI.ReadInteger(GameStrings::AudioVisual, key, &this->CustomSequenceRates[i]); + + bool normalized; + std::snprintf(key, sizeof(key), "Sequence.%s.DefaultNormalized", SequenceRates::Entries[i].Name); + if (exINI.ReadBool(GameStrings::AudioVisual, key, &normalized)) + this->CustomSequenceNormalized[i] = normalized ? 1 : 0; + } + } // this should load everything that TypeData is not dependant on @@ -839,7 +857,9 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->AllowBeaconHotKeyInSinglePlayer) .Process(this->StartFacing) .Process(this->StartFacing_Random) - ; + .Process(this->CustomSequenceRates) + .Process(this->CustomSequenceNormalized) + ; } void RulesExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index df6cf336af..5f856e8f7e 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -404,6 +404,12 @@ class RulesExt Valueable StartFacing; Valueable StartFacing_Random; + // Global default per-sequence animation rates for infantry + std::vector CustomSequenceRates; + + // Global default per-sequence game-speed normalization flags for infantry + std::vector CustomSequenceNormalized; + ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } , HarvesterDumpAmount { 0.0f } @@ -747,6 +753,9 @@ class RulesExt , StartFacing { 0 } , StartFacing_Random { false } + + , CustomSequenceRates(42, -1) + , CustomSequenceNormalized(42, -1) { } virtual ~ExtData() = default; diff --git a/src/Utilities/SequenceRates.h b/src/Utilities/SequenceRates.h new file mode 100644 index 0000000000..bf93844288 --- /dev/null +++ b/src/Utilities/SequenceRates.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +namespace SequenceRates +{ + struct Entry + { + const char* Name; + int DefaultRate; + bool Normalized; + }; + + inline constexpr std::array Entries = {{ + // Name DefaultRate Normalized + // --------------------------------------------- + { "Ready", 0, false }, + { "Guard", 0, false }, + { "Prone", 6, false }, + { "Walk", 3, false }, + { "FireUp", 1, false }, + { "Down", 1, false }, + { "Crawl", 1, false }, + { "Up", 1, false }, + { "FireProne", 1, false }, + { "Idle1", 3, true }, // <-- true in vanilla + { "Idle2", 3, true }, // <-- true in vanilla + { "Die1", 1, false }, + { "Die2", 1, false }, + { "Die3", 1, false }, + { "Die4", 1, false }, + { "Die5", 1, false }, + { "Tread", 3, false }, + { "Swim", 1, false }, + { "WetIdle1", 3, true }, // <-- true in vanilla + { "WetIdle2", 3, true }, // <-- true in vanilla + { "WetDie1", 1, false }, + { "WetDie2", 1, false }, + { "WetAttack", 1, false }, + { "Hover", 2, true }, // <-- true in vanilla + { "Fly", 1, false }, + { "Tumble", 1, false }, + { "FireFly", 1, false }, + { "Deploy", 1, false }, + { "Deployed", 1, false }, + { "DeployedFire", 1, false }, + { "DeployedIdle", 1, false }, + { "Undeploy", 1, false }, + { "Cheer", 3, true }, // <-- true in vanilla + { "Paradrop", 1, false }, + { "AirDeathStart", 3, false }, + { "AirDeathFalling", 1, false }, + { "AirDeathFinish", 3, false }, + { "Panic", 4, false }, + { "Shovel", 6, false }, + { "Carry", 3, false }, + { "SecondaryFire", 1, false }, + { "SecondaryProne", 1, false }, + }}; +} From 9413ae12fb5e49b9a137606a7b833c02bd238cab Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 30 Aug 2026 04:27:20 +0800 Subject: [PATCH 2/3] update docs & tr. --- CREDITS.md | 1 + docs/Whats-New.md | 1 + docs/locale/zh_CN/LC_MESSAGES/CREDITS.po | 3 ++ .../LC_MESSAGES/New-or-Enhanced-Logics.po | 39 +++++++++++++++++++ docs/locale/zh_CN/LC_MESSAGES/Whats-New.po | 6 +++ 5 files changed, 50 insertions(+) diff --git a/CREDITS.md b/CREDITS.md index ded5242180..52e7d3ab56 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -756,6 +756,7 @@ This page lists all the individual contributions to the project by their author. - Roof production anim - Customize whether the unit exits from the roof - Customize whether the unit can be detected by psychic detector + - Customizable infantry sequence rates - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 2d5542f1be..3b2cd6bfe5 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -433,6 +433,7 @@ HideShakeEffects=false ; boolean - [Customize whether the unit can be detected by psychic detector](Fixed-or-Improved-Logics.md#customize-whether-the-unit-can-be-detected-by-psychic-detector) (by Noble_Fish) - Cloak Enhancement (by NetsuNegi) - [`513` Set mission timer properties](AI-Scripting-and-Mapping.md#set-mission-timer-properties) (by Ollerus) +- [Customizable infantry sequence rates](New-or-Enhanced-Logics.md#customizable-infantry-sequence-rates) (by Noble_Fish) #### Vanilla fixes: - Fixed the bug where a building with `Factory=BuildingType` owned by the AI did not play `ProductionAnim` when placing a produced building (by Noble_Fish) diff --git a/docs/locale/zh_CN/LC_MESSAGES/CREDITS.po b/docs/locale/zh_CN/LC_MESSAGES/CREDITS.po index af6dc40f4f..8c54abab96 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/CREDITS.po +++ b/docs/locale/zh_CN/LC_MESSAGES/CREDITS.po @@ -2579,6 +2579,9 @@ msgstr "天窗生产动画" msgid "Customize whether the unit exits from the roof" msgstr "自定义单位是否从天窗退出" +msgid "Customizable infantry sequence rates" +msgstr "自定义步兵序列播放速率" + msgid "**Ollerus**:" msgstr "**Ollerus**:" diff --git a/docs/locale/zh_CN/LC_MESSAGES/New-or-Enhanced-Logics.po b/docs/locale/zh_CN/LC_MESSAGES/New-or-Enhanced-Logics.po index 75693ac6dc..a3aadfdb9c 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/New-or-Enhanced-Logics.po +++ b/docs/locale/zh_CN/LC_MESSAGES/New-or-Enhanced-Logics.po @@ -1928,6 +1928,45 @@ msgstr "" "现在步兵处于匍匐(爪巴)或部署状态的 FLH 可以独立于 `PrimaryFireFLH` 和 `SecondaryFireFLH` " "进行设置。同样对 [Burst 开火坐标](#firing-offsets-for-specific-burst-shots) 生效。" +msgid "Customizable infantry sequence rates" +msgstr "自定义步兵序列播放速率" + +msgid "" +"In vanilla, the number of game frames each animation frame of the " +"infantry sequence stays (i.e., its playback rate) is hardcoded. Now it " +"can be customized for each sequence in each infantry's sequence section." +msgstr "" +"在原版中,步兵序列每个动画帧所停留的游戏帧数(即其播放速率)是硬编码的。现在可以在每个步兵的序列段落中为每个序列自定义。" + +msgid "" +"`Rate` defines the number of game logic frames that 1 animation" +" frame stays; 0 means still, for example, the `Ready` and `Guard` " +"sequences in vanilla are still." +msgstr "" +"`Rate` 用于定义 1 动画帧所停留的游戏逻辑帧数,0 代表静止,例如原版中的 `Ready` 和 `Guard` 序列是静止的。" + +msgid "" +"`Normalized` defines whether the playback rate is affected by " +"game speed, for example, the `Idle1`, `Idle2`, `WetIdle1`, `WetIdle2`, " +"`Hover`, and `Cheer` sequences in vanilla are affected by game speed." +msgstr "" +"`Normalized` 用于定义播放速率是否受游戏速度影响,例如原版中的 `Idle1`、`Idle2`、`WetIdle1`、`WetIdle2`、`Hover` 和 `Cheer` 序列受游戏速度影响。" + +msgid "" +"The effective rate of a normalized sequence is `rate` adjusted by the " +"game speed:" +msgstr "" +"`Normalized` 化序列的有效速率为经过游戏速度调整后的 `rate`:" + +msgid "`rate < 5`: from the table below (`rate` row, `GameSpeed` column)." +msgstr "`rate < 5`:使用下表(`rate` 行,`GameSpeed` 列)。" + +msgid "`rate >= 5`: `rate * 8 / (GameSpeed + 1)`." +msgstr "`rate >= 5`:`rate * 8 / (GameSpeed + 1)`。" + +msgid "`GameSpeed` goes from `0` (fastest) to `6` (slowest)." +msgstr "`GameSpeed` 的取值范围为 `0`(最快)到 `6`(最慢)。" + msgid "Customizable `SlavesFreeSound`" msgstr "自定义 `SlavesFreeSound`" diff --git a/docs/locale/zh_CN/LC_MESSAGES/Whats-New.po b/docs/locale/zh_CN/LC_MESSAGES/Whats-New.po index 508ac00dc4..331d4967e2 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/Whats-New.po +++ b/docs/locale/zh_CN/LC_MESSAGES/Whats-New.po @@ -867,6 +867,12 @@ msgstr "" msgid "Cloak Enhancement (by NetsuNegi)" msgstr "隐形增强(by NetsuNegi)" +msgid "" +"[Customizable infantry sequence rates](New-or-Enhanced-Logics.md" +"#customizable-infantry-sequence-rates) (by Noble_Fish)" +msgstr "" +"[自定义步兵序列播放速率](New-or-Enhanced-Logics.md#customizable-infantry-sequence-rates)(by Noble_Fish)" + msgid "Vanilla fixes:" msgstr "原版问题修复:" From edb3b7a8cba6629d18bc69267c0f915ef26bf551 Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sun, 30 Aug 2026 18:22:56 +0800 Subject: [PATCH 3/3] [doc] Default value table --- docs/New-or-Enhanced-Logics.md | 139 ++++++++++++--------------------- 1 file changed, 52 insertions(+), 87 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 540e991b6c..1711879f7d 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -831,97 +831,62 @@ The effective rate of a normalized sequence is `rate` adjusted by the game speed In `rulesmd.ini`: ```ini [AudioVisual] -Sequence.Ready.DefaultRate=0 ; integer -Sequence.Ready.DefaultNormalized=no ; boolean -Sequence.Guard.DefaultRate=0 ; integer -Sequence.Guard.DefaultNormalized=no ; boolean -Sequence.Prone.DefaultRate=6 ; integer -Sequence.Prone.DefaultNormalized=no ; boolean -Sequence.Walk.DefaultRate=3 ; integer -Sequence.Walk.DefaultNormalized=no ; boolean -Sequence.FireUp.DefaultRate=1 ; integer -Sequence.FireUp.DefaultNormalized=no ; boolean -Sequence.Down.DefaultRate=1 ; integer -Sequence.Down.DefaultNormalized=no ; boolean -Sequence.Crawl.DefaultRate=1 ; integer -Sequence.Crawl.DefaultNormalized=no ; boolean -Sequence.Up.DefaultRate=1 ; integer -Sequence.Up.DefaultNormalized=no ; boolean -Sequence.FireProne.DefaultRate=1 ; integer -Sequence.FireProne.DefaultNormalized=no ; boolean -Sequence.Idle1.DefaultRate=3 ; integer -Sequence.Idle1.DefaultNormalized=yes ; boolean -Sequence.Idle2.DefaultRate=3 ; integer -Sequence.Idle2.DefaultNormalized=yes ; boolean -Sequence.Die1.DefaultRate=1 ; integer -Sequence.Die1.DefaultNormalized=no ; boolean -Sequence.Die2.DefaultRate=1 ; integer -Sequence.Die2.DefaultNormalized=no ; boolean -Sequence.Die3.DefaultRate=1 ; integer -Sequence.Die3.DefaultNormalized=no ; boolean -Sequence.Die4.DefaultRate=1 ; integer -Sequence.Die4.DefaultNormalized=no ; boolean -Sequence.Die5.DefaultRate=1 ; integer -Sequence.Die5.DefaultNormalized=no ; boolean -Sequence.Tread.DefaultRate=3 ; integer -Sequence.Tread.DefaultNormalized=no ; boolean -Sequence.Swim.DefaultRate=1 ; integer -Sequence.Swim.DefaultNormalized=no ; boolean -Sequence.WetIdle1.DefaultRate=3 ; integer -Sequence.WetIdle1.DefaultNormalized=yes ; boolean -Sequence.WetIdle2.DefaultRate=3 ; integer -Sequence.WetIdle2.DefaultNormalized=yes ; boolean -Sequence.WetDie1.DefaultRate=1 ; integer -Sequence.WetDie1.DefaultNormalized=no ; boolean -Sequence.WetDie2.DefaultRate=1 ; integer -Sequence.WetDie2.DefaultNormalized=no ; boolean -Sequence.WetAttack.DefaultRate=1 ; integer -Sequence.WetAttack.DefaultNormalized=no ; boolean -Sequence.Hover.DefaultRate=2 ; integer -Sequence.Hover.DefaultNormalized=yes ; boolean -Sequence.Fly.DefaultRate=1 ; integer -Sequence.Fly.DefaultNormalized=no ; boolean -Sequence.Tumble.DefaultRate=1 ; integer -Sequence.Tumble.DefaultNormalized=no ; boolean -Sequence.FireFly.DefaultRate=1 ; integer -Sequence.FireFly.DefaultNormalized=no ; boolean -Sequence.Deploy.DefaultRate=1 ; integer -Sequence.Deploy.DefaultNormalized=no ; boolean -Sequence.Deployed.DefaultRate=1 ; integer -Sequence.Deployed.DefaultNormalized=no ; boolean -Sequence.DeployedFire.DefaultRate=1 ; integer -Sequence.DeployedFire.DefaultNormalized=no ; boolean -Sequence.DeployedIdle.DefaultRate=1 ; integer -Sequence.DeployedIdle.DefaultNormalized=no ; boolean -Sequence.Undeploy.DefaultRate=1 ; integer -Sequence.Undeploy.DefaultNormalized=no ; boolean -Sequence.Cheer.DefaultRate=3 ; integer -Sequence.Cheer.DefaultNormalized=yes ; boolean -Sequence.Paradrop.DefaultRate=1 ; integer -Sequence.Paradrop.DefaultNormalized=no ; boolean -Sequence.AirDeathStart.DefaultRate=3 ; integer -Sequence.AirDeathStart.DefaultNormalized=no ; boolean -Sequence.AirDeathFalling.DefaultRate=1 ; integer -Sequence.AirDeathFalling.DefaultNormalized=no ; boolean -Sequence.AirDeathFinish.DefaultRate=3 ; integer -Sequence.AirDeathFinish.DefaultNormalized=no ; boolean -Sequence.Panic.DefaultRate=4 ; integer -Sequence.Panic.DefaultNormalized=no ; boolean -Sequence.Shovel.DefaultRate=6 ; integer -Sequence.Shovel.DefaultNormalized=no ; boolean -Sequence.Carry.DefaultRate=3 ; integer -Sequence.Carry.DefaultNormalized=no ; boolean -Sequence.SecondaryFire.DefaultRate=1 ; integer -Sequence.SecondaryFire.DefaultNormalized=no ; boolean -Sequence.SecondaryProne.DefaultRate=1 ; integer -Sequence.SecondaryProne.DefaultNormalized=no ; boolean +Sequence..DefaultRate=0 ; integer +Sequence..DefaultNormalized=no ; boolean +``` + +```{dropdown} Default value table +| Name | DefaultRate | Normalized | +|-----------------|-------------|------------| +| Ready | 0 | false | +| Guard | 0 | false | +| Prone | 6 | false | +| Walk | 3 | false | +| FireUp | 1 | false | +| Down | 1 | false | +| Crawl | 1 | false | +| Up | 1 | false | +| FireProne | 1 | false | +| Idle1 | 3 | true | +| Idle2 | 3 | true | +| Die1 | 1 | false | +| Die2 | 1 | false | +| Die3 | 1 | false | +| Die4 | 1 | false | +| Die5 | 1 | false | +| Tread | 3 | false | +| Swim | 1 | false | +| WetIdle1 | 3 | true | +| WetIdle2 | 3 | true | +| WetDie1 | 1 | false | +| WetDie2 | 1 | false | +| WetAttack | 1 | false | +| Hover | 2 | true | +| Fly | 1 | false | +| Tumble | 1 | false | +| FireFly | 1 | false | +| Deploy | 1 | false | +| Deployed | 1 | false | +| DeployedFire | 1 | false | +| DeployedIdle | 1 | false | +| Undeploy | 1 | false | +| Cheer | 3 | true | +| Paradrop | 1 | false | +| AirDeathStart | 3 | false | +| AirDeathFalling | 1 | false | +| AirDeathFinish | 3 | false | +| Panic | 4 | false | +| Shovel | 6 | false | +| Carry | 3 | false | +| SecondaryFire | 1 | false | +| SecondaryProne | 1 | false | ``` In `artmd.ini`: ```ini -[SEQUENCE] ; Sequence -Rate= ; integer, defaults to [AudioVisual] -> Sequence..DefaultRate -Normalized= ; boolean, defaults to [AudioVisual] -> Sequence..DefaultNormalized +[SEQUENCE] ; Sequence +Rate= ; integer, defaults to [AudioVisual] -> Sequence..DefaultRate +Normalized= ; boolean, defaults to [AudioVisual] -> Sequence..DefaultNormalized ``` ### Customizable `SlavesFreeSound`