diff --git a/code/language/language.h b/code/language/language.h index 90cc5f3c8..686472e83 100644 --- a/code/language/language.h +++ b/code/language/language.h @@ -1165,6 +1165,7 @@ #define IDC_DESYNC_CONTINUE 1712 #define IDC_DESYNC_QUIT 1713 #define IDC_EDGE_SCROLL 1714 +#define TXT_SUPERWEAPON_TOOLTIP 1715 // Next default values for new objects // diff --git a/code/language/language.rc b/code/language/language.rc index 1b35fbc6f..63bc56eb8 100644 --- a/code/language/language.rc +++ b/code/language/language.rc @@ -131,8 +131,8 @@ BEGIN TXT_COPYRIGHT "© 2000 Electronic Arts, All Rights Reserved" TXT_TIME_FORMAT_HOURS "Time:%02d:%02d:%02d" TXT_TIME_FORMAT_NO_HOURS "Time:%02d:%02d" - TXT_MONEY_FORMAT_1 "$%d" - TXT_MONEY_FORMAT_2 "%s ($%d)" + TXT_MONEY_FORMAT_1 "{}\r${}" + TXT_MONEY_FORMAT_2 "{}\r${}\r\r{}" TXT_READY "Ready" TXT_HOLD "On Hold" TXT_PRIMARY "Primary" @@ -1084,6 +1084,7 @@ BEGIN TXT_CONNECTION_QUALITY_STATUS "Connection quality target: %s." TXT_CONNECTION_QUALITY_RUNG "%s (rung %u)" TXT_PLAYER_NUMBER "Player %d" + TXT_SUPERWEAPON_TOOLTIP "{}\r\r{}" END #endif // English (U.S.) resources diff --git a/code/sidebar.cpp b/code/sidebar.cpp index a3a704094..2bcab7492 100644 --- a/code/sidebar.cpp +++ b/code/sidebar.cpp @@ -126,6 +126,7 @@ #include #include +#include ShapeSet const * SidebarClass::SidebarShape = NULL; ShapeSet const * SidebarClass::SidebarMiddleShape = NULL; @@ -1821,14 +1822,22 @@ bool SidebarClass::StripClass::AI(KeyNumType & input, Point2D const & xy) /// Returns with a pointer to the help text, or NULL if that slot is empty. char const * SidebarClass::StripClass::Help_Text(int id) { - static char _buffer[84]; + static std::string text = ""; int i = id + TopIndex; if (GameActive) { if (i < BuildableCount) { if (Buildables[i].BuildableType == RTTI_SPECIAL) { - return(SuperWeaponTypes[Buildables[i].BuildableID]->Full_Name()); + auto superweapon = SuperWeaponTypes[Buildables[i].BuildableID]; + + if (superweapon->Description.empty()) { + return(superweapon->Full_Name()); + } + + std::string_view fullName = superweapon->Full_Name(); + text = std::vformat(Fetch_String(TXT_SUPERWEAPON_TOOLTIP), std::make_format_args(fullName, superweapon->Description)); + return(text.c_str()); } TechnoTypeClass const * choice = Fetch_Techno_Type(Buildables[i].BuildableType, Buildables[i].BuildableID); @@ -1836,13 +1845,16 @@ char const * SidebarClass::StripClass::Help_Text(int id) return(NULL); } - if (Map.IsCameoText) { - sprintf(_buffer, Fetch_String(TXT_MONEY_FORMAT_1), choice->Cost_Of(PlayerPtr)); + std::string_view fullName = choice->Full_Name(); + int cost = choice->Cost_Of(PlayerPtr); + + if (choice->Description.empty()) { + text = std::vformat(Fetch_String(TXT_MONEY_FORMAT_1), std::make_format_args(fullName, cost)); } else { - sprintf(_buffer, Fetch_String(TXT_MONEY_FORMAT_2), choice->Full_Name(), choice->Cost_Of(PlayerPtr)); + text = std::vformat(Fetch_String(TXT_MONEY_FORMAT_2), std::make_format_args(fullName, cost, choice->Description)); } - return(_buffer); + return(text.c_str()); } } diff --git a/code/suprtype.cpp b/code/suprtype.cpp index 67a37738d..f46f9852f 100644 --- a/code/suprtype.cpp +++ b/code/suprtype.cpp @@ -76,7 +76,8 @@ SuperWeaponTypeClass::SuperWeaponTypeClass(char const * ininame) : SidebarImage(), UseChargeDrain(false), IsPowered(true), - IsManualControl(false) + IsManualControl(false), + Description("") { Create_ID(); HeapID = (SuperWeaponType)SuperWeaponTypes.Count(); @@ -144,6 +145,7 @@ void SuperWeaponTypeClass::Serialize(SaveStreamClass & stream) stream.Serialize(UseChargeDrain); stream.Serialize(IsPowered); stream.Serialize(IsManualControl); + stream.Serialize(Description); } @@ -176,6 +178,7 @@ void SuperWeaponTypeClass::Compute_CRC(CRCEngine & crc) const crc(Type); crc(UseChargeDrain); crc(IsManualControl); + crc(Description.c_str()); } @@ -220,6 +223,8 @@ bool SuperWeaponTypeClass::Read_INI(CCINIClass const & ini) AuxBuilding = TGet_Class(ini, IniName, "AuxBuilding", AuxBuilding); UseChargeDrain = ini.Get_Bool(IniName, "UseChargeDrain", UseChargeDrain); IsManualControl = ini.Get_Bool(IniName, "ManualControl", IsManualControl); + Description = ini.Get_String(Name(), "Description", Description.c_str()); + CameoSortOrder = ini.Get_Int(IniName, "CameoSortOrder", CameoSortOrder); float recharge = ini.Get_Float(IniName, "RechargeTime"); diff --git a/code/suprtype.h b/code/suprtype.h index 66ff6b4e0..9c98a9b7f 100644 --- a/code/suprtype.h +++ b/code/suprtype.h @@ -136,6 +136,11 @@ class SuperWeaponTypeClass : public AbstractTypeClass * after every firing, so something else must decide when it becomes ready. */ bool IsManualControl; + + /* + * Optional description for the sidebar tooltip. + */ + std::string Description; }; SuperWeaponType Special_From_Name(char const * name); diff --git a/code/techtype.cpp b/code/techtype.cpp index dbd47bc7c..7cc93f898 100644 --- a/code/techtype.cpp +++ b/code/techtype.cpp @@ -200,7 +200,8 @@ TechnoTypeClass::TechnoTypeClass(char const * ininame, SpeedType speed) : ZFudgeCliff(10), ZFudgeColumn(5), ZFudgeTunnel(10), - ZFudgeBridge(0) + ZFudgeBridge(0), + Description("") { IsSentient = true; @@ -674,6 +675,7 @@ bool TechnoTypeClass::Read_INI(CCINIClass const & ini) TargetStrengthCoefficient = ini.Get_Float(Name(), "TargetStrengthCoefficient", TargetStrengthCoefficient == 0 ? Rule->TargetStrengthCoefficientDefault : TargetStrengthCoefficient); TargetDistanceCoefficient = ini.Get_Float(Name(), "TargetDistanceCoefficient", TargetDistanceCoefficient == 0 ? Rule->TargetDistanceCoefficientDefault : TargetDistanceCoefficient); SpecialThreatValue = ini.Get_Float(Name(), "SpecialThreatValue", SpecialThreatValue); + Description = ini.Get_String(Name(), "Description", Description.c_str()); IsLeader = false; if (Weapons[0].Weapon != NULL && Weapons[0].Weapon->Attack > 0) { @@ -1081,6 +1083,7 @@ void TechnoTypeClass::Serialize(SaveStreamClass & stream) stream.Serialize(ZFudgeColumn); stream.Serialize(ZFudgeTunnel); stream.Serialize(ZFudgeBridge); + stream.Serialize(Description); } @@ -1182,6 +1185,7 @@ void TechnoTypeClass::Compute_CRC(class CRCEngine & crc) const crc(IsManualReload); crc(IsVisibleLoad); crc(IsLightningRod); + crc(Description.c_str()); } diff --git a/code/techtype.h b/code/techtype.h index 5c453045c..61a7f627b 100644 --- a/code/techtype.h +++ b/code/techtype.h @@ -745,6 +745,11 @@ class TechnoTypeClass : public ObjectTypeClass int ZFudgeTunnel; int ZFudgeBridge; + /* + * Optional description for the sidebar tooltip. + */ + std::string Description; + //-------------------------------------------------------------------- TechnoTypeClass(char const * ininame, SpeedType speed); virtual ~TechnoTypeClass() override; diff --git a/manual/changes/sidebar-tooltip-descriptions.md b/manual/changes/sidebar-tooltip-descriptions.md new file mode 100644 index 000000000..c35b81159 --- /dev/null +++ b/manual/changes/sidebar-tooltip-descriptions.md @@ -0,0 +1,26 @@ +--- +title: Add descriptions to sidebar tooltips +category: feature +release: 0.2.0 +targets: +- type: system + id: sidebar + effect: changed +- type: system + id: superweapons + effect: changed +- type: key + id: Description + scope: aircrafttype + effect: added +- type: key + id: Description + scope: superweapontype + effect: added +credit: +- JoyfulShush +--- + +Buildable type sections now accept `Description=`. The sidebar tooltip always shows the type's name and price, and adds the description below them when it is set; `SidebarCameoText=` now changes only the cameo caption. + +Superweapon sections also accept `Description=`. When it is set, the text appears below the weapon's name in its sidebar tooltip. diff --git a/manual/content/keys/description--aircrafttype.md b/manual/content/keys/description--aircrafttype.md new file mode 100644 index 000000000..b07f92bac --- /dev/null +++ b/manual/content/keys/description--aircrafttype.md @@ -0,0 +1,16 @@ +--- +key: Description +scope: aircrafttype +label: Buildable type sidebar tooltip text +see_also: ["system:sidebar", Cameo, SidebarCameoText] +when_omitted: + kind: value + value: "" +--- + +`Description=` adds its text below the name and price in the sidebar cameo tooltip for any `TechnoTypeClass` type: aircraft, structures, infantry, and vehicles. The tooltip shows the name and price whether or not [`SidebarCameoText`](/keys/sidebarcameotext/) captions the cameo. The setting is read from the type's section in `rules.ini`. + +```ini title="rules.ini" +[MYTANK] ; example UnitType section +Description=Heavy armored tank +``` diff --git a/manual/content/keys/description--superweapontype.md b/manual/content/keys/description--superweapontype.md new file mode 100644 index 000000000..21deb2a23 --- /dev/null +++ b/manual/content/keys/description--superweapontype.md @@ -0,0 +1,16 @@ +--- +key: Description +scope: superweapontype +label: Superweapon sidebar tooltip text +see_also: ["system:superweapons", SidebarImage] +when_omitted: + kind: value + value: "" +--- + +`Description=` adds its text below the superweapon's name in its sidebar cameo tooltip. An empty or omitted value leaves the tooltip with the name alone. + +```ini title="rules.ini" +[MyIonStrike] ; example superweapon section +Description=Target a structure with a powerful ion blast +``` diff --git a/manual/content/keys/description.md b/manual/content/keys/description.md index fc3f39947..d4a5e03b9 100644 --- a/manual/content/keys/description.md +++ b/manual/content/keys/description.md @@ -1,4 +1,4 @@ --- key: Description -summary: The text a campaign, a multiplayer map, or a saved map seed is listed under. +summary: The text a campaign, a multiplayer map, a saved map seed is listed under, or a sidebar tooltip. --- diff --git a/manual/content/systems/sidebar.md b/manual/content/systems/sidebar.md index ebce2e2dd..eb15f262f 100644 --- a/manual/content/systems/sidebar.md +++ b/manual/content/systems/sidebar.md @@ -6,6 +6,7 @@ keys: - Cameo - CameoSortOrder - CreditTicks + - Description - MaximumQueuedObjects - RecheckPrerequisites - ScoldSound @@ -120,7 +121,7 @@ The top-right corner of a cameo shows how many of that type are outstanding: the With [`SidebarCameoText=yes`](/keys/sidebarcameotext/), each cameo is captioned with its name. A name wider than the cameo is wrapped at spaces and hyphens, with earlier lines stacked above later ones. The caption never shows the price. -The tooltip carries the price. It shows the price alone while captions are on, and the name and price while they are off. A superweapon's tooltip shows only its name. +The tooltip always shows a buildable type's name and price, whether captions are on or off. [`Description=`](/keys/description/#scope-unittype) adds its text below them for any `TechnoTypeClass` type. A superweapon's tooltip shows its name, and [`Description=`](/keys/description/#scope-superweapontype) adds its text below it. ## Clicking a build cameo diff --git a/manual/content/systems/superweapons.md b/manual/content/systems/superweapons.md index 8ae0bd08e..9b902ffaa 100644 --- a/manual/content/systems/superweapons.md +++ b/manual/content/systems/superweapons.md @@ -20,6 +20,7 @@ keys: - ChargeToDrainRatio - ChargingVoice - DamageToFirestormDamageCoefficient + - Description - FirestormWall - FirestormWarhead - GDIFirestormGenerator @@ -206,6 +207,8 @@ Two events outside the cycle also change the wall. Damage aimed at a raised sect A superweapon's cameo is the shape file named by [`SidebarImage=`](/keys/sidebarimage/), or `XXICON.SHP` when that file cannot be found. The cameo shows no numeric countdown. Its charge appears as a clock and a short caption. [The sidebar](/systems/sidebar/) covers where the cameo sits, how it is announced, how it is captioned, and when it leaves. +The tooltip shows the superweapon's name. [`Description=`](/keys/description/#scope-superweapontype) adds its text below the name. + The caption depends on the weapon's state. A charge-draining weapon has a caption while it charges and a fourth state that ordinary weapons lack. | Weapon state | Caption | diff --git a/manual/data/ini-keys.yaml b/manual/data/ini-keys.yaml index b81893b99..2e75e49cf 100644 --- a/manual/data/ini-keys.yaml +++ b/manual/data/ini-keys.yaml @@ -6983,6 +6983,34 @@ DeploysInto: Description: key: Description scopes: + - applies_to: *id003 + file: rules.ini + section: + kind: identifier + source: object-type + value_type: string + status: needs_review + _provenance: + default_candidate: '' + declared_in: TechnoTypeClass + member: Description + source: code/techtype.cpp + guard: null + level: TechnoTypeClass + - applies_to: *id011 + file: rules.ini + section: + kind: identifier + source: object-type + value_type: string + status: needs_review + _provenance: + default_candidate: '' + declared_in: SuperWeaponTypeClass + member: Description + source: code/suprtype.cpp + guard: null + level: SuperWeaponTypeClass - applies_to: *id021 file: battle.ini section: diff --git a/manual/site/package.json b/manual/site/package.json index 67d41e7ec..a9bad2f73 100644 --- a/manual/site/package.json +++ b/manual/site/package.json @@ -30,5 +30,8 @@ "@astrojs/check": "^0.9.6", "@types/semver": "^7.8.0", "typescript": "^5.9.3" + }, + "allowScripts": { + "esbuild@0.28.1": true } }