Skip to content

Commit

Permalink
New & restored TerrainType features (#1241)
Browse files Browse the repository at this point in the history
## Animated TerrainTypes

- Length of the animation can now be customized by setting
`AnimationLength` as well, defaulting to half (or quarter if damaged
frames are enabled) the number of frames in TerrainType's image.

In `rulesmd.ini`:
```ini
[SOMETERRAINTYPE]  ; TerrainType
AnimationLength=   ; integer, number of frames
```

## Damaged frames and crumbling animation

- By default game shows damage frame only for TerrainTypes alive at only
1 point of health left. Because none of the original game TerrainType
assets were made with this in mind, the logic is now locked behind a new
key `HasDamagedFrames`.
- Instead of showing at 1 point of HP left, TerrainTypes switch to
damaged frames once their health reaches `[AudioVisual]` ->
`ConditionYellow.Terrain` percentage of their maximum health. Defaults
to `ConditionYellow` if not set.
- In addition, TerrainTypes can now show 'crumbling' animation after
their health has reached zero and before they are deleted from the map
by setting `HasCrumblingFrames` to true.
- Crumbling frames start from first frame after both regular & damaged
frames and ends at halfway point of the frames in TerrainType's image.
- Note that the number of regular & damage frames considered for this
depends on value of `HasDamagedFrames` and for `IsAnimated`
TerrainTypes, `AnimationLength`. Exercise caution and ensure there are
correct amount of frames to display.
- Sound event from `CrumblingSound` (if set) is played when crumbling
animation starts playing.
- Destroy animation & sound only play after crumbling animation has
finished.

In `rulesmd.ini`:
```ini
[AudioVisual]
ConditionYellow.Terrain=  ; floating-point value

[SOMETERRAINTYPE]         ; TerrainType
HasDamagedFrames=false    ; boolean
HasCrumblingFrames=false
CrumblingSound=           ; Sound
```

## Custom palette

- You can now specify custom palette for TerrainTypes in similar manner
as TechnoTypes can.
- Note that this palette behaves like an object palette and does not use
tint etc. that have been applied to the tile the TerrainType resides on
like a TerrainType using tile palette would.

In `artmd.ini`:
```ini
[SOMETERRAINTYPE]  ; TerrainType
Palette=           ; filename - excluding .pal extension and three-character theater-specific suffix
```
  • Loading branch information
Starkku authored Aug 28, 2024
1 parent 8baebb6 commit 4ce6739
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 28 deletions.
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ This page lists all the individual contributions to the project by their author.
- Trailer animation owner inheritance
- Warhead detonation on all objects on map
- Animated TerrainTypes extension
- TerrainType damage & crumbling frames
- Exploding unit passenger killing customization
- Railgun particle target coordinate fix
- Building target coordinate offset fix
Expand Down
44 changes: 43 additions & 1 deletion docs/Fixed-or-Improved-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ This page describes all ingame logics that are fixed or improved in Phobos witho
![Waving trees](_static/images/tree-shake.gif)
*Animated trees used in [Ion Shock](https://www.moddb.com/mods/tiberian-war-ionshock)*

- `IsAnimated`, `AnimationRate` and `AnimationProbability` now work on TerrainTypes without `SpawnsTiberium` set to true. Note that this might impact performance.
- Fixed transports recursively put into each other not having a correct killer set after second transport when being killed by something.

![image](_static/images/translucency-fix.png)
Expand Down Expand Up @@ -877,6 +876,17 @@ ForbidParallelAIQueues.Building=no ; boolean

## Terrains

### Animated TerrainTypes

- By default `IsAnimated`, `AnimationRate` and `AnimationProbability` only work on TerrainTypes with `SpawnsTiberium` set to true. This restriction has now been lifted.
- Length of the animation can now be customized by setting `AnimationLength` as well, defaulting to half (or quarter if [damaged frames](#damaged-frames-and-crumbling-animation) are enabled) the number of frames in TerrainType's image.

In `rulesmd.ini`:
```ini
[SOMETERRAINTYPE] ; TerrainType
AnimationLength= ; integer, number of frames
```

### Customizable ore spawners

![image](_static/images/ore-01.png)
Expand All @@ -895,6 +905,38 @@ SpawnsTiberium.GrowthStage=3 ; integer - single or comma-sep. range
SpawnsTiberium.CellsPerAnim=1 ; integer - single or comma-sep. range
```

### Custom palette

- You can now specify custom palette for TerrainTypes in similar manner as TechnoTypes can.
- Note that this palette behaves like an object palette and does not use tint etc. that have been applied to the tile the TerrainType resides on like a TerrainType using tile palette would.

In `artmd.ini`:
```ini
[SOMETERRAINTYPE] ; TerrainType
Palette= ; filename - excluding .pal extension and three-character theater-specific suffix
```

### Damaged frames and crumbling animation

- By default game shows damage frame only for TerrainTypes alive at only 1 point of health left. Because none of the original game TerrainType assets were made with this in mind, the logic is now locked behind a new key `HasDamagedFrames`.
- Instead of showing at 1 point of HP left, TerrainTypes switch to damaged frames once their health reaches `[AudioVisual]` -> `ConditionYellow.Terrain` percentage of their maximum health. Defaults to `ConditionYellow` if not set.
- In addition, TerrainTypes can now show 'crumbling' animation after their health has reached zero and before they are deleted from the map by setting `HasCrumblingFrames` to true.
- Crumbling frames start from first frame after both regular & damaged frames and ends at halfway point of the frames in TerrainType's image.
- Note that the number of regular & damage frames considered for this depends on value of `HasDamagedFrames` and for `IsAnimated` TerrainTypes, `AnimationLength` (see [Animated TerrainTypes](#animated-terraintypes). Exercise caution and ensure there are correct amount of frames to display.
- Sound event from `CrumblingSound` (if set) is played when crumbling animation starts playing.
- [Destroy animation & sound](New-or-Enhanced-Logics.md#destroy-animation-sound) only play after crumbling animation has finished.

In `rulesmd.ini`:
```ini
[AudioVisual]
ConditionYellow.Terrain= ; floating-point value

[SOMETERRAINTYPE] ; TerrainType
HasDamagedFrames=false ; boolean
HasCrumblingFrames=false
CrumblingSound= ; Sound
```

### Minimap color customization

- TerrainTypes can now be made to display on minimap with different colors by setting `MinimapColor`.
Expand Down
2 changes: 2 additions & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ New:
- Disabling `MultipleFactory` bonus from specific BuildingType (by Starkku)
- Customizable ChronoSphere teleport delays for units (by Starkku)
- Allowed and disallowed types for `FactoryPlant` (by Starkku)
- Customizable damage & 'crumbling' (destruction) frames for TerrainTypes (by Starkku)
- Custom object palettes for TerrainTypes (by Starkku)
Vanilla fixes:
- Allow AI to repair structures built from base nodes/trigger action 125/SW delivery in single player missions (by Trsdy)
Expand Down
16 changes: 2 additions & 14 deletions src/Ext/OverlayType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,17 @@ void OverlayTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)

this->ZAdjust.Read(exArtINI, pArtSection, "ZAdjust");
this->PaletteFile.Read(pArtINI, pArtSection, "Palette");

BuildPalette();
this->Palette = GeneralUtils::BuildPalette(this->PaletteFile);

if (GeneralUtils::IsValidString(this->PaletteFile) && !this->Palette)
Debug::Log("[Developer warning] [%s] has Palette=%s set but no palette file was loaded (missing file or wrong filename). Missing palettes cause issues with lighting recalculations.\n", pArtSection, this->PaletteFile);
}

void OverlayTypeExt::ExtData::BuildPalette()
{
if (GeneralUtils::IsValidString(this->PaletteFile))
{
char pFilename[0x20];
strcpy_s(pFilename, this->PaletteFile.data());

this->Palette = ColorScheme::GeneratePalette(pFilename);
}
}

void OverlayTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
{
Extension<OverlayTypeClass>::LoadFromStream(Stm);
this->Serialize(Stm);
this->BuildPalette();
this->Palette = GeneralUtils::BuildPalette(this->PaletteFile);
}

void OverlayTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm)
Expand Down
1 change: 0 additions & 1 deletion src/Ext/OverlayType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ class OverlayTypeExt
private:
template <typename T>
void Serialize(T& Stm);
void BuildPalette();
};

class ExtContainer final : public Container<OverlayTypeExt>
Expand Down
6 changes: 3 additions & 3 deletions src/Ext/OverlayType/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ DEFINE_HOOK(0x47F974, CellClass_DrawOverlay_Walls, 0x5)
int colorSchemeIndex = HouseClass::CurrentPlayer->ColorSchemeIndex;

if (wallOwnerIndex >= 0)
colorSchemeIndex = HouseClass::Array->GetItem(wallOwnerIndex)->ColorSchemeIndex;
colorSchemeIndex = HouseClass::Array->Items[wallOwnerIndex]->ColorSchemeIndex;

LightConvertClass* pConvert = nullptr;
auto const pTypeExt = OverlayTypeExt::ExtMap.Find(pOverlayType);

if (pTypeExt->Palette)
pConvert = pTypeExt->Palette->GetItem(colorSchemeIndex)->LightConvert;
pConvert = pTypeExt->Palette->Items[colorSchemeIndex]->LightConvert;
else
pConvert = ColorScheme::Array->GetItem(colorSchemeIndex)->LightConvert;
pConvert = ColorScheme::Array->Items[colorSchemeIndex]->LightConvert;

DSurface::Temp->DrawSHP(pConvert, pShape, pThis->OverlayData, &pLocation, pBounds,
BlitterFlags(0x4E00), 0, -2 - zAdjust, ZGradient::Deg90, pThis->Intensity_Normal, 0, 0, 0, 0, 0);
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Rules/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)
this->PlacementPreview.Read(exINI, GameStrings::AudioVisual, "PlacementPreview");
this->PlacementPreview_Translucency.Read(exINI, GameStrings::AudioVisual, "PlacementPreview.Translucency");

this->ConditionYellow_Terrain.Read(exINI, GameStrings::AudioVisual, "ConditionYellow.Terrain");
this->Shield_ConditionYellow.Read(exINI, GameStrings::AudioVisual, "Shield.ConditionYellow");
this->Shield_ConditionRed.Read(exINI, GameStrings::AudioVisual, "Shield.ConditionRed");
this->Pips_Shield.Read(exINI, GameStrings::AudioVisual, "Pips.Shield");
Expand Down Expand Up @@ -286,6 +287,7 @@ void RulesExt::ExtData::Serialize(T& Stm)
.Process(this->PlacementGrid_TranslucencyWithPreview)
.Process(this->PlacementPreview)
.Process(this->PlacementPreview_Translucency)
.Process(this->ConditionYellow_Terrain)
.Process(this->Shield_ConditionYellow)
.Process(this->Shield_ConditionRed)
.Process(this->Pips_Shield)
Expand Down
1 change: 1 addition & 0 deletions src/Ext/Rules/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class RulesExt
Valueable<bool> PlacementPreview;
TranslucencyLevel PlacementPreview_Translucency;

Nullable<double> ConditionYellow_Terrain;
Nullable<double> Shield_ConditionYellow;
Nullable<double> Shield_ConditionRed;
Valueable<Vector3D<int>> Pips_Shield;
Expand Down
29 changes: 29 additions & 0 deletions src/Ext/TerrainType/Body.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "Body.h"

#include <AnimClass.h>
#include <TacticalClass.h>
#include <TerrainClass.h>
#include <TerrainTypeClass.h>
Expand All @@ -18,6 +19,14 @@ int TerrainTypeExt::ExtData::GetCellsPerAnim()
return GeneralUtils::GetRangedRandomOrSingleValue(this->SpawnsTiberium_CellsPerAnim.Get());
}

void TerrainTypeExt::ExtData::PlayDestroyEffects(const CoordStruct& coords)
{
VocClass::PlayIndexAtPos(this->DestroySound, coords);

if (auto const pAnimType = this->DestroyAnim)
GameCreate<AnimClass>(pAnimType, coords);
}

void TerrainTypeExt::Remove(TerrainClass* pTerrain)
{
if (!pTerrain)
Expand Down Expand Up @@ -46,6 +55,11 @@ void TerrainTypeExt::ExtData::Serialize(T& Stm)
.Process(this->MinimapColor)
.Process(this->IsPassable)
.Process(this->CanBeBuiltOn)
.Process(this->HasDamagedFrames)
.Process(this->HasCrumblingFrames)
.Process(this->CrumblingSound)
.Process(this->AnimationLength)
.Process(this->PaletteFile)
;
}

Expand All @@ -71,14 +85,29 @@ void TerrainTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->IsPassable.Read(exINI, pSection, "IsPassable");
this->CanBeBuiltOn.Read(exINI, pSection, "CanBeBuiltOn");

this->HasDamagedFrames.Read(exINI, pSection, "HasDamagedFrames");
this->HasCrumblingFrames.Read(exINI, pSection, "HasCrumblingFrames");
this->CrumblingSound.Read(exINI, pSection, "CrumblingSound");
this->AnimationLength.Read(exINI, pSection, "AnimationLength");

//Strength is already part of ObjecTypeClass::ReadIni Duh!
//this->TerrainStrength.Read(exINI, pSection, "Strength");

auto const pArtINI = &CCINIClass::INI_Art();
auto pArtSection = pThis->ImageFile;

this->PaletteFile.Read(pArtINI, pArtSection, "Palette");
this->Palette = GeneralUtils::BuildPalette(this->PaletteFile);

if (GeneralUtils::IsValidString(this->PaletteFile) && !this->Palette)
Debug::Log("[Developer warning] [%s] has Palette=%s set but no palette file was loaded (missing file or wrong filename). Missing palettes cause issues with lighting recalculations.\n", pArtSection, this->PaletteFile);
}

void TerrainTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
{
Extension<TerrainTypeClass>::LoadFromStream(Stm);
this->Serialize(Stm);
this->Palette = GeneralUtils::BuildPalette(this->PaletteFile);
}

void TerrainTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm)
Expand Down
14 changes: 14 additions & 0 deletions src/Ext/TerrainType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ class TerrainTypeExt
Nullable<ColorStruct> MinimapColor;
Valueable<bool> IsPassable;
Valueable<bool> CanBeBuiltOn;
Valueable<bool> HasDamagedFrames;
Valueable<bool> HasCrumblingFrames;
ValueableIdx<VocClass> CrumblingSound;
Nullable<int> AnimationLength;

PhobosFixedString<32u> PaletteFile;
DynamicVectorClass<ColorScheme*>* Palette; // Intentionally not serialized - rebuilt from the palette file on load.

ExtData(TerrainTypeClass* OwnerObject) : Extension<TerrainTypeClass>(OwnerObject)
, SpawnsTiberium_Type { 0 }
Expand All @@ -37,6 +44,12 @@ class TerrainTypeExt
, MinimapColor {}
, IsPassable { false }
, CanBeBuiltOn { false }
, HasDamagedFrames { false }
, HasCrumblingFrames { false }
, CrumblingSound {}
, AnimationLength {}
, PaletteFile {}
, Palette {}
{ }

virtual ~ExtData() = default;
Expand All @@ -50,6 +63,7 @@ class TerrainTypeExt

int GetTiberiumGrowthStage();
int GetCellsPerAnim();
void PlayDestroyEffects(const CoordStruct& coords);

private:
template <typename T>
Expand Down
Loading

0 comments on commit 4ce6739

Please sign in to comment.