From 067ee604b18d8147bc9acfe6a03635291dde86c6 Mon Sep 17 00:00:00 2001 From: <> Date: Tue, 17 Dec 2024 12:08:22 +0000 Subject: [PATCH] Deployed b0bf22e with MkDocs version: 1.6.1 --- .nojekyll | 0 404.html | 1270 ++++ Credits/index.html | 3306 ++++++++ Developers/Building-Locally/index.html | 1660 ++++ Developers/Coding-standards/index.html | 1411 ++++ Developers/From-code-to-deployment/index.html | 1526 ++++ Developers/Game-Making-Tips/index.html | 1760 +++++ Developers/Map-rendering/index.html | 1425 ++++ .../index.html | 1804 +++++ .../Saved-games-and-transients/index.html | 1327 ++++ Developers/Testing-Android-Builds/index.html | 1513 ++++ .../index.html | 1495 ++++ Developers/UI-development/index.html | 1516 ++++ Developers/Uniques/index.html | 1470 ++++ Guiding-Principles/index.html | 1424 ++++ Modders/Autoupdates/index.html | 1390 ++++ Modders/Creating-a-UI-skin/index.html | 2417 ++++++ Modders/Creating-a-custom-tileset/index.html | 1772 +++++ Modders/Images-and-Audio/index.html | 2481 ++++++ Modders/Making-a-new-Civilization/index.html | 1594 ++++ .../Mod-file-structure/1-Overview/index.html | 1538 ++++ .../index.html | 2399 ++++++ .../3-Map-related-JSON-files/index.html | 2042 +++++ .../4-Unit-related-JSON-files/index.html | 1654 ++++ .../5-Miscellaneous-JSON-files/index.html | 2915 +++++++ Modders/Mods/index.html | 1620 ++++ Modders/Scenarios/index.html | 1400 ++++ Modders/Type-checking/index.html | 1491 ++++ Modders/Unique-parameters/index.html | 2001 +++++ Modders/schemas/buildings.json | 73 + Modders/schemas/civilopediaText.json | 22 + Modders/schemas/color.json | 11 + Modders/schemas/events.json | 32 + Modders/schemas/nations.json | 66 + Modders/schemas/stats.json | 14 + Modders/schemas/techs.json | 58 + Modders/schemas/terrains.json | 41 + Modders/schemas/tileImprovements.json | 38 + Modders/schemas/tileResources.json | 54 + Modders/schemas/uniques.json | 18 + Modders/schemas/unitPromotions.json | 29 + Modders/schemas/unitTypes.json | 19 + Modders/schemas/units.json | 56 + Modders/uniques/index.html | 5556 ++++++++++++++ Other/Force-rating-calculation/index.html | 1557 ++++ Other/Installing-on-macOS/index.html | 1329 ++++ .../index.html | 1429 ++++ Other/Multiplayer/index.html | 1522 ++++ Other/Regions/index.html | 1434 ++++ Other/Translating/index.html | 1670 ++++ Privacy-Policy/index.html | 1387 ++++ assets/Android_SDK_Platforms.png | Bin 0 -> 38563 bytes assets/Android_SDK_Tools.png | Bin 0 -> 51636 bytes assets/Desktop_Build.png | Bin 0 -> 20675 bytes assets/FasterUIDevelopment.png | Bin 0 -> 17369 bytes assets/Icon.png | Bin 0 -> 69531 bytes assets/favicon.png | Bin 0 -> 6626 bytes assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.88dd0f4e.min.js | 16 + assets/javascripts/bundle.88dd0f4e.min.js.map | 7 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.el.min.js | 1 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.he.min.js | 1 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.6ce7567c.min.js | 42 + .../workers/search.6ce7567c.min.js.map | 7 + assets/stylesheets/main.6f8fc17f.min.css | 1 + assets/stylesheets/main.6f8fc17f.min.css.map | 1 + assets/stylesheets/palette.06af60db.min.css | 1 + .../stylesheets/palette.06af60db.min.css.map | 1 + img.png | Bin 0 -> 822 bytes index.html | 1393 ++++ search/search_index.json | 1 + sitemap.xml | 3 + sitemap.xml.gz | Bin 0 -> 127 bytes 105 files changed, 74710 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 Credits/index.html create mode 100644 Developers/Building-Locally/index.html create mode 100644 Developers/Coding-standards/index.html create mode 100644 Developers/From-code-to-deployment/index.html create mode 100644 Developers/Game-Making-Tips/index.html create mode 100644 Developers/Map-rendering/index.html create mode 100644 Developers/Project-structure-and-major-classes/index.html create mode 100644 Developers/Saved-games-and-transients/index.html create mode 100644 Developers/Testing-Android-Builds/index.html create mode 100644 Developers/Translations,-mods,-and-modding-freedom-in-Open-Source/index.html create mode 100644 Developers/UI-development/index.html create mode 100644 Developers/Uniques/index.html create mode 100644 Guiding-Principles/index.html create mode 100644 Modders/Autoupdates/index.html create mode 100644 Modders/Creating-a-UI-skin/index.html create mode 100644 Modders/Creating-a-custom-tileset/index.html create mode 100644 Modders/Images-and-Audio/index.html create mode 100644 Modders/Making-a-new-Civilization/index.html create mode 100644 Modders/Mod-file-structure/1-Overview/index.html create mode 100644 Modders/Mod-file-structure/2-Civilization-related-JSON-files/index.html create mode 100644 Modders/Mod-file-structure/3-Map-related-JSON-files/index.html create mode 100644 Modders/Mod-file-structure/4-Unit-related-JSON-files/index.html create mode 100644 Modders/Mod-file-structure/5-Miscellaneous-JSON-files/index.html create mode 100644 Modders/Mods/index.html create mode 100644 Modders/Scenarios/index.html create mode 100644 Modders/Type-checking/index.html create mode 100644 Modders/Unique-parameters/index.html create mode 100644 Modders/schemas/buildings.json create mode 100644 Modders/schemas/civilopediaText.json create mode 100644 Modders/schemas/color.json create mode 100644 Modders/schemas/events.json create mode 100644 Modders/schemas/nations.json create mode 100644 Modders/schemas/stats.json create mode 100644 Modders/schemas/techs.json create mode 100644 Modders/schemas/terrains.json create mode 100644 Modders/schemas/tileImprovements.json create mode 100644 Modders/schemas/tileResources.json create mode 100644 Modders/schemas/uniques.json create mode 100644 Modders/schemas/unitPromotions.json create mode 100644 Modders/schemas/unitTypes.json create mode 100644 Modders/schemas/units.json create mode 100644 Modders/uniques/index.html create mode 100644 Other/Force-rating-calculation/index.html create mode 100644 Other/Installing-on-macOS/index.html create mode 100644 Other/Intentional-departures-from-Civ-V/index.html create mode 100644 Other/Multiplayer/index.html create mode 100644 Other/Regions/index.html create mode 100644 Other/Translating/index.html create mode 100644 Privacy-Policy/index.html create mode 100644 assets/Android_SDK_Platforms.png create mode 100644 assets/Android_SDK_Tools.png create mode 100644 assets/Desktop_Build.png create mode 100644 assets/FasterUIDevelopment.png create mode 100644 assets/Icon.png create mode 100644 assets/favicon.png create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.88dd0f4e.min.js create mode 100644 assets/javascripts/bundle.88dd0f4e.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js.map create mode 100644 assets/stylesheets/main.6f8fc17f.min.css create mode 100644 assets/stylesheets/main.6f8fc17f.min.css.map create mode 100644 assets/stylesheets/palette.06af60db.min.css create mode 100644 assets/stylesheets/palette.06af60db.min.css.map create mode 100644 img.png create mode 100644 index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/404.html b/404.html new file mode 100644 index 0000000000..f4f1bddc4c --- /dev/null +++ b/404.html @@ -0,0 +1,1270 @@ + + + + + + + + + + + + + + + + + + + Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Credits/index.html b/Credits/index.html new file mode 100644 index 0000000000..f9670d8aa1 --- /dev/null +++ b/Credits/index.html @@ -0,0 +1,3306 @@ + + + + + + + + + + + + + + + + + + + + + + + Credits - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Credits

+

Icon Credits

+

Flag Icons made by Freepik from www.flaticon.com and licensed by Creative Commons 3.0, except for:

+ +

New Unciv logo made by u-ndefined on Discord

+

Base tile icons for the "Fantasy Hex" tileset belong to CuddlyClover @ https://cuddlyclover.itch.io/fantasy-hex-tiles with a few additions by various contributors

+

Promotional trailer for Steam and other storefronts made by letstalkaboutdune

+

Unless otherwise specified, all the following are from the Noun Project licenced under either Creative Commons or Public Domain

+

Stat icons

+
    +
  • Gear by Alex Bickov for Production
  • +
  • Apple by Pedro Gonçalves for Food
  • +
  • gold by Eliricon for Gold
  • +
  • Beaker by Delwar Hossain for Science
  • +
  • Music by Naomi Atkinson for Culture
  • +
  • Smiley by Alexander Skowalsky for Happiness
  • +
  • Dove by Sandra for Faith
  • +
  • User by Stéphanie Rusch for Population
  • +
  • Unhappy By Daryl Vandermont for Malcontent
  • +
+

Units

+

Ancient Era

+
    +
  • Hammer By Hea Poh Lin for Worker
  • +
  • Flag By Melvin Poppelaars for Settler
  • +
  • Eagle By anggun for Scout
  • +
  • Axe By ehab.abdullah for Warrior
  • +
  • Haka By Josh for Maori Warrior
  • +
  • Spiked Club by Hamish
  • +
  • Bow And Arrow By Viktor Ostrovsky for Archer
  • +
  • Bow By Arthur Shlain for Bowman
  • +
  • Javelin By WEBTECHOPS LLP for Atlatlist
  • +
  • arrows by Ayub Irawan for Skirmisher
  • +
  • Fishing Vessel By Luis Prado for Work Boats
  • +
  • Greek Trireme By Zachary McCune for Trireme
  • +
  • Greek Trireme By Zachary McCune for Quinquereme. The original work has been modified.
  • +
  • dragon by BGBOXXX Design for Dromon
  • +
  • Viking Boat by Eucalyp for Galley
  • +
  • Chariot By Andrew Doane for Chariot Archer
  • +
  • Elephant By Luis Prado for War Elephant
  • +
  • Centaur by Michael Wohlwend for Horse Archer
  • +
  • Spear By Stephen Copinger for Spearman
  • +
  • Greek shield for Hoplite
  • +
  • ram by Becris for Battering Ram
  • +
  • Spear by lastspark for Marauder
  • +
  • Spiked club for Brute
  • +
  • Hoplite by Eucalyp for Immortal
  • +
  • Slingshot by James Keuning for Slinger
  • +
  • warrior By DinosoftLab for Pictish Warrior (combined with Shamrock, see Promotions)
  • +
+

Classical Era

+
    +
  • Catapult By Jakub Ukrop
  • +
  • Unloaded Crossbow By Hamish as Ballista
  • +
  • Bow and arrow By Lars Meiertoberens for Composite Bowman
  • +
  • Sword By Guilherme Furtado for Swordsman
  • +
  • Mohawk By Dairy Free Design for Mohawk Warrior
  • +
  • Roman Helmet By parkjisun for Legion
  • +
  • Horse By AFY Studio for Horseman
  • +
  • Horse Head By Juan Pablo Bravo for Companion Cavalry
  • +
  • Elephant By Angriawan Ditya Zulkarnain for African Forest Elephant. The original work has been modified.
  • +
  • Horse By Ranah Pixel Studio for Cataphract
  • +
+

Medieval Era

+
    +
  • Ship By Vanisha for Galleass
  • +
  • Crossbow By Creaticca Creative Agency for Crossbowman
  • +
  • Longbow By Hamish for Longbowman
  • +
  • Trebuchet By Ben Davis
  • +
  • Sword By uzeir syarief for Longswordsman
  • +
  • Samurai By Chanut is Industries
  • +
  • Spear By Alvaro Cabrera for Pikeman
  • +
  • Halberd parkjisun for Landsknecht
  • +
  • Knight By Tyler Glaude
  • +
+

Renaissance Era

+ +

Industrial Era

+
    +
  • Rifle By Chameleon Design for Rifleman
  • +
  • Bayonet By food lover for Carolean
  • +
  • military cap by Robert Bjurshagen for Mehal Sefari
  • +
  • Horse By Bakunetso Kaito for Cavalry
  • +
  • horse racing By Sergio Morozov for Cossack
  • +
  • Marching Band by Darrin Loeliger, US for Hussar. The original work has been modified.
  • +
  • Artillery By Creative Mania
  • +
  • Ship By Aisyah for Ironclad
  • +
+

Modern Era

+
    +
  • Submarine By Hea Poh Lin, MY
  • +
  • Helmet By Daniel Turner for Great War Infantry
  • +
  • Cap By Creative Mania for Foreign Legion
  • +
  • xm8 By Xela Ub for Infantry
  • +
  • Icon for Carrier made by JackRainy, based on Aircraft Carrier By IcoLabs, BR
  • +
  • Battleship By Vitaliy Gorbachev, KZ for Battleship
  • +
  • machine-gun By Joana Pereira for Machine Gun
  • +
  • artillery by Izwar Muis for Anti-Aircraft Gun
  • +
  • Tank By corpus delicti for Landship
  • +
  • Warship By zidney for Destroyer
  • +
+

Atomic Era

+ +

Information Era

+
    +
  • Submarine by Freepik adapted for Nuclear Submarine
  • +
  • APC By Luke Anthony Firth for Mechanized Infantry
  • +
  • Battleship by Edi Prastyo for Missile Cruiser
  • +
  • Modern Armor By Public Domain Nouns for Modern Armor
  • +
  • Nuclear Missile By Lluisa Iborra, ES
  • +
  • Robot by Lluisa Iborra, ES for Giant Death Robot
  • +
  • Missile By ProSymbols for SS Booster
  • +
  • Rocket By BomSymbols for SS Cockpit
  • +
  • Engine By Andre for SS Engine
  • +
  • Chamber By IYIKON for SS Stasis Chamber
  • +
+

All Eras

+
    +
  • Pallet By James Keuning for Great Artist
  • +
  • Gear By Melvin Salas for Great Engineer
  • +
  • Beaker By Delwar Hossain for Great Scientist
  • +
  • Dove by sandra for Great Prophet
  • +
  • General By anbileru adaleru for Great General
  • +
  • Religion by Bruno Gätjens González adapted for Missionary
  • +
  • invisibility cloak by Locad for Inquisitor
  • +
+

Units - AbsoluteUnits unitset images

+

Unless otherwise specified, units for the AbsoluteUnits unitset are made by letstalkaboutdune and are licensed under Creative Commons Attribution 4.0 International

+

Barbarian variants by Pelo, made for Playable Barbarians:

+
    +
  • Frigate-Barbarians
  • +
  • Horse Archer-Barbarians.png
  • +
  • Lancer-Barbarians
  • +
  • Longswordsman-Barbarians
  • +
  • Pikeman-Barbarians
  • +
  • Rider-Barbarians
  • +
  • Swordsman-Barbarians
  • +
  • Trireme-Barbarians
  • +
  • Musketman-Barbarians
  • +
+

By Basil:

+
    +
  • Worker-Barbarians
  • +
+

HexaRealm

+

Unless otherwise specified, Tile improvements and units, as well as the terrains and improvements for HexaRealm tileset, are made by The Bucketeer / @GeneralWadaling and are licenced under Creative Commons 3.0

+

HexaRealm tileset images by legacymtgsalvationuser69544 here:

+
    +
  • Coast-Land edge tiles
  • +
  • Coast-Ocean edge tiles
  • +
  • Lake-Land edge tiles
  • +
  • Krakatoa (based on The Bucketeer's tile)
  • +
+

Resources

+
    +
  • Saffron By parkjisun for Dye
  • +
  • Can By Nick Bluth for Aluminum
  • +
  • Coal By Michael Wohlwend
  • +
  • Anvil By Jason Dilworth for Iron
  • +
  • Deer By Richard Nixon
  • +
  • Banana By Adrian Coquet
  • +
  • Oil By Tiago Maricate (also as Civilopedia category icon)
  • +
  • Statue By Joris Hoogendoorn for Marble
  • +
  • Ribbon By Anton for Silk
  • +
  • Stone By AFY Studio
  • +
  • Goblet By Pedro Santos for Silver
  • +
  • Sugar By ahmad
  • +
  • Spice By ahmad
  • +
  • Radiation symbol By icon 54 for Uranium
  • +
  • Wine By Adrien Coquet
  • +
  • Wheat By Juraj Sedlak
  • +
  • Sheep By Unrecognized
  • +
  • Elephant By Kelsey Armstrong for Ivory
  • +
  • Cattle By Daniela Baptista
  • +
  • Leather By Alen Krummenacher for Furs
  • +
  • Gem By Lluisa Iborra
  • +
  • Joss Stick By Hea Poh Lin for Incense
  • +
  • Pottery By Laymik, UA for Porcelain
  • +
  • Jewelry By Shocho, IN
  • +
  • Lemons By sachin modgekar, IN for Citrus
  • +
  • Pipes By Nibras@design for Copper
  • +
  • Crab By YuguDesign
  • +
  • Truffle By parkjisun
  • +
  • Salt By HAMEL KHALED, DZ
  • +
+

Improvements

+
    +
  • Woodcutting axe By Chanut is Industries for Lumber Mill
  • +
  • Monument By Setyo Ari Wibowo for Landmark
  • +
  • Farm By Bonnie Beach
  • +
  • Bank By Scott Dunlap for Customs House
  • +
  • Academy By CJS
  • +
  • Factory By RULI for Manufactory
  • +
  • Mine By Edward Boatman
  • +
  • Corral By Luis Prado for Pasture
  • +
  • Plants By hendra sudibyo for Plantation
  • +
  • Pickaxe By Creative Stall for Quarry
  • +
  • Food stall By I Putu Kharismayadi for Trading post
  • +
  • Road By REVA
  • +
  • Ruins By Creative Stall for Ancient runs
  • +
  • Ruins By Paulo Volkova for City ruins
  • +
  • Fishing Net By Made for Fishing Boats
  • +
  • Moai By Template
  • +
  • Fort By Adrien Coquet
  • +
  • Citadel By Adrien Coquet
  • +
  • Village by Andrey Vasiliev
  • +
  • pumping station by Peter van Driel for Polder
  • +
  • Oil Platform by Georgiana Ionescu for Offshore Platform
  • +
  • Broom by Rakhmat Setiawan for Remove Fallout
  • +
  • Axe by Those Icons for Remove Forest and Remove Jungle
  • +
  • Machete by Robert A. Di Ieso for Remove Marsh
  • +
  • Icon for Remove Railroad by vegeta1k95
  • +
  • Icon for Remove Road by vegeta1k95
  • +
+

Buildings

+
    +
  • Building by Aurel for fallback image
  • +
+

Ancient Era

+ +

Classical Era

+ +

Medieval Era

+ +

Renaissance Era

+ +

Industrial Era

+ +

Modern Era

+ +

Atomic Era

+ +

Information Era

+ +

All Era's

+ +

Social Policies

+

Tradition

+
    +
  • coat of arms By Martina Krasnayova for Oligarchy
  • +
  • Apple By EnQiu for Landed Elite
  • +
  • Crown By Alexander Skowalsky for Monarchy
  • +
  • Pyramid By Creative Stall for Aristocracy
  • +
  • Gavel By Rflor for Legalism
  • +
+

Liberty

+
    +
  • People By Elizabeth Lopez for Citizenship
  • +
  • Assembly By Noël Rasendrason for Republic
  • +
  • People By Gregor Cresnar for Meritocracy
  • +
  • People By Wilson Joseph for Representation
  • +
  • Torch By Hea Poh Lin for Collective Rule
  • +
+

Honor

+
    +
  • Sword By dsathiyaraj for Military Tradition
  • +
  • Castle By Gabriele Malaspina for Military Caste
  • +
  • Roman Armor By Parkjisun for Professional Army
  • +
  • Shield By Kimmi Studio for Discipline
  • +
  • Spartan Helmet By Joni Ramadhan for Warrior Code
  • +
+

Piety

+
    +
  • Protestantism By Evgeni Moryakov for Reformation
  • +
  • Temple By N.K.Narasimhan for Theocracy
  • +
  • Religion By Ben Avery for Free Religion
  • +
  • Flame By Ian Shoobridge for Mandate Of Heaven
  • +
+

Patronage

+
    +
  • Adapted from Gold by Aneeque Ahmed for Philantropy
  • +
  • Ornament by Tommy Suhartomo for Aesthetics
  • +
  • Book Gift by Wolf Böse for Scholasticism
  • +
  • agreement by RomanP for Cultural Diplomacy
  • +
  • professor by Andrew Doane for Educated Elite
  • +
+

Commerce

+
    +
  • Trade By Gregor Cresnar for Trade Unions
  • +
  • Pie Chart By Adrien Coquet for Protectionism
  • +
  • Coins By icon 54 for Mercantilism
  • +
  • Sextant By lastspark for Naval Tradition
  • +
  • captain by taamir468 + and Wheel by Andrejs Kirma for Merchant Navy
  • +
+

Rationalism

+
    +
  • Science By Three Six Five for Scientific Revolution
  • +
  • Graph By Ben Davis for Secularism
  • +
  • Logic By Jenya K for Sovereignty
  • +
  • Dialogue By ProSymbols for Free Thought
  • +
  • Logic By Arthur Shlain for Humanism
  • +
+

Freedom

+
    +
  • Social Network By Kirby Wu for Civil Society
  • +
  • Gender Equality By corpus delicti for Universal Suffrage
  • +
  • Feather By Eduardo Souza for Constitution and Open Policies button
  • +
  • Voting By Nikita Kozin for Democracy
  • +
  • Mic By Aybige for Free Speech
  • +
+

Autocracy

+ +

Order

+
    +
  • Adapted from Plan by Cattaleeya Thongsriphong for Planned Economy
  • +
  • Flag by Muhammad Tajudin for Nationalism
  • +
  • Communism By Valerio Poltrini for Socialism
  • +
  • Hammer and Sickle by Dmitry Baranovskiy for Communism
  • +
  • United by Izwar Muis for United Front
  • +
+

Technologies

+
    +
  • Lightbulb by Davo Sime for fallback image
  • +
+

Ancient

+
    +
  • Agriculture By OCHA Visual Information Unit
  • +
  • Jug By Vladimir Belochkin for Pottery
  • +
  • Archery By icon 54
  • +
  • Mining By art shop
  • +
  • Sailing By Daniela Baptista
  • +
  • Sundial By Bonegolem for Calendar
  • +
  • Cuneiform By Michael Wohlwend for Writing
  • +
  • Trap By Sergey Demushkin for Trapping
  • +
  • innovative By Matt Brooks for The Wheel
  • +
  • Bricks By Vaibhav Radhakrishnan for Construction
  • +
  • Mallet By Ben Avery for Bronze Working
  • +
+

Classical

+ +

Medieval

+ +

Renaissance

+ +

Industrial

+ +

Modern

+
    +
  • Gears By Aiden Icons for Replaceable Parts
  • +
  • Radio By Arthur Shlain
  • +
  • Piston By Proletkult Graphik for Combustion
  • +
  • Plastic By Yu luck
  • +
  • Microphone By Viktor Vorobyev for Mass Media
  • +
  • Flight By Genius Icons
  • +
  • Train By Federico Panzano for Railroad
  • +
  • Fridge By b farias for Refrigeration
  • +
  • telegraph by Luke Anthony Firth for Telegraph*
  • +
+

Atomic

+ +

Information

+ +

Future

+ +

Terrain

+ +

Nations

+ +

Promotions

+
    +
  • Sight By Jejen Juliansyah Nur Agung for Scouting
  • +
  • skill bow rain arrow by Maxicons for Barrage
  • +
  • Bomb By Angelo Troiano for Bombardment
  • +
  • Private E2 By Yeong Rong Kim for Shock
  • +
  • Private First Class By Yeong Rong Kim for Drill
  • +
  • Medic By Yohann Berger for Medic
  • +
  • City By iconcheese for Siege
  • +
  • Skull and Crossbones By Anton Outkine for Coastal Raider
  • +
  • Spear By Deemak Daksina for Formation
  • +
  • Wall By Graphic Tigers for Volley
  • +
  • Lightning By Mooms for Blitz
  • +
  • Move By Sandra for Mobility
  • +
  • accuracy By vigtographics for Targeting
  • +
  • location by Humantech for Accuracy
  • +
  • Plane By Tran Minh Villageois for Interception / Domain Air
  • +
  • Hammer by Thengakola for Air Repair
  • +
  • Hook By Yeong Rong Kim for Boarding Party
  • +
  • Leaf By Paul Verhulst for Indirect Fire
  • +
  • Move By Muneer A.Safiah for Logistics
  • +
  • Evasion By Anna Sophie for Evasion
  • +
  • Interrupt by Stephen Plaster for Ambush
  • +
  • Submarine by Lloyd Humphreys for Wolfpack
  • +
  • Axe by Brian Oppenlander for Woodsman
  • +
  • Competition by luca fruzza
  • +
  • Icon for Flight Deck is made by JackRainy
  • +
  • Icon for Armor Plating is made by JackRainy
  • +
  • Slingshot by James Keuning for Slinger Withdraw
  • +
  • Anchor by Gregor Cresnar for Amphibious / Domain Water
  • +
  • survival knife by b faris for Survivalism
  • +
  • Shamrock By P Thanga Vignesh for Pictish Courage
  • +
  • home sweet home By Silviu Ojog for Home Sweet Home
  • +
  • Star by Trent Kuhn for Dogfighting
  • +
+

Religions

+
    +
  • Lightning Bolt by sian huxtable for Pantheon
  • +
  • Christianity by Public Domain Nouns for Christianity
  • +
  • Islam by Muhammed Riza for Islam
  • +
  • taoism by parkjisun for Taosim
  • +
  • Buddhism by Julio Yanes for Buddhism
  • +
  • Hinduism by Mugda Damle for Hinduism
  • +
  • Confucianism by Dabid J. Pascual for Confucianism
  • +
  • Judaism by Dabid J. Pascual for Judaism
  • +
  • Shinto by Dabid J. Pascual for Shinto
  • +
  • Sikhism by Dabid J. Pascual for Sikhism
  • +
  • Tengrism by Dabid J. Pascual for Tengriism
  • +
  • Zoroastrianism by Dabid J. Pascual for Zoroastrianism
  • +
  • praying by parkjisun for Religion (Civilopedia concept entry)
  • +
  • praying by Gan Khoon Lay for Follower
  • +
  • Hero by Andrew J. Young for Founder
  • +
  • yell by Adrien Coquet for Enhancer
  • +
+

Others

+
    +
  • Circle By Aybige - Circle used to compose icons on the fly
  • +
  • Link By Brad for Civilopedia links and unit escort formation. The original work has been slightly modified.
  • +
  • Arrow By Joe Mortell for movement
  • +
  • Swap By iconomania for swapping units
  • +
  • Road By Gábor István Karaba for connect road automation
  • +
  • Connection By Travis Avery
  • +
  • Skull By Vladimir Belochkin for disbanding units
  • +
  • Crosshair By Bakunetsu Kaito for selecting enemies to attack
  • +
  • City By Felix Westphal
  • +
  • Fire By Lloyd Humphreys for "city being razed" icon
  • +
  • Sleep By Saeful Muslim for unit "sleep" action and status
  • +
  • Clockwise By Universal Icons (Louis Dawson) for "Wait" icon. The original work has been slightly modified.
  • +
  • Banner By Emir Palavan for embarked units
  • +
  • Arrow By uzeir syarief for moving between idle units, expanders, etc.
  • +
  • Exchange By Mike Rowe for switching tiles between cities
  • +
  • Revolution By HeadsOfBirds for cities in resistance - world screen, city screen, city overview
  • +
  • Viking Hat By my name is mud for pillaging improvements
  • +
  • Aim By Kaviashri for ranged strength
  • +
  • Capitol By Loren Klein for City-States
  • +
  • Aircraft By Tom Fricker for aircraft icon in city button
  • +
  • radar scan By icon 54 for Range
  • +
  • short range radar by Vectors Point for Intercept range
  • +
  • Puppet by vegeta1k95 based on Puppet by Ben Davis, for puppeted cities
  • +
  • City By Muhajir ila Robbi in the Icon center
  • +
  • Lock by Vadim Solomakhin for locked tiles
  • +
  • Hourglass by I Create Stuff for the 'Turn' icon
  • +
  • Shield by Gregor Cresnar for Religious Strength
  • +
  • skill sword flame by Maxicons) for Remove Heresy
  • +
  • Pencil by Muhamad Aldi Maulana for Enter Text Prompt Button / Pencil
  • +
  • Parchment by hans draiman for Cultured City-States
  • +
  • connection by Popular for Mercantile City-States
  • +
  • crossed sword by ProSymbols for Militaristic City-States
  • +
  • ship helm by Vectors Market for Maritime City-States
  • +
  • Magnifying Glass by John Caserta for Mod filter
  • +
  • tick by Adrien Coquet on Nation picker
  • +
  • people by Wilson Joseph as base for Civilopedia category Nations
  • +
  • Mountains by Andrew J. Young as base for Civilopedia category Terrains / Domain Land
  • +
  • Civilopedia category UnitTypes icon done by SomeTroglodyte from sources cited for Interception, Cavalry and Longbowman
  • +
  • File:Maya.svg for Mayan numerals
  • +
  • East side of stela C, Quirigua for Mayan calendar symbols
  • +
  • Footprints by Abdul Wahhab for movement overlay toggle, slightly modified. Currently unused.
  • +
  • Arrows.svg by Intralexical (@will-ca), CC0.
  • +
  • favor by MICHAEL G BROWN for WLTK marker on City Overview
  • +
  • WLTKD by vegeta1k95 based on favor by MICHAEL G BROWN for WLTKD status icon
  • +
  • Party by Adrien Coquet for WLTK header on City Overview
  • +
  • Party by Lars Meiertoberens as additional WLKT decoration
  • +
  • spy by Vectorstall for Spy and the Espionage button
  • +
  • secret agency by Martina Hanžlová - Espionage tutorial
  • +
  • turn right by Alice Design for Resource Overview
  • +
  • Tyrannosaurus Rex by Amethyst Studio for Civilopedia Eras header
  • +
  • Realistic easter day eggs with curvy lines and dots by freepik for easter eggs
  • +
  • Eggs Easter Scrapbook by Claudette Gallant for easter eggs
  • +
  • Halloween pumpkin by freepik for easter eggs
  • +
  • Vector set of different sweets by macrovector for easter eggs
  • +
  • Part of Merry christmas design elements set by vikayatskina for easter eggs
  • +
  • Part of 3d winter holidays background rawpixel.com for easter eggs
  • +
  • Stack of dollar bills with bow by katemangostar for easter eggs
  • +
  • present boxes collection by nikapeshkov for easter eggs
  • +
  • Reindeer in santa hat peeping out by katemangostar for easter eggs
  • +
  • Positive santa ringing bell by katemangostar for easter eggs
  • +
  • Snowman wearing santa hat scarf and mittens by katemangostar for easter eggs
  • +
  • AirSweep by Creative Stall for Air Sweep icon
  • +
  • Timer created by Gregor Cresnar Premium - Flaticon - for Game speeds civilopedia category
  • +
  • Political Science created by Hilmy Abiyyu A. - Flaticon - for Global politics overview screen
  • +
  • Question created by Aneeque Ahmed for Question Icon
  • +
  • Trade created by Smashicons for Conduct Trade Mission
  • +
  • Nothing created by Freepik for Nothing construction process
  • +
  • Icon for Unique created by vegeta1k95
  • +
  • [Transform] created by letstalkaboutdune for the Transform unit action
  • +
  • Swords created by Muhajir ila Robbi for Blockaded tile marker
  • +
  • Keyboard by Twenty Foo Studio for Options Keys
  • +
  • charts by Srinivas Agra (gimped to appear bolder) for the Charts page
  • +
  • framed image by Jose Dean for Victory Illustrations page
  • +
  • down by Cengiz SARI for Show unit destination
  • +
  • Cat by Josi for Politics overview diagram legend
  • +
  • Bell by Lyhn, transparency modified, for Notifications (overview, unhide button)
  • +
  • Galileo Donatello for the "Meet another civilization" tutorial: Public domain
  • +
  • ServerRack by Chad Remsing
  • +
  • [RobotArm] (https://thenounproject.com/icon/robot-7300306/) by Faizal khusein
  • +
  • [RobotHeadSquare] (https://thenounproject.com/icon/robot-head-6356754/) by Ahmad Arzaha
  • +
  • [RobotHeadRect] (https://thenounproject.com/icon/robot-1704106/) by suib icon
  • +
  • [Bobot] by reallybasicname in the Discord
  • +
  • [DollarSign] (https://thenounproject.com/icon/dollar-sign-6466560/) by Ahmad Arzaha
  • +
+ + +

Sound credits

+

Sounds are from FreeSound.org unless otherwise noted and are either Creative Commons or Public Domain unless otherwise noted

+ +

Music

+

The following music is from https://filmmusic.io +"Thatched Villagers" by Kevin MacLeod (https://incompetech.com)

+

Trailer audio

+

The following audio is from https://pixabay.com/ Pixabay License +- Beyond New Horizons - Free Epic Viking Medieval Soundtrack by GioeleFazzeri for background music +- Beep 6 by Eponn for beep in Cross-Platform Play section +- Cinematic Boom by Rizzard for final boom +- Cymbal Swell 2 by rubberduckie for cymbal swells +- hit of orchestral cymbals and bass drum by Selector for intro crash

+

Visual effects

+

The fireworks on the City Screen of a WLTK-celebrating city are loosely based on the Fireworks.p file included in Particle Park. +All differences and edits done by the Unciv team. +License quoted: +

Particle Park Fireworks License
+
+------------------------------------------------------------------------------------------
+
+Copyright © 2019 Raymond Buckley
+
+Particle Park Fireworks can be used under the Creative Commons Attribution 4.0 International license.
+
+See a human readable version here: https://creativecommons.org/licenses/by/4.0/
+
+------------------------------------------------------------------------------------------
+

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/Building-Locally/index.html b/Developers/Building-Locally/index.html new file mode 100644 index 0000000000..875053efaf --- /dev/null +++ b/Developers/Building-Locally/index.html @@ -0,0 +1,1660 @@ + + + + + + + + + + + + + + + + + + + + + + + Building Locally - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Building Locally

+

By the end of this guide, you will have Unciv running locally from code, so you can make changes and test them locally.

+

With Android Studio

+
    +
  • Install Android Studio - it's free and awesome! Be aware that it's a long download!
  • +
  • Install Git, it's the way for us to work together on this project. UI is optional, Android Studio has good Git tools built in :)
  • +
  • Getting the code
      +
    • Create a Github account, if you don't already have one
    • +
    • Fork the repo - this will create a "copy" of the code on your account, at https://github.com/<YourUsername>/Unciv
    • +
    +
  • +
  • Load the project in Android Studio
      +
    • File -> New -> Project from Version Control -> GitHub
    • +
    • Enter your GitHub username and password
    • +
    • Select the repository and hit clone - The GitHub repo will be created as a new project in Android Studio.
    • +
    +
  • +
  • Gradle will attempt the initial sync. If this is your first time with Android Studio, this may require you to accept the Android Build-tools licenses, which works differently on every device, so search for your OS-specific solution.
      +
    • A new install may not be able to do the initial sync - this comes in the form of Unable to find method ''void org.apache.commons.compress.archivers.zip.ZipFile.<init>(java.nio.channels.SeekableByteChannel)'' errors when you try to sync. If you have this problem go into File > Settings > Languages & Frameworks > Android SDK
        +
      • Click "SDK Platforms"
      • +
      • Click "Android 14 ("UpsideDownCake")" +image + (Optionally, you can save some space by selecting 'Show Package Details' and choosing the Platform SDK only, without Sources or system Images)
      • +
      • Click "SDK Tools"
      • +
      • Select "Show Package Details" in the bottom right
      • +
      • Choose version 34.0.0 under "Android SDK Build-Tools"
      • +
      • Click "Apply"
      • +
      • Restart Android Studio
      • +
      +
    • +
    +
  • +
  • In Android Studio, Run > Edit configurations (be sure the Gradle sync is finished successfully first).
      +
    • Click "+" to add a new configuration
    • +
    • Choose "Application"
    • +
    • Give the configuration a name, we recommend "Desktop"
    • +
    • Set the module classpath (the box to the right of the Java selection) to Unciv.desktop.main (Unciv.desktop for Bumblebee or below), main class to com.unciv.app.desktop.DesktopLauncher and $ProjectFileDir$/android/assets as the Working directory, OK to close the window
        +
      • It may be useful to set some VM options - activate the field in the run config editor with Alt-V or via the Modify Options menu, then add -Xmx4096m -Xms256m -XX:MaxMetaspaceSize=256m to allow a debugged game a little more memory. Or, use the -DnoLog= or -DonlyLog= options to control console logging. See the Log.kt comments for details.
      • +
      • If you get a ../../docs/uniques.md (No such file or directory) error that means you forgot to set the working directory! +image
      • +
      +
    • +
    +
  • +
  • Select the Desktop configuration (or however you chose to name it) and click the green arrow button to run! Or you can use the next button -the green critter with six legs and two feelers - to start debugging.
  • +
  • A few Android Studio settings that are recommended:
      +
    • Going to Settings > Version Control > Commit and turning off 'Before Commit - Analyze code'
    • +
    • Settings > Editor > Code Style > Kotlin > Tabs and Indents > Continuation Indent: 4 +image
    • +
    • Settings > Editor > General > On Save > Uncheck Remove trailing spaces on: [...] to prevent it from removing necessary trailing whitespace in template.properties for translation files +image
    • +
    • Right-click the android/assets/SaveFiles folder, "Mark directory as" > Excluded
    • +
    • If you download mods do the same for the android/assets/mods folder and any other files you may create while testing that do not belong in the public project.
    • +
    • This disables indexing for performance.
    • +
    +
  • +
+

Unciv uses Gradle to specify dependencies and how to run. In the background, the Gradle gnomes will be off fetching the packages (a one-time effort) and, once that's done, will build the project!

+

Unciv uses Gradle 8.7 and the Android Gradle Plugin 8.5. Can check in File > Project Structure > Project

+
+

Note: advanced build commands (as described in the next paragraph), specifically gradlew desktop:dist to build a jar, run just fine in Android Studio's terminal (Alt+F12), with most dependencies already taken care of.

+
+

Without Android Studio

+
    +
  • Ensure you have JDK 11 or higher installed
  • +
  • Clone the project (see above initial steps)
  • +
  • Open a terminal in the Unciv folder and run the following commands
  • +
+

Windows

+
    +
  • Running: gradlew desktop:run
  • +
  • Building: gradlew desktop:dist
  • +
+

Linux/Mac OS

+
    +
  • Running: ./gradlew desktop:run
  • +
  • Building: ./gradlew desktop:dist
  • +
+

If the terminal returns Permission denied or Command not found on Mac/Linux, run chmod +x ./gradlew first. This is a one-time procedure.

+

If you get an error that Android SDK folder wasn't found, install it by running:

+

sudo apt update && sudo apt install android-sdk (Debian, Ubuntu, Mint etc.)

+

Then, set the SDK location in the local.properties file by adding:

+

sdk.dir = /path/to/android/sdk - for example, /usr/lib/android-sdk

+

If during initial launch you get an error that the JDK version is wrong, install the JDK from here.

+
+

Note: Gradle may take up to several minutes to download files +After building, the output .JAR file should be in /desktop/build/libs/Unciv.jar

+
+

For actual development, you'll probably need to download Android Studio and build it yourself - see above :)

+

Debugging on Android

+

Sometimes, checking things out on the desktop version is not enough and you need to debug Unciv running on an Android device. +For an introduction, see Testing android builds.

+

Next steps

+

Congratulations! Unciv should now be running on your computer! Now we can start changing some code, and later we'll see how your changes make it into the main repository!

+

Now would be a good time to get to know the project in general at the Project Structure overview!

+

Unit Tests

+

You can (and in some cases should) run and even debug the unit tests locally.

+
    +
  • In Android Studio, Run > Edit configurations.
      +
    • Click "+" to add a new configuration
    • +
    • Choose "Gradle" and name the config, e.g. "Unit Tests"
    • +
    • Under "Gradle Project", choose "Unciv" from the dropdown (or type it), set "Tasks" to :tests:test and "Arguments" to --tests "com.unciv.*", OK to close the window.
    • +
    +
  • +
  • Select the "Unit Tests" configuration and click the green arrow button to run! Or start a debug session as above.
  • +
+

Linting

+

Detekt checks for code smells and other linting issues. +To generate Detekt reports:

+
    +
  • Download detekt-cli (the zip file) and unzip it
  • +
  • Open a terminal in the Unciv root directory and run one of the following commands to generate the report. NOTE: If you're using Windows, replace detekt-cli with detekt-cli.bat.
      +
    • For warnings: PATH/TO/DETEKT/detekt-cli --parallel --report html:detekt/reports.html --config .github/workflows/detekt_config/detekt-warnings.yml
    • +
    • For errors: PATH/TO/DETEKT/detekt-cli --parallel --report html:detekt/reports.html --config .github/workflows/detekt_config/detekt-errors.yml
    • +
    +
  • +
  • The report will be generated in detekt/reports.html
  • +
+

UncivServer

+

The simple multiplayer host included in the sources can be set up to debug or run analogously to the main game: +- In Android Studio, Run > Edit configurations. + - Click "+" to add a new configuration + - Choose "Application" and name the config, e.g. "UncivServer" + - Set the module to Unciv.server.main (Unciv.server for Studio versions Bumblebee or below), main class to com.unciv.app.server.UncivServer and <repo_folder>/android/assets/ as the Working directory, OK to close the window. +- Select the UncivServer configuration and click the green arrow button to run! Or start a debug session as above.

+

To build a jar file, refer to Without Android Studio and replace 'desktop' with 'server'. That is, run ./gradlew server:dist and when it's done look for /server/build/libs/UncivServer.jar

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/Coding-standards/index.html b/Developers/Coding-standards/index.html new file mode 100644 index 0000000000..a78455c300 --- /dev/null +++ b/Developers/Coding-standards/index.html @@ -0,0 +1,1411 @@ + + + + + + + + + + + + + + + + + + + + + + + Coding standards - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Coding standards

+

As an open-source project, there will be a lot of eyes on our code.

+

The main purpose of having a coding standard is for the code to be as immediately readable as possible to as many potential contributors, and hence most of it focuses on defaulting to coding structures that exist in other similar languages (Java, C#) when possible.

+

Don't use .let{} and ?:

+

Kotlin is made greater for being strict with nullability. Don't let this fact confuse people new to it. These can be simply replaced by if(x!=null) which is much more readable. They all probably compile to the same bytecode anyway, so when in doubt - readability.

+

for(item in list) and not list.forEach{}

+

For loops go waaaay back, forEach doesn't. As an added bonus, I'm pretty sure that because forEach accepts a function parameter, then when debugging it won't automatically step into these lines, unlike for.

+

Avoid premature abstraction

+

There's no need to create an interface if there is only one implementation of that interface. Doing so obfuscates the actual code that's running and increases the Time To Relevant Code. If abstraction becomes necessary later, we can always do it later.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/From-code-to-deployment/index.html b/Developers/From-code-to-deployment/index.html new file mode 100644 index 0000000000..1606d0c8c3 --- /dev/null +++ b/Developers/From-code-to-deployment/index.html @@ -0,0 +1,1526 @@ + + + + + + + + + + + + + + + + + + + + + + + From code to deployment - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

From code to deployment

+

So, your code works! You've solved all the bugs and now you just need to get it out to everyone!

+

So, how does THAT work?

+

The process has two major parts, one is "Getting your code in the main repository" and the other is "Deploying versions" - as a developer, you'll be taking an active part in the first process, but the second process is on me =)

+

Getting your code in the main repo

+
    +
  • First off, push your changes with Git to your own branch at https://github.com/YourUsername/Unciv.git. I hope you've been doing this during development too, but that's none of my business *sips tea*
  • +
  • Issue a pull request from https://github.com/YourUsername/Unciv - from the Pull Requests is the simplest
  • +
  • The Travis build will check that your proposed change builds properly and passes all tests
  • +
  • I'll go over your pull request and will ask questions and request changes - this is not only for code quality and standard, it's mostly so you can learn how the repo works for the next change you make =)
  • +
  • When everything looks good, I'll merge your code in and it'll enter the next release!
  • +
+

Deploying versions

+

When I'm ready to release a new version I:

+
    +
  • Comment "prepare version" in one of the open PRs tagged as 'mergeable translation' to trigger the translation branch creation.
  • +
  • This also bumps the versions in the buildConfig.kt file, and generates a starting version summary in changelog.md.
  • +
  • Merge the PR and delete the branch (so next version branch starts fresh)
  • +
  • From my workstation - pull the latest changes and run the translation generation
  • +
  • Edits to the autogenerated version notes in changelog.md to make them less than 500 chars (needed for Google Play) and more user-readable
  • +
  • Add a tag to the commit of the version. When the Github action sees that we've added a tag, it will run a build, and this time (because of the configuration we put in the yml file file), it will:
      +
    • Pack a .jar file, which will work for every operating system with Java
    • +
    • Use Linux and Windows JDKs to create standalone zips for 32 and 64 bit systems, because we can't rely on the fact that users will have a JRE
    • +
    • Download Butler and use it to push the new versions to the itch.io page
    • +
    • Read the changelog.md file to get the changes for the latest version
    • +
    • Upload all of these files to a new release on Github, with the release notes, which will get added to the Releases page
    • +
    • Send an announcement on the Discord server of the version release and release notes via webhook
    • +
    • Pack, Sign, and Upload a new APK to the Google Play Console at 10% rollout
    • +
    +
  • +
  • The F-Droid bot checks periodically if we added a new tag. When it recognizes that we did, it will update the yaml file here
      +
    • When the bot next runs and sees that there's a version it doesn't have a release for, it will attempt to build the new release. The log of the build will be added here (search for Unciv, there's a log link like this: https://monitor.f-droid.org/builds/log/com.unciv.app/<appCodeNumber>), and the new release will eventually be available here
    • +
    +
  • +
+

Deploying a release patch

+

In case a fix is urgent because a bug in the last release severely affects a large number of players, any Collaborator (next step up from Contributor) is able to trigger a patch release.

+
    +
  • In the PR that fixes the game-breaking bug, instead of merging, add a comment with "release patch" as body.
  • +
  • Wait for the workflow (UncivBot) to do its thing (should be less than a minute - or watch from the "Actions" tab of the repo) and check.
  • +
+

About Google Play publishing

+

We start at a 10% rollout, after a day with no major problems go to 30%, and after another day to 100%. If you were counting that means that most players will get the new version after 2+ days.

+

If there were problems, we halt the current rollout, fix the problems, and release a patch version, which starts at 10% again.

+

Dear future me - the automation was extremely annoying guesswork to set up, so the facts you need to know are:

+
    +
  • There is a user at the Google Cloud Platform Account Manager called Unciv_Upload_Account. There is an access key to this account, in json, stored as the Github secret GOOGLE_PLAY_SERVICE_ACCOUNT_JSON.
  • +
  • This user was granted ADMIN permissions to the Google Play (after much trial and error since nothing else seemed to work) under User > Users and Permissions. Under Manage > Account permissions, you can see that it has Admin.
  • +
+

Updating the wiki

+

Pages for the Unciv Github Wiki are kept in the main repository under docs.

+

The process to edit the wiki is as follows:

+
    +
  1. Open a pull request in the main Unciv repository that changes files under docs.
  2. +
  3. Once the pull request is merged, an account with commit privileges on the Unciv repository leaves a comment saying "update wiki".
  4. +
  5. This comment triggers a bot to copy all the wiki files from the main repository into the Github wiki, with a link back to the PR in its commit message for credit.
  6. +
+

Doing things this way has several distinct advantages over using the Github Wiki web interface directly:

+
    +
  • Changes can be proposed via PR and proofread or fact-checked.
  • +
  • A proper MarkDown editor or IDE can be used to write the wiki, bringing faster editing, clickable links while editing, better live HTML preview, and automatic detection of problems like broken links.
  • +
  • The wiki files can also be browsed at https://github.com/yairm210/Unciv/tree/master/docs/wiki.
  • +
  • Auto-generated documentation made by the build process can be placed directly in the wiki.
  • +
+

However, it also imposes a couple of conventions about how links should best be formatted:

+ + + + + + + + + + + + + + + + + + + + +
Link typeFormatExample
Inter-wikiShould begin with "./", and include ".md"../Mods.md#other
Code or asset fileShould begin with "https://github.com/yairm210/Unciv/blob/master/", and be relative to the project root.https://github.com/yairm210/Unciv/blob/master/android/assets/game.png
+

These formats will allow IDEs like Android studio to resolve these links and check for broken links, while also working on the Github code browser.

+

The bot that updates the wiki from the main repository automatically translates them into formats that are compatible with Github Wikis, which have somewhat non-standard requirements.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/Game-Making-Tips/index.html b/Developers/Game-Making-Tips/index.html new file mode 100644 index 0000000000..a84e83a712 --- /dev/null +++ b/Developers/Game-Making-Tips/index.html @@ -0,0 +1,1760 @@ + + + + + + + + + + + + + + + + + + + + + + + Game Making Tips - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Game Making Tips

+

Tips and tricks for making a LibGDX game

+

Here are a bunch of things I've learned from by brief excursion into the world of game making.

+

Some of our will be obvious to you, some will not.

+

Use Kotlin

+

Unciv started its life as a Unity project in C#, was shifted to Java and LibGDX, and finally to Kotlin.

+

I regret every minute that I spent writing events in Java, this is probably the most significant change that your application could see.

+

Use Scene2d

+

Unless you plan on creating images on the fly, you'll probably be using prerendered assets.

+

Placing them manually is akin to manually positioning html tags, instead of using html hierarchy and css to guide positions.

+

So too is Scene2d - as a placement framework. it's relatively simple to understand, especially when you...

+

Ignore Horizontal and Vertical groups - use Table

+

I personally found that table has all the functionality of the above, and more.

+

Each class has a different syntax too, so I found it much simpler to just stick with Table for everything.

+

Table does just about EVERYTHING! It's insanely amazing!

+

If your game is getting slow, use the Android profiler in Android Studio

+

The top-down CPU chart is the best code profiler I've ever seen, use it to your advantage!

+

Cache everything

+

Caching is a trade-off between purer, state-agnostic code and higher performance. +Coming from a PC background, I automatically assume that anything less than O(n^2) is less than a millisecond and therefore, not a caching candidate. +This is not so in mobile development.

+

This becomes especially relevant when you need to save and load game data which has lots of connected parts - you have to avoid circular references, and you want to minimise the save size, but you need to reconstruct the missing links when loading.

+

Minimize String operations

+

All the tip and tricks you've heard to minimize String operations? Use them!

+

String constants should be consts, use StringBuilders (or just ArrayLists of strings that you later .joinToString())

+

Sequences everywhere!

+

One thing I did not expect to be such an issue is intermediate lists when sorting and mapping.

+

But apparently, the memory allocation for these tasks is Serious Business.

+

So whenever possible, take your list and .asSequence() it before activating list operations - this results in huge savings of both time and memory!

+

The only time you shouldn't be doing this, though, is when you want to cache the specific values for future use - sequences will go through the whole process every time you iterate on them, so just .toList() them when you've gotten the final results!

+

General tips for making an Open Source game

+

Lower the entry bar - for both programmers and players

+

I think that most Open Source games suffer from this problem - those that are in are way in, but those that are out and want to join have to learn the ecosystem.

+

Documentation is a big issue here, but so are detailed instructions - and I mean "Spoonfeeding".

+

Treat new developers as if they've never used Git before - it's possible they haven't!

+

Explain how to download the sourecode, the tools, how to get the game running locally, how to make changes and how to submit them.

+

Same think with new players - getting the game up and running should be AS SIMPLE AS HUMANLY POSSIBLE - you want people to play your game, don't you?

+

This includes:

+
    +
  • Source-To-Executable automation - I use Travis
  • +
  • Play stores and the like
  • +
  • Internal game tutorials - your players will NEVER BE SATISFIED with this last point, but at least do what you candidate
  • +
+

Community, Community, Community!

+

I, personally, underestimated this point for about a year after launch.

+

I communicated with players through the Google Play Store and Github issues, and that seemed to be enough.

+

It was only after repeated urgings from players that I opened a Discord server - and that gradually lead to a massive change!

+

You see, it's not ABOUT programmer-to-player interaction. There will always be a small number of core devs relative to the large playerbase.

+

The key to the community is the player-to-player interaction. Explaining things, questions, ideas, things that players bounce off each other, +not only make the amorphous community a better place, but actually lead to a better game!

+

Another think to remember is that there's a larger community around you - the Open Source community, the Linux community, etc.

+

There are lots of people who will play your game only because it's open source, and it also means they don't have as many options.

+

For example...

+
    +
  • Being the best 4X game means competing with the biggest names out there
  • +
  • Being the best 4X game for Linux means many less competitors, but All The Cool Kids (tm) are multiplatforming nowadays so you're still outperformed.
  • +
  • Being the best Open Source 4X game means about 5 competitors, and no money is involved either so the average entry is not as polished.
  • +
  • Being the best Open Source 4X game for Android... means having so few competitors that it's totally doable.
  • +
+

Everything is marketing.

+

Your game's name, the icon, screenshots, everything a player sees about your game is marketing.

+

Icons and bylines are especially important, since they're the first things your players will probably see.

+

I saw an almost 50% (!) by changing the icon, after several experiments, which Google Play lets you conduct very easily.

+

Translations are part of your source code

+

This may be slightly controversial, so I'll explain.

+

We went though a number of iterations regarding how to save translations until we arrived at the current format.

+

The important parts are:

+
    +
  • +

    Game translation files should be AUTO GENERATED. This allows you to add new objects into the game with impunity, + knowing that corresponding lines will be auto-added to the translations.

    +
  • +
  • +

    Translations for each language should be stored separately - this allows concurrent modification of several independent languages with no risk of conflict

    +
  • +
  • +

    Translations should be PR'd in! This allows other speakers to question or change the proposed translations, and allows you to run tests on your translations. + If you require a specific format, this is invaluable as it means that bad translations will be rejected at the door.

    +
  • +
+

Open source problems require open (source?) solutions

+

TL;DR, consider using APIs that are free, even if they're not Open Source.

+

Multiplayer requires syncing game files between clients, even when one of them is not currently online.

+

The 'correct' way to solve this would probably be to have an online DB and a service which handles user requests.

+

Since this is an Open Source game, I'm working on a 0$ budget, so we just store all the files in Dropbox and upload/download there.

+

Is this secure? No, but does it need to be? You need to think of the cost vs the value.

+

Same thing with Mods. Steam is big and secure so it handles its mods itself.

+

We are small and open, so we just allow to download from Github, which lets us use all of Github's built in functions (user management, readmes, stars, versioning...) at no extra cost.

+

And unlike the Dropbox usage, which is basically abuse, Github is built for this kind of thing! +This is exactly the kind of use case they were thinking of to start with!

+

The Reckoning

+

There comes a time in every project where the cool stuff is done. All the cutting-edge awesomeness and algorithmic playdough is done, and now all (hah) it needs is polish.

+

You know who loves polish? Players! Sure, there are some that say "a good game is good even if it's basic" but they have standards for what a basic game should have as well.

+

And the numbers don't lie. Polished games sell themselves better, and so are played more.

+

You know who doesn't love polish? DEVELOPERS.

+

When your game is relatively simple, then the options for polish are more limited, but the more complex the game, the more polish-venues there are.

+

And it can be an ABSOLUTE GRIND. Another weird use-case, another ingame option, "better performance" (I must have spent dozens of hours on different performance related actions)

+

And the worst thing is, that everyone notices when it's missing, but no one notices when it's there. A hundred versions of polish - literally - and the average player may notice only a slight change.

+

And then comes the moment when you ask yourself, why bother? What are we even doing here?

+

For me, the answers are as follows:

+

A. To build something truly great, you have to keep going way beyond when it stops being fun.

+

B. There's a community of people that like what you're doing and want there to be more of it :)

+

C. You know you want to keep coding, and what, you think you're going to start another project and it'll work out as well? You've tried that multiple times, and let's face it the chance of you making a second game that goes so well is really small unless you invest in it as much time as you have in this, and yeah, then you'll be back in this position again.

+

And that's basically the loop I've been in for the last hundred versions or so! Solve bugs, fix edge cases, improve AI, accept PRs. Lots of mod-related changes, both to stop the game breaking when people do things in mods that they shouldn't and to allow them more freedom in making them.

+

I don't think I'll ever really continue to finish G&K, I'm DEFINITELY not planning on implementing BNW mechanics which frankly I think are...not great.

+

That's where I am right now. Kind of done with the game, but considering that I thought that half a year ago and releases are still releasing roughly every week, also kind of not.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/Map-rendering/index.html b/Developers/Map-rendering/index.html new file mode 100644 index 0000000000..318f28583d --- /dev/null +++ b/Developers/Map-rendering/index.html @@ -0,0 +1,1425 @@ + + + + + + + + + + + + + + + + + + + + + + + Map rendering - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Map rendering

+

Introduction - how does LibGDX render images?

+

Images in LibGDX are displayed on screen by a SpriteBatch, which uses GL to bind textures to load them in-memory, and can then very quickly display them on-screen. +The actually rendering is then very fast, but the binding process is slow. +Therefore, ideally we'd want as little bindings as possible, so the textures should contain as many images as possible. +This is why we compile images (ImagePacker.packImages()) into large PNGs.

+

However, due to limitations in different chipsets etc, these images are limited to a maximum size of 2048*2048 pixels, and the game contains more images than would fit into a single square of that size. +What we do, then, is separate them by category, and thus rendering proximity. +The 'android' folder contains Images, but also various sub-categories - Images.Flags, Images.Tech, etc. +Each of these sub-categories is compiled into a separate PNG file in the 'android/assets' folder.

+

When rendering, the major time-sink is in rebinding textures. We therefore need to be careful to minimize the number of -rebinds, or 'swapping between different categories'.

+

Layering

+

Each map tile is comprised of several layers, and each layer needs to be rendered for all tiles before the next layer is. +For example, we don't want one tile's unit sprite to be overlayed by another's improvement. +This layering is done in TileGroupMap, where we take the individual parts for all tiles, separate them into the layers, and add them all to one big group. +This also has a performance advantage, since e.g. text and contruction images in the various city buttons are not rendered until the very end, and therefore swap per the number of of cities and not for every single tile. +This also means that mods which add their own tilesets or unit sprites have better performance than 'render entire tile; would provide, since we first render all terrains, then all improvements, etc, +so if my tileset provides all terrains, it won't be swapped out until we're done.

+

Debugging

+

Android Studio's built-in profiler has a CPU profiler which is perfect for this. +Boot up the game on your Android device, open a game, start recording CPU, move the screen around a bit, and stop recording. +Select the "GL Thread" from the list of threads, and change visualization to a flame graph. You'll then see what's actually taking rendering time.

+

You can find various games to test on here - This for example is a crowded one.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/Project-structure-and-major-classes/index.html b/Developers/Project-structure-and-major-classes/index.html new file mode 100644 index 0000000000..b30ee14047 --- /dev/null +++ b/Developers/Project-structure-and-major-classes/index.html @@ -0,0 +1,1804 @@ + + + + + + + + + + + + + + + + + + + + + + + Project structure - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Project structure

+

Since LibGDX, and therefore Unciv, are built for multi-platform support, the project structure is built accordingly.

+

99% of the code is in the core project, which contains all the platform-independent code.

+

The desktop and android folders contain platform-specific things, and the Android folder also contains the game Images and the all-important Assets, which are required for running from Desktop as well, so we bundle them up into the .jar file when releasing.

+

The tests folder contains tests that can be run manually via gradle with ./gradlew tests:test, and are run automatically by Travis for every push.

+

The server folder contains the sources for the UncivServer (a host enabling communication between multiplayer game instances), which is packaged into its own separate jar.

+

Translations

+

Before we get to the Classes, a word on Languages. Unciv is playable in several handfuls of languages, and there's magic to support that. Whenever you include a new string in code you will need to give it a quick evaluation - will users see it, and if so, what do I need to do to support its translations. Sometimes you may not need to do anything, sometimes you will add a line to the translation templates, and sometimes you will adapt the string formatting to support the translations. For details, see the 'Translation generation - for developers' chapter.

+

Major classes

+

Civ, and therefore Unciv, is a game with endless interconnectivity - everything affects everything else.

+

In order to have some semblance of order, we'll go over the main classes in the order in which they are serialized.

+

So yes, you can - for instance - get the center tile of a city, a TileInfo, directly from CityInfo. But delving into all the connections would only harm the point of this overview, that's what the actual code is for ;)

+

The Game State:

+
    +
  • GameInfo
      +
    • CivilizationInfo
        +
      • CityInfo
      • +
      +
    • +
    • TileMap
        +
      • TileInfo
          +
        • MapUnit
        • +
        +
      • +
      +
    • +
    • RuleSet (unique in that it is not part of the game state)
    • +
    +
  • +
+

The UI:

+
    +
  • MainMenuScreen
  • +
  • NewGameScreen
  • +
  • WorldScreen
  • +
  • CityScreen
  • +
  • MapEditorScreen
  • +
  • Picker Screens - TechPickerScreen, PolicyPickerScreen, ImprovementPickerScreen, PromotionPickerScreen
  • +
+

Game State

+

The Game - GameInfo

+

First off, let's clarify: When we say "The Game", we mean the state of the game (what turn it is, who the players are, what each one has etc) and not the UI of the game.

+

That is, The Game is the currently played game, not Unciv.

+

The game contains three major parts:

+
    +
  • The list of the players, or civilizations - List<CivilizationInfo>
  • +
  • The map upon which the game is played - TileMap
  • +
  • The ruleset by which the game is played - RuleSet. This includes what technologies, buildings, units etc. are available, and IS NOT serialized and deserialized, but comes straight from the game files - more on that later.
  • +
  • Parameters unique to this game - difficulty, game speed, victory conditions, etc.
  • +
+

When we save the game, or load the game, we're actually serializing and deserializing this class, which means that the this class is the root of the entire game state.

+

Most objects in the "state tree" have a transient reference to their parent, meaning the tree can be traversed in-code in all directions, and frequently is.

+

A Civilization - CivilizationInfo

+

This represents one of the players of the game, and NOT a specific nation - meaning, not France, but rather "Player X who is France in this game". In another game, there will be another France.

+

As one of the focal points of the game, it contains a lot of important information, the most important of which are:

+
    +
  • The list of cities the civilization has - List<CityInfo>
  • +
  • Which nation this is - references a certain Nation (part of the ruleset)
  • +
  • Various Managers for the different aspects of the civilization - PolicyManager, GoldenAgeManager, GreatPersonManager, TechManager, VictoryManager, DiplomacyManager
  • +
+

A City - CityInfo

+

This contains the information about a specific city.

+

Beyond basic information like name, location on map etc, the most important classes it contains are:

+
    +
  • Calculating the yield of the city - CityStats
  • +
  • Managers for the various aspects - PopulationManager, CityConstructions, CityExpansionManager
  • +
  • The tiles controlled and worked by the city - only their locations are permanently saved in the CityInfo, the actual information is in the TileInfo in the TileMap
  • +
+

The map - TileMap

+

This contains mostly helper functions and acts as a wrapper for the list of tiles it contains

+

A tile - TileInfo

+

Each tile is comprised of several layers, and so has information for each.

+

Tiles have, primarily:

+
    +
  • A base terrain - Grassland, Hills, Desert etc. References a certain Terrain (part of the ruleset)
  • +
  • An optional terrain feature - Forest, Jungle, Oasis etc. References a certain Terrain (part of the ruleset)
  • +
  • An optional resource - Iron, Dye, Wheat etc. References a certain TileResource (part of the ruleset)
  • +
  • An improvement built on the tile, if any. References a certain TileImprovement (part of the ruleset)
  • +
  • The units that are currently in the tile - MapUnit
  • +
+

A unit on the map - MapUnit

+

Unlike buildings, Unit in Unciv has two meanings. One is a Type of unit (like Spearman), and one is a specific instance of a unit (say, a Babylonian Spearman, at a certain position, with X health).

+

MapUnit is a specific instance of a unit, whereas BaseUnit is the type of unit.

+

Main information:

+
    +
  • A name - references a specific BaseUnit
  • +
  • Health and Movement
  • +
  • Promotion status - UnitPromotions
  • +
+

Ruleset

+

So far so good - but what of everything that makes Civ, Civ? The units, the buildings, the nations, the improvements etc?

+

Since these things remain the same for every game, these are not saved on a per-game basis, but rather are saved in json files in Unciv's asset folder.

+

Each class in the game state that saves one of these will reference it by name, and when the game is running it will check the Ruleset to find the relevant information for that object.

+

The various objects are:

+
    +
  • Technology - referenced mainly in CivilizationInfo.TechManager
  • +
  • Nations - referenced mainly in CivilizationInfo
  • +
  • Policy - referenced mainly in CivilizationInfo.PolicyManager (seeing a pattern here?)
  • +
  • Building - referenced mainly in CityInfo.ConstructionManager
  • +
  • BaseUnit - referenced mainly in MapUnit
  • +
  • Promotion - referenced mainly in MapUnit
  • +
  • Terrain - referenced mainly in TileInfo
  • +
  • TileResource - referenced mainly in TileInfo
  • +
  • TileImprovement - referenced mainly in TileInfo
  • +
+

There are also Translations in the Ruleset, but they technically have nothing to do with the game state but rather with the UI display.

+

The information for all of these is in json files in android\assets\jsons

+

UI

+

UncivGame is the 'base' class for the UI, from which everything starts, but it itself doesn't do much.

+

When we change a screen, we're changing a value in UncivGame, the interesting stuff happens in the screens themselves.

+

The main menu - MainMenuScreen

+

This is what the user sees when first entering the game. It acts as a hub to loading games, adding mods, options etc, without loading an actual game upfront - this allows us to differentiate between "User can't enter game" and "User can't load game" problems

+

Starting a new game - NewGameScreen

+

This is basically a giant setting screen for GameOptions and MapOptions classes, divided into:

+
    +
  • GameOptionsTable - game speed, mods, etc
  • +
  • MapOptionsTable - either from preexisting map file or generated, in which case: size, map generation type, etc.
  • +
  • PlayerPickerTable - What civs are in the game and who controls them
  • +
+

The World Screen - WorldScreen

+

90% of the game is spent on this screen, so naturally it's the fullest, with the most things happening.

+

This is the main hub of the game, with all other screens being opened from it, and closing back to reveal it.

+

Most notable are:

+
    +
  • The map itself - a TileMapHolder - with each of the rendered tiles being a TileGroup
  • +
  • The information panels - WorldScreenTopBar for stats and resources, UnitTable for the currently selected unit, TileInfoTable or the currently selected tile, BattleTable for battle simulation, and NotificationsScroll for the notifications
  • +
  • The minimap - MinimapHolder
  • +
  • Buttons linking to other screens - to the TechPickerScreen, EmpireOverviewScreen, and PolicyPickerScreen
  • +
  • The almighty Next Turn button
  • +
+

The city screen - CityScreen

+

The second-most important screen.

+

Notable parts:

+
    +
  • the City Stats table - should definitely be its own class come to think of it
  • +
  • The construction list and current construction (bottom left) - ConstructionsTable
  • +
  • Existing buildings, specialists and stats drilldown - CityInfoTable
  • +
+

Others

+

A few words need to be said about the NextTurn process, but there isn't really a good place for it so I'll put it here.

+

We clone the GameInfo and use a "new" GameInfo for each turn because of 2 reasons.

+

The first is multithreading and thread safety, and the second is multiplayer reproducibility.

+

The first point is pretty basic. The NextTurn needs to happen in a separate thread so that the user can still have a responsive game when it's off doing stuff. Stuff in the GameInfo changes on NextTurn, so if you're rendering that same GameInfo, this could cause conflicts. Also, after NextTurn we generally autosave, and if stuff changes in the state while we're trying to serialize it to put it in the save file, that's Not Fun. A single clone solves both of these problems at once.

+

The second point is less obvious. If we use our mutable state, changing stuff in place, then what happens when we're playing in Multiplayer? Multiplayer is based upon the fact that you can receive an entire game state and go from there, and in fact the move to multiplayer was what made the whole "clone" thing necessary (on the way it also solved the aforementioned threading problems)

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/Saved-games-and-transients/index.html b/Developers/Saved-games-and-transients/index.html new file mode 100644 index 0000000000..5543e80896 --- /dev/null +++ b/Developers/Saved-games-and-transients/index.html @@ -0,0 +1,1327 @@ + + + + + + + + + + + + + + + + + + + + + + + Saved games and transients - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Saved games and transients

+

Unciv is a game where many things are interconnected. Each map unit, for example, belongs to a civ, is located on a tile, can have several promotions and "inherits" from a base unit.

+

When saving a game state, we want it to be as small as possible - so we limit the information saved to the bare minimum. We save names instead of pointers, and anything that can be recalculated is simply not saved.

+

But during runtime, we need these links for performance - why perform a lookup every time if we can save a reference on the object?

+

Classes are therefore freeze dried on serialization, and rehydrated for runtime. Since these fields are marked in Kotlin as @Transient fields, we call the rehydration function setTransients.

+

Take the map unit for example. How can we calculate the uniques for that unit? They can come from several places:

+
    +
  • Base unit uniques
  • +
  • Promotions
  • +
  • Civ-wide uniques
  • +
+

So from the save file, we get the civ name, unit name, and promotion names; for runtime, we'll want a reference to the civ, base unit, and promotions.

+

We can find these by looking up the civ in the game, and the unit and promotions from the ruleset.

+

The civ itself - a game object - references the nation - a ruleset object, which is another link. The base unit references the unit type, another link.

+

The nation, base unit, and promotions, all contain uniques - which are saved as strings. For performance, these too get saved at runtime as Unique instances - which contain the unique type, the parameters, conditionals, etc.

+

Beyond the fact that each ruleset object retains a "hydrated" list of its uniques, the unit's uniques don't change very often - so we can add yet another layer of caching by saving all the unit's uniques, and rebuilding this every time there's a change

+

All of this is VITAL for performance - Unciv is built to run on potatoes, and even hash lookups are expensive when performed often, not to mention Regexes required for Unique parsing!

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/Testing-Android-Builds/index.html b/Developers/Testing-Android-Builds/index.html new file mode 100644 index 0000000000..0e56b34b53 --- /dev/null +++ b/Developers/Testing-Android-Builds/index.html @@ -0,0 +1,1513 @@ + + + + + + + + + + + + + + + + + + + + + + + Building for and testing on Android - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Building for and testing on Android

+

This is a work in progress - feel free to contribute. Much of this information is not specific to Unciv and publicly available.

+

Run configuration

+
    +
  • In Android Studio, Run > Edit configurations (be sure the Gradle sync is finished successfully first).
      +
    • Click "+" to add a new configuration
    • +
    • Choose "Android App"
    • +
    • Give the configuration a name, we recommend "Android"
    • +
    • Set module to Unciv.android.main
    • +
    • On the Miscellaneous tab, we recommend checking both logcat options
    • +
    • On the Debugger tab, we recommend checking Automatically attach on Debug.waitForDebugger()
    • +
    • That's it, the rest can be left as defaults.
    • +
    +
  • +
+

Physical devices

+

Debugging on physical devices is actually easiest. +With Studio running, you will have adb running, and any newly connected device that generally allows debugging will ask to confirm your desktop's fingerprint (use an USB cable for this tutorial, IP is another matter). +Once adb sees the device and your desktop is authorized from the device, it will be available and preselected on the device select-box to the right of your "android" run configuration and you can start debugging just like the desktop version. +Note A debug session does not end after selecting Exit from Unciv's menus - swipe it out of the recents list to end the debug session. Hitting the stop button in Studio is less recommended. That's an Android feature.

+

Building an APK

+

Android Studio has a menu entry "Build -> Build Bundle(s) / APK(s) -> Build APK(s)." +This will build a ready-to-install APK, and when it is finished, pop a message that offers to show you the file in your local file manager. +Important such locally built APK's are debug-signed and not interchangeable with Unciv downloaded from stores. You cannot update one with the other or switch without uninstalling first - losing all data.

+

Virtual devices (AVD)

+

(TODO) +- Install Emulator +- Intel HAXM: Deprecated by Intel but still recommended +- Download system image + - Choice: Match host architecture, w/o Google, older is faster...? +- Configure AVD +- Debug on AVD

+

Unciv's log output

+

Unciv's log system runs on top of the Android SDK one, and filters and tags the messages before passing them to the system.

+

Like the desktop variant, it has a 'release' mode where all logging from Unciv code is dropped. +A release is detected when the actual APK manifest says debuggable=false - all possibilities discussed here are debug builds in that sense. +Running from Studio it does not matter which button you use - Run or Debug - both deploy a debug build, the difference is only whether it attaches the debugger right away. +An APK built from Studio is also always a debug build.

+

Therefore, logging is always enabled unless you run a store version. +You can override this by providing an intent extra: In your Run configuration, on the "General" Tab, add in the "Launch Flags" field: --ez debugLogging false. +The override can also be controlled without Studio using the activity manager: +

adb shell am start com.unciv.app/com.unciv.app.AndroidLauncher --ez debugLogging true
+
+(or am start... directly from a device terminal) will turn on logging for a release (store) build.

+

The log system's filtering capabilities that work by providing -D options to the Java virtual machine cannot be controlled on Android as far as we know. +(TODO - document those in the desktop/Studio wiki article)

+

Reading the logcat

+

(TODO) +- Studio + - If the logcat window is missing: View - Tool Windows - Logcat +- Studio's filtering + - When you debug Unciv, a matching filter is pre-applied to the Logcat window, but the tool can actually show the entire system log, including those for other apps. + - Using package:com.unciv.app tag:Unciv as filter is useful to see only the output of Unciv's own logging system. +- logcat apps on the device + - com.pluscubed.matloglibre? Outdated. +- logcat apps need root or specific authorization + - adb shell pm grant <logcat app's package id> android.permission.READ_LOGS

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/Translations,-mods,-and-modding-freedom-in-Open-Source/index.html b/Developers/Translations,-mods,-and-modding-freedom-in-Open-Source/index.html new file mode 100644 index 0000000000..a8494743eb --- /dev/null +++ b/Developers/Translations,-mods,-and-modding-freedom-in-Open-Source/index.html @@ -0,0 +1,1495 @@ + + + + + + + + + + + + + + + + + + + + + + + Translations, mods, and modding freedom in Open Source - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Translations, mods, and modding freedom in Open Source

+

Unciv is, at its core, a remake of Civ V, meaning mechanics-wise there's almost by definition not much place for innovation. +In terms of UI, there's nothing here that hasn't been done dozens of times, with far greater polish. +However, there is one area where Unciv is groundbreaking: in its accessibility of translations, the possibility space of its mods, and the relationship between them.

+

Translations

+

The translation process

+

So let's start with translation. Surely this is a solved problem, right? Source text + language = translated text, and this information needs to be in a file so the game can read it. What makes us different from, for example, Firaxis?

+

There are a couple of things, but the most significant is that this is an open-source game, and thus the translations are open-source as well. +This means translators are both amateurs and not obligated to translate, so if translating is difficult, they simply won't.

+

Amateurs can make mistakes, which is why it's vital that mistakes are easy to spot. That means that formats like "translation key" - e.g. DIPLOMACY_GREETING = Siamo lieti di fare la vostra conoscenza. are much less effective than A pleasure to meet you. = Siamo lieti di fare la vostra conoscenza. This format lends itself both the easier translation (it's immediately obvious what needs to be translated) and actual collaboration.

+

A common suggestion that we get (by people with little familiarity with the project) is to "use a website for translation". This is not bad advice for a small open source game, but there are multiple disadvantages that (for now) no translation website provides enough advantage to outweigh:

+
    +
  1. Testing. Currently, translations undergo a number of tests for verification - more on that later! This allows some language changes to be accepted and others not, and it's all in the same platform with the same tests. External translation tools don't allow for this.
  2. +
  3. History and revisions. This is what Git was made for, and nothing like it exists in the world. By itself this would not
  4. +
  5. Release cycle. We release versions semiweekly, and if we needed to upload changes to the translation website for every in-game change, and download them for every release, that's extra work. For some websites this is automate-able - for most it is not.
  6. +
  7. Discussions. Most crowdsourcing translation websites don't allow for discussions and corrections on translations. Github makes every translation collaborative work.
  8. +
  9. Mass changes. If we're changing the source of the translation but want to keep the various destinations (say, we change "Gold from trade routes +[amount]%" to "+[amount]% Gold from trade routes"), if all the translation files are in Git we can do that in 1 minute. If it's external, this varies greatly.
  10. +
+

Here are some ways that we managed to go wrong in the past:

+
    +
  • Putting all languages into the same file ("one big translation dictionary") - when multiple people edit this file for different languages, they can conflict with each other. Separate to different files for simpler management.
  • +
  • Using json - json is great for machines, but less so for humans, who can easily make mistakes. Json format is surprisingly finnicky, miss a closing " and the whole file is now unreadable.
  • +
+

The format we decided to go for is one file per language, delimited by " = " for visual separation, in a .properties file. Lines starting in # are considered comments, so we can add comments for translators.

+

Building the translation files

+

As stated, Unciv releases versions semiweekly, and very often these changes include new objects or new UI elements. How do we keep all translation files up to date?

+

In Unciv, all object data is stored in json format. This allows us to iterate on all objects, regardless of type, and extract the various text fields (strings or lists of strings). We avoid duplication by saving all translation texts we've already added, and use the existing translations to populate the "value" for each translation "key" we found in the json files.

+

Since we rebuild the entire translation file every time, there's currently no way for translators to retain their own comments for future translators. +But on the other hand, since for each line that we add we already know if it's translated or not, this allows us to add a # Requires translation line before every non-translated line, which helps translators for languages that are almost fully translated to easily locate the new or changed terms for translation with ctrl+f (and of course this marking will disappear the next time we rebuild the file).

+

Since there are UI texts that are not part of any specific object (like "Start new game"), we have a separate template.properties file for texts to translate that are not in the json files. Unlike adding objects, where the developer doesn't need to address the translation files at all since it's all linked, when adding UI elements with new texts devs need to remember to add the texts to template.properties file.

+

Translation placeholders

+

This is all well and good for specific text-to-text translations, but what about translating "A Temple has been built in Rome"? The same template could potentially be any building name, or any city name!

+

We do this with placeholders, which looks something like this: [construction] has been built in [cityName] = [cityName] ha costruito [construction]. +As you can see, the placement of the parameters can change between languages, so we have to name all parameters.

+

This also means that there can be explicitly wrong translations - if any parameter that appears in the source does not appear in the translated version, we won't be able to display this in-game! This is one of the translation tests that we mentioned earlier - when a translator opens a PR, the game undergoes build & test via the Github Actions, and will notify on failures. Finding the text that warns of the failure within the action output is currently mostly done by devs, but I hope to be able to automate this too someday.

+

To translate a text like "[Temple] has been built in [Rome]", therefore, we need to:

+
    +
  • Find the relevant translation (we do this by erasing all text between square brackets in input and finding the relevant translation text)
  • +
  • Map placeholder names to input text (construction = Temple, cityName = Rome)
  • +
  • Replace placeholders in translation with TRANSLATED input text (in [cityName] ha costruito [construction], replace "[cityName]" with translation of "Rome", and "[construction]" with translation of "Temple")
  • +
+

Translating mod data

+

The translation generation reads information from "a ruleset", i.e. the set of jsons defining the game's objects. +Every mod is also a ruleset, either replacing or adding to the base ruleset defined in the game. +This means that the same translation generation that we do for the base game can also be applied to mods, and so each modder can decide (from within the game) to generate translation files for his mod, and since mods are uploaded to Github to be widely available as part of the mod release methodology, translators will be able to translate those files the exact same way that they translate Unciv's base ruleset.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/UI-development/index.html b/Developers/UI-development/index.html new file mode 100644 index 0000000000..42ed135aa4 --- /dev/null +++ b/Developers/UI-development/index.html @@ -0,0 +1,1516 @@ + + + + + + + + + + + + + + + + + + + + + + + UI Development - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

UI Development

+

Unciv is backed by GDX's scene2d for the UI, so check out their official documentation for more info about that.

+

We mainly use the Table class of scene2d, because it offers nice flexibility in laying out all the user interface.

+

The FasterUIDevelopment class

+

This class is basically just a small helper GDX application to help develop UI components faster.

+

It sets up the very basics of Unciv, so that you can then show one single UI component instantly. This gives you much faster response times for when you change something, so that you can immediately see the changes you made, without having to restart the game, load a bunch of stuff and navigate to where your UI component would actually be.

+

To use it, you change the DevElement class within the FasterUIDevelopment.kt file so that the actor field is set to the UI element you want to develop. A very basic usage is there by default, just showing a label, but you can put any UI element there instead.

+

Once it's up and running, your UI element is centered in the window and gets an orange border. You can toggle Scene2D debugging with the middle mouse button (click the wheel).

+

Note that the parent your UI element will get doesn't set the size (which in reactive nested layouts would be its responsibility), so if your element is a WidgetGroup like Table, just pack() it. Or ignore the orange dot left over from the border. Also please do not resize the window - there's no support for that at the moment and no guarantees can be given for the results.

+
class DevElement(
+    val screen: UIDevScreen
+) {
+    lateinit var actor: Actor
+    fun createDevElement() {
+        actor = "This could be your UI element in development!".toLabel()
+    }
+
+    fun afterAdd() {
+    }
+}
+
+

You can then simply run the main method of FasterUIDevelopment to show your UI element.

+

There's two ways to do so: +* Open this file in Android Studio under project:docs/Developers. That line above has a little green arrow in the left gutter that can be clicked. +* Create a Run Configuration similar to the desktop one described here, but with classpath Unciv.tests.test and main class com.unciv.dev.FasterUIDevelopment. + image

+

The 'Gdx Scene2D debug' option

+

This option (on the secret 'Debug' page) turns on several UI debug features: +* Gdx Actor debug lines +* Mouse coordinates and FPS +* Descriptor of the Actor under the mouse +* Coordinate scales

+

Gdx Actor debug lines

+

See Gdx wiki +The Scene2D debug option turns on mouse-over Gdx debug lines at the stage level using setDebugUnderMouse, setDebugTableUnderMouse and setDebugParentUnderMouse. +* Blue lines are Table bounds - Each Table has dimensions as a Widget and as a logical table determined by the cells. They will coincide if there is both expandX and expandY set somewhere. +* Red lines are Cell bounds - the part within cell padding. +* Green lines are Actor bounds of the Cell contents. If the Cell has both Fill directions set, they disappear below the red cell bounds (that is, unless the content Actor has a maxSize limiting the fill).

+

Mouse coordinates and FPS

+

On the bottom right is a semi-transparent panel with 3 numbers on top: X and Y of the mouse in Gdx stage coordinates, and FPS.

+

Descriptor of the Actor under the mouse

+

The lower part of said panel shows a string helping to identify which Actor the mouse is over. This will look for the actor's parent and potentially children, and the optional Actor.name field. Java class names or Label text are used when appropriate - it tries to build something short, but just descriptive enough. It uses the following separators / symbols: +* : a colon separates class name from Actor.name - or Actor.name is used undecorated if it contains the class name. +* " double-quotes show actual Label or TextButton text, max 20 characters, prefixed directly with the class name. +* . a dot separates parent from hit Actor: If the above alone does not yield a descriptive label, the parent (if any) is added as descriptive label per the rules above or as simple class name. +* (..) after the above designates a sample from the children (the first nicely descriptive one), only if the parent won't add good recognition value.

+

Coordinate scales

+

The bottom and right edges of the screen get tiny tick marks, each 20 units in the Gdx stage coordinates, to help estimate paddings or sizes.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Developers/Uniques/index.html b/Developers/Uniques/index.html new file mode 100644 index 0000000000..b14b513923 --- /dev/null +++ b/Developers/Uniques/index.html @@ -0,0 +1,1470 @@ + + + + + + + + + + + + + + + + + + + + + + + Uniques - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Uniques

+

What Modders Need To Know

+

Objects in the game - terrains, units, buildings, improvements, etc - differ by their stats, but what makes them truly different mechanically are their special abilities, or as we call them - Uniques.

+

Each game object can have any number of these Uniques.

+

The different possible types of uniques are available here, with each unique having a string value, e.g. "Gain [amount] [stat/resource]"

+

These are unique types, because they are in fact templates for possible concrete uniques, where the parameters in square brackets are filled in with specific values - e.g. "Gain [20] [Gold]" +Game objects should have concrete uniques (parameters filled in)

+

Every parameter in square brackets, is defined by its type, a list of which is available here - each parameter type has its own text value, e.g. "amount" means an integer. +That determines possible values that this parameter can contain, e.g. "amount" should only contain strings that can be serialized as integers.

+

Concrete uniques that contain incorrect values (e.g. "Gain [three] [money]") are warned against in the mod checker, and if they're serious enough, also when starting a new game with the mod

+

Sometimes uniques are deprecated - Unciv provides autoupdating, meaning you only need to click a button to update the deprecated uniques in your mod!

+

Conditionals and Modifiers

+

Uniques can be modified to do certain things, using special Uniques that are shown in the uniques list within <these brackets>.

+

This is done by adding these modifiers after the unique like so: "Gain [30] [Gold] <after discovering [Steam Power]>"

+

The most common type of modifier is a conditional - basically limiting the unique to only apply under certain conditions - so all modifiers are sometimes refered to as conditionals.

+

Other more specialized types of modifiers exist:

+
    +
  • Triggerable uniques can get under what circumstances they activate
  • +
  • Unit actions can get costs, side effects, and limited uses
  • +
+

As you can see, these conditionals also can contain parameters, and these follow the same rules for parameters as the regular uniques.

+

Triggerable Uniques

+

Most uniques are long-term effects - they apply as long as you have the object containing them (building, resource, tech, policy). +Trigger uniques are different - they are one-time effects, but which may be triggered several times.

+

Trigger uniques come in two flavors - civ-wide triggers and unit-wide triggers. +Unit triggerables are only relevant in the context of a specific unit, so they can be attached to units, promotions or unit types.

+

Any triggerable unique added to a unit which has unit action modifiers will be considered as a unit action. +Unit actions can contain civ-wide effects as well.

+

Trigger uniques specify their activation with trigger conditions. +Like the triggers themselves, these come in two flavors - civ-wide conditions and unit-wide conditions. +Trigger uniques with no trigger modifiers are activated upon construction (units, buildings, improvements) or discovery (techs, policies).

+

Events are a ruleset object that act as a sort of "extended trigger" - they are activated by the "Triggers a [event] event" unique, and they trigger other uniques depending on user choice.

+

What Developers Need To Know

+

We parse the unique by comparing the string given by the modder minus square bracket contents, to the known list of uniques. +If we find a match - Congrats, we set that as the unique type.

+

When we check for uniques in the code, it's always for uniques of a specific type, so we can just check the 'unique type' we previously assigned

+

We then take the parameters of that unique, which we also determined previously by scanning for all strings within square brackets, and use their values in determining the effect of the unique

+

There is a LOT of caching involved everywhere to make this all as fast as possible, but you really don't need to worry about that, that's my job ;)

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Guiding-Principles/index.html b/Guiding-Principles/index.html new file mode 100644 index 0000000000..9346e2b4fe --- /dev/null +++ b/Guiding-Principles/index.html @@ -0,0 +1,1424 @@ + + + + + + + + + + + + + + + + + + + + + + + Guiding Principles - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + +

Guiding Principles

+

The AI plays to win

+

In a perfect world, the AI would pass the "Turing test" of gameplay - you would't be able to tell if you're playing against a human or AI.

+

Examples:

+
    +
  • No "what would you offer me for this"
  • +
  • AI will choose to attack you if your military is weak (WILL kick you when you're down)
  • +
+

There is a fine line here between "exploitable" and "no fun" regarding trade - regular players may refuse any trade you offer them on principle. +We don't want that from the AI, which leaves us slightly open to exploits, but that's a trade-off we make knowingly.

+

Modding philosophy - minimal objects, maximum interactions

+

As a new modder it's easy to get lost in the sheer number of uniques.

+

Our aim is to minimize the number of uniques as much as possible, but enable "emergent modding" by allowing combinations.

+

Examples:

+
    +
  • Parameters in uniques > multiple uniques
  • +
  • Conditions should be Conditionals, so they can be applied to all uniques
  • +
  • Triggered uniques and unique triggers - all combinations
  • +
  • Unit Action modifiers, rather than special attributes for specific unit actions
  • +
+

Crash early, crash often

+

A crash stacktrace is halfway to a solution - a game save which reliably produces it is 90% there.

+

Whenever an unexpected situation occurs - the game has reached an incorrect state - we should crash, to allow the problem to be fixed as soon as possible.

+

Persisting with an incorrect state makes the eventual resulting problems further from the cause, and complicates debugging.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Autoupdates/index.html b/Modders/Autoupdates/index.html new file mode 100644 index 0000000000..aae7697729 --- /dev/null +++ b/Modders/Autoupdates/index.html @@ -0,0 +1,1390 @@ + + + + + + + + + + + + + + + + + + + + + + + Autoupdates - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Autoupdates

+

Unciv contains built-in capabilities for packing images and autoupdating uniques.

+

You can automate these updates by adding a GitHub Actions workflow to your repository.

+
    +
  • In your Github page go to "actions" tab
  • +
  • Suggested for this repo > Simple workflow > "Configure"
  • +
  • Copy the text of this file ("copy raw file") to the new file in your repo
  • +
  • Change the file name to "autoupdate.yml" at the top
  • +
  • "Commit changes" (green button, top-right) > "Commit changes"
  • +
+

On every commit, and once per day, it will:

+
    +
  • Try to shrink PNGs (this is lossless, the pixel data remains the same but the file takes less space)
  • +
  • Try to autoupdate deprecated uniques
  • +
+

If there are changes, this will create a PR to your repo - here's an example - which you can choose to accept

+

If you see that the autoupdate isn't 100% - in which case talk to me and we'll sort it out 🙂

+

Errors

+

If you get a remote: Permission to <...>.git denied to github-actions[bot]. +You need to:

+
    +
  • Open Settings > Actions > General
  • +
  • Under "Workflow permissions" select "Read and write permissions", "Allow GitHub Actions to create and approve pull requests"
  • +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Creating-a-UI-skin/index.html b/Modders/Creating-a-UI-skin/index.html new file mode 100644 index 0000000000..60f3573593 --- /dev/null +++ b/Modders/Creating-a-UI-skin/index.html @@ -0,0 +1,2417 @@ + + + + + + + + + + + + + + + + + + + + + + + Creating a UI skin - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Creating a UI skin

+

You should read the Mods page first before proceeding

+

In order to add a UI skin mod (yes, UI skins are just another type of mod), all you need to do is add your images under Images/Skins/MyCoolSkinExample and enable the mod as a permanent visual mod.

+

The game will then recognize the skin, and allow you to pick it in the options menu.

+

Just like tilesets, UI skins can be used to alter the appearance of Unciv. Please note that UI skins do not support custom icons and fonts and not every UI element can be customized yet too.

+

We use so called 9.png (or Ninepatch) files for every skin image because UI elements need a way to be resized based on game window size and resolution. Ninepatch files can be created manually by adding black pixels around your custom images in a specific manner or by using Android Studio's Draw 9-patch tool or this tool by romannurik for example. You may also check if your favorite image creation tool supports nine patches itself to generate them more easily.

+

A skin image can either be gray scale and later be colored in game by modifying the tint in the skinConfig or be colored directly in the image. When coloring the image directly it is important to set the tint of the UI element to white. Please note that tileable ninepatches and ninepatches with multiple stretch areas are not supported because of technical restrictions by libgdx.

+

There are 6 basic shapes which can be placed inside the Images/Skins/MyCoolSkinExample folder: + - checkbox + - checkbox-pressed + - rectangleWithOutline + - roundedEdgeRectangle + - select-box + - select-box-pressed

+

These shapes are used all over Unciv and can be replaced to make a lot of UI elements change appearance at once. To change just one specific element use the table below to create an image at the specified directory using the specified name inside Images/Skins/MyCoolSkinExample. See the image below for an example file structure. skinExample

+

Limitations

+
    +
  • UI elements which change color because they have multiple states can not be given multiple colors based on their state using tint
  • +
  • When coloring the image directly, setting the tint of the UI element to white overwrites these states
  • +
  • Tileable ninepatches and ninepatches with multiple stretch areas are not supported because of technical restrictions by libgdx
  • +
+

Available UI elements

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DirectoryNameDefault shapeImage
AnimatedMenu/ButtonroundedEdgeRectangleMid
CityScreen/CityPickerTableroundedEdgeRectangle
CityScreen/CitizenManagementTable/AvoidCellnull
CityScreen/CitizenManagementTable/FocusCellnull
CityScreen/CitizenManagementTable/ResetCellnull
CityScreen/CityConstructionTable/AvailableConstructionsTablenull
CityScreen/CityConstructionTable/ConstructionsQueueTablenull
CityScreen/CityConstructionTable/Headernull
CityScreen/CityConstructionTable/PickConstructionButtonnull
CityScreen/CityConstructionTable/PickConstructionButtonSelectednull
CityScreen/CityConstructionTable/QueueEntrynull
CityScreen/CityConstructionTable/QueueEntrySelectednull
CityScreen/CityScreenTileTable/Backgroundnull
CityScreen/CityScreenTileTable/InnerTablenull
CityScreen/CityStatsTable/Backgroundnull
CityScreen/CityStatsTable/InnerTablenull
CityScreen/ConstructionInfoTable/Backgroundnull
CityScreen/ConstructionInfoTable/SelectedConstructionTablenull
CivilopediaScreen/EntryButtonnull
DiplomacyScreen/LeftSidenull
DiplomacyScreen/RightSidenull
DiplomacyScreen/SelectedCivnull
General/AnimatedMenuroundedEdgeRectangle
General/Bordernull
General/ExpanderTabnull
General/HealthBarnull
General/KeyCapturingButtonroundedEdgeRectangleSmall
General/TabbedPagernull
General/TooltiproundedEdgeRectangle
General/Popup/Backgroundnull
General/Popup/InnerTablenull
LanguagePickerScreen/LanguageTablenull
LoadGameScreen/BottomTablenull
LoadGameScreen/TopTablenull
MainMenuScreen/Backgroundnull
MainMenuScreen/MenuButtonroundedEdgeRectangle
MainMenuScreen/VersionroundedEdgeRectangle
MapEditor/MapEditorToolsDrawer/Handlenull
ModManagementOptions/ExpanderTabnull
ModManagementScreen/BottomTablenull
ModManagementScreen/TopTablenull
MultiplayerScreen/BottomTablenull
MultiplayerScreen/TopTablenull
NewGameScreen/BottomTablenull
NewGameScreen/GameOptionsTablenull
NewGameScreen/MapOptionsTablenull
NewGameScreen/PlayerPickerTablenull
NewGameScreen/TopTablenull
NewGameScreen/NationTable/Backgroundnull
NewGameScreen/NationTable/BorderTablenull
NewGameScreen/NationTable/RightInnerTablenull
NewGameScreen/NationTable/Titlenull
NewGameScreen/PlayerPickerTable/PlayerTablenull
OverviewScreen/DiplomacyOverviewTab/CivTablenull
OverviewScreen/NotificationOverviewTable/NotificationroundedEdgeRectangle
OverviewScreen/ReligionOverviewTab/BeliefDescriptionnull
OverviewScreen/TradesOverviewTab/OffersTablenull
OverviewScreen/UnitOverviewTab/UnitSupplyTablenull
PlayerReadyScreen/Backgroundnull
PolicyScreen/PolicyBranchAdoptButtonroundedEdgeRectangleSmall
PolicyScreen/PolicyBranchAdoptButtonBorderroundedEdgeRectangleSmall
PolicyScreen/PolicyBranchBackgroundrectangleWithOutline
PolicyScreen/PolicyBranchBackgroundBorderrectangleWithOutline
PolicyScreen/PolicyBranchHeaderrectangleWithOutline
PolicyScreen/PolicyBranchHeaderBorderrectangleWithOutline
PolicyScreen/Colors/BranchBGAdopted50,45,5
PolicyScreen/Colors/BranchBGCompleted255,205,0
PolicyScreen/Colors/BranchBGNotAdopted5,45,65
PolicyScreen/Colors/BranchHeaderBG47,90,92
PolicyScreen/Colors/BranchLabelAdopted150,70,40
PolicyScreen/Colors/BranchLabelNotPickable0xffffff7f
PolicyScreen/Colors/BranchLabelPickableWHITE
PolicyScreen/Colors/ButtonBGAdopted1,17,19
PolicyScreen/Colors/ButtonBGAdoptedSelected1,17,19
PolicyScreen/Colors/ButtonBGNotPickable20,20,20
PolicyScreen/Colors/ButtonBGNotPickableSelected20,20,20
PolicyScreen/Colors/ButtonBGPickable32,46,64
PolicyScreen/Colors/ButtonBGPickableSelected37,87,82
PolicyScreen/Colors/ButtonIconAdoptedGOLD
PolicyScreen/Colors/ButtonIconAdoptedSelectedGOLD
PolicyScreen/Colors/ButtonIconNotPickable0xffffff33
PolicyScreen/Colors/ButtonIconNotPickableSelected0xffffff33
PolicyScreen/Colors/ButtonIconPickableWHITE
PolicyScreen/Colors/ButtonIconPickableSelectedWHITE
PromotionScreen/PromotionButtonroundedEdgeRectangleMid
PromotionScreen/PromotionButtonBorderroundedEdgeRectangleMidBorder
TechPickerScreen/Backgroundnull
TechPickerScreen/BottomTablenull
TechPickerScreen/CurrentTechColor72, 147, 175
TechPickerScreen/QueuedTechColor72, 462, 43*2
TechPickerScreen/ResearchableTechColor28, 170, 0
TechPickerScreen/ResearchedFutureTechColor127, 50, 0
TechPickerScreen/ResearchedTechColor255, 215, 0
TechPickerScreen/TechButtonIconsOutlineroundedEdgeRectangleSmall
VictoryScreen/CivGrouproundedEdgeRectangle
WorldScreen/AirUnitTablenull
WorldScreen/BattleTablenull
WorldScreen/NotificationroundedEdgeRectangle
WorldScreen/PickTechButtonroundedEdgeRectangle
WorldScreen/TileInfoTablenull
WorldScreen/TutorialTaskTablenull
WorldScreen/UnitTableroundedEdgeRectangleMid
WorldScreen/CityButton/AirUnitTableroundedEdgeRectangleSmall
WorldScreen/CityButton/AirUnitTableBorderroundedEdgeRectangleSmall
WorldScreen/CityButton/DefenceTableroundedTopEdgeRectangleSmall
WorldScreen/CityButton/DefenceTableBorderroundedTopEdgeRectangleSmallBorder
WorldScreen/CityButton/IconTableroundedEdgeRectangleMid
WorldScreen/CityButton/IconTableBorderroundedEdgeRectangleMidBorder
WorldScreen/CityButton/InfluenceBarnull
WorldScreen/Minimap/Backgroundnull
WorldScreen/Minimap/Bordernull
WorldScreen/NextTurn/ProgressBarnull
WorldScreen/NextTurn/ProgressColorFOREST
WorldScreen/TopBar/LeftAttachmentroundedEdgeRectangle
WorldScreen/TopBar/ResourceTablenull
WorldScreen/TopBar/RightAttachmentroundedEdgeRectangle
WorldScreen/TopBar/StatsTablenull
WorldScreenMusicPopup/TrackList/Downnull
WorldScreenMusicPopup/TrackList/Overnull
WorldScreenMusicPopup/TrackList/Upnull
+ + +

SkinConfig

+

The skinConfig is similar to the tilesetConfig and can be used to define different colors and shapes for unciv to use.

+

To create a config for your skin you just need to create a new .json file under jsons/Skins/. Just create a .txt file and rename it to MyCoolSkinExample.json. You only have to add things if you want to change them. Else the default values will be used.

+

This is an example of such a config file that will be explain below:

+
{
+    "baseColor": {"r":1,"g":0,"b":0,"a":1},
+    "defaultVariantTint": {"r":1,"g":1,"b":1,"a":1},
+    "skinVariants": {
+        "MainMenuScreen/MenuButton": {
+            "image": "MyCoolNewDesign",
+            "foregroundColor": {"r": 0, "g": 0, "b": 1, "a": 1},
+            "iconColor": {"r": 0, "g": 1, "b": 0, "a": 1}
+        },
+        "TechPickerScreen/TechButton": {
+            "image": "MyCoolNewDesign",
+            "alpha": 0.7
+        },
+        "WorldScreen/TopBar/ResourceTable": {
+            "alpha": 0.8
+        },
+        "WorldScreen/UnitTable": {
+            "tint": {"r": 1, "g": 0, "b": 0},
+            "image": "WorldScreen/TopBar/ResourceTable",
+            "alpha": 0.4
+        },
+        "WorldScreen/Minimap/Background": {
+            "tint": {"r": 0.2, "g": 0.4, "b": 0.45, "a": 1}
+        }
+    }
+}
+
+

baseColor

+

A color defined with normalized RGBA values. Default value: {"r": 0, "g": 0.251, "b": 0.522, "a": 0.749}

+

Defines the color unciv uses in most ui elements as default

+

fallbackSkin

+

A string. Default value: "Minimal".

+

The name of another skin to use as a fallback if an image is not found or not specified in this skin. +Set to null to disable fallback.

+

defaultVariantTint

+

A color defined with normalized RGBA values. Default value: null

+

The tint all skinVariants should use if not explicitly specified in a skinVariant. +If you mostly use colored images set this to white ({"r": 1, "g": 1, "b": 1, "a": 1}) to get +the correct colors.

+

skinVariants

+

A dictionary mapping string to a SkinElement. Default value: empty

+

These variants can be used to define a different image, tint and/or alpha for a specified UI element. The string used to identify the UI element can be taken from the table above by appending the name to the directory. +

| Directory             | Name          |
+|-----------------------|---------------|
+| WorldScreen/          | Notification  | -> WorldScreen/Notification
+| WorldScreen/TopBar/   | StatsTable    | -> WorldScreen/TopBar/StatsTable
+

+

image

+

A path to an image. The file is expected to be located alongside the 6 basic shapes inside the Images/Skins/MyCoolSkinExample folder if just a name like MyCoolNewDesign is given. The image path can also be another ui element like WorldScreen/TopBar/ResourceTable so images can be reused by other elements.

+

tint

+

A color defined with normalized RGBA values. Default value: null

+

The tint this UI element should get. Is applied as a tint on top of the image. This means that if the +image is colored and the tint is not white the tint color will merge with the image color and not override it.

+

alpha

+

A float value. Default value: null

+

The alpha this UI element should have. Overwrites the alpha value of tint if specified.

+

foregroundColor

+

A color defined with normalized RGBA values. Default value: null

+

The color this UI element should use for its font and icons. To color icon and font differently use +the iconColor in addition to this.

+

iconColor

+

A color defined with normalized RGBA values. Default value: null

+

The color this UI element should use for its icons. Overrides the foregroundColor for icons if specified.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Creating-a-custom-tileset/index.html b/Modders/Creating-a-custom-tileset/index.html new file mode 100644 index 0000000000..916938324d --- /dev/null +++ b/Modders/Creating-a-custom-tileset/index.html @@ -0,0 +1,1772 @@ + + + + + + + + + + + + + + + + + + + + + + + Creating a custom tileset - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Creating a custom tileset

+

You should read the Mods page first before proceeding

+

In order to add a tileset mod (yes, tilesets are just another type of mod), all you need to do is add your images under Images/Tilesets/MyCoolTilesetExample and enable the mod as a permanent visual mod - the game will recognize the tileset, and allow you to pick it in the options menu.

+

Let's look at the example "Grassland+Jungle+Dyes+Trading post" to learn how the game decides which images it should use for this tile:

+
    +
  1. When there is a rule variant entry in the tileset config for this tile we will use the entry.
  2. +
  3. Else if there is an image called "Grassland+Jungle+Dyes+Trading post" we will use it instead.
  4. +
  5. Otherwise, we will check if there is an image called "Grassland+Jungle" (BaseTerrain+Terrainfeatures) and "Dyes+Trading post" (Resource+Improvement) and use the remainings of it. Let's say you made an image called "Grassland+Jungle" but none called "Dyes+Trading post". In the end, we will then use the images "Grassland+Jungle", "Dyes" and "Trading post".
  6. +
+

All these images can also use era-dependant variants if you want to change the appearance of, let's say, "Trading post" throughout the game. Just create images and add the suffix "-[era name]". +E.g. "Trading post-Classical era", "Trading post-Industrial era", etc.

+

It is advised to use the layered approach (1 and 3) often because it comes with a few advantages. Mainly:

+
    +
  • Decreased filesize (on disk, for downloads)
  • +
  • Easier support for new terrains, improvements, resources, and for changing existing tiles
  • +
+

You should keep in mind that the default rendering order is: +BaseTerrain, TerrainFeatures, Resource, Improvement.

+

Tileset config

+

This is where tileset configs shine. You can use these to alter the way Unicv renders tiles.

+

To create a config for your tileset you just need to create a new .json file under jsons/Tilesets/. Just create a .txt file and rename it to MyCoolTilesetExample.json. You only have to add things if you want to change them. Else the default values will be used.

+

This is an example of such a config file that will be explain below:

+
{
+    "useColorAsBaseTerrain": "false",
+    "useSummaryImages": "true",
+    "unexploredTileColor": {"r":1,"g":1,"b":1,"a":1},
+    "fogOfWarColor": {"r":1,"g":0,"b":0,"a":1},
+    "fallbackTileSet": null,
+    "tileScale":0.9,
+    "tileScales": {
+        "City center":1.2,
+        "Citadel":1.5
+    },
+    "ruleVariants": {
+        "Grassland+Forest": ["Grassland","ForestForGrassland"],
+        "Grassland+Jungle+Dyes+Trading post": ["Grassland","JungleForGrasslandBack","Dyes+Trading post","JungleForGrasslandFront"]
+    }
+}
+
+

useColorAsBaseTerrain

+

A boolean value ("true" or "false"). Default value: "false"

+

If true, an additional "Hexagon" image is placed below each tile and colored in the corresponding BaseTerrain color. This removes the necessity to add individual BaseTerrain images. This is how the "Minimal" tileset works.

+

useSummaryImages

+

A boolean value ("true" or "false"). Default value: "false"

+

If true, summary images are used for specific groups of images instead of using individual tile images. The summary images must be placed in the same folder as every other tile image. Summary images used:

+ + + + + + + + + + + + + +
Image groupSummary image
Natural wonders"NaturalWonder"
+

unexploredTileColor

+

A color defined with normalized RGBA values. Default value: "{"r":0.24705882, "g":0.24705882, "b":0.24705882, "a":1}" (DarkGray)

+

Defines the color of the unexplored tiles.

+

fogOfWarColor

+

A color defined with normalized RGBA values. Default value: "{"r":0, "g":0, "b":0, "a":1}" (Black)

+

Defines the color of the fog of war. The color gets approximated by 60% to allow the colors of the images below to shine through.

+

fallbackTileSet

+

A string value. Default value: "FantasyHex"

+

The name of another tileset whose images should be used if this tileset is missing images. Can be set to null to disable the the fallback tileset

+

tileScale

+

A float value. Default value: 1.0

+

The scale of all tiles. Can be used to increase or decrease the size of every tile. Is being used by the tileset mod 5Hex (made by ravignir) to fake shadows.

+

tileScales

+

A dictionary mapping string to a float value. Default value: empty

+

Used by the "Minimal" tileset to scale all its tiles except the base terrain down. Each entry overrides the tileScale value for the specified tile.

+

ruleVariants

+

A dictionary mapping string to a list of strings. Default value: empty

+

The ruleVariants are the most powerful part of the tileset config. With this, you can define, for a specific tile, which images and in which order these images should be used.

+

An example is given in the code above. For the tile "Grassland+Jungle+Dyes+Trading post" we then use the images "Grassland", "JungleForGrasslandBack", "Dyes+Trading post" and "JungleForGrasslandFront" in that order.

+

Fog and unexplored tiles

+

Unciv distinguishes between "unexplored" tiles, which are tiles the Civ has never seen, +and "not visible" tiles, which are those that were seen once but now are not.

+

Not visible tiles are grayed out by design, and on top of that have the CrosshatchHexagon.png image applied to them.

+

Unexplored tiles display the UnexploredTile.png image, on top of which CrosshatchHexagon.png is applied.

+

You can set the CrosshatchHexagon to be functionally invisible by replacing it with a 1px by 1px invisible image.

+

Unit images

+

Unit images can be changed according to civ-specific styles (if a mod specifies a "style" variable for each civilization) and according to the owning civ's current era. Unciv attempts to load the unit images in the following order (where unitName is the unit name given in Units.json, styleName is optionally specified in Nations.json, and eraName is the era name given in Eras.json (including " era")).

+
    +
  1. unitName-styleName-eraName (example: "Archer-customStyle1-Classical era.png")
  2. +
  3. unitName-eraName (example: "Archer-Classical era.png")
  4. +
  5. unitName-styleName (example: "Archer-customStyle1.png")
  6. +
  7. unitName (example: "Archer.png")
  8. +
+

Era-specific sprites do not need to be specified for each era, only on eras where the sprites change. If a modder wants a Great General unit to change sprites starting in the Modern era, they only need to create a "Great General-Modern era.png" image. The Great General unit would use the default "Great General.png" sprite for all eras up to the Modern era then the Modern era sprite for the Modern era and all eras after unless there is a later era sprite for this unit.

+

Nation-coloured units

+

Unciv can colour units according to the civilization that owns them. [PR3231]

+

This is used by providing multiple images per unit, each representing a coloured layer. The image suffixed with "-1" will be tinted to the civilization's inner colour, and the image suffixed with "-2" will be tinted to the civilization's outer colour. For example:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ImageDescriptionColour
Archer.pngBase imageUntinted
Archer-1.pngColour layerNation inner colour
Archer-2.pngColour layerNation outer colour
+

The Civ Army Color Style Sheet mod by @AdityaMH and the 5Hex Tileset by @ravignir are very good practical examples of how this can be used.

+

Attack animations

+

These are small animations that play on units when they receive damage.

+

They can be for unit types (Archery, Seige, Cavalry) or for specific unit names

+

The files should be in the format of <unit type/unit name>-attack-<frame number>. +For example, a 3 frame animation for Sword units would have the files Sword-attack-1.png, Sword-attack-3.png, Sword-attack-3.png

+

Edge images

+

You can add additional images that will be drawn only when a tile is adjacent to another tile in a specific direction.

+

The images should be placed in the Images/Tilesets/<tileset name>/Edges folder, rather than in /Tiles.

+

The name of the tile should be <tile name>-<origin tile filter>-<destination tile filter>-<neighbor direction>.png, where direction of one of:

+
    +
  • Bottom
  • +
  • BottomLeft
  • +
  • BottomRight
  • +
  • Top
  • +
  • TopLeft
  • +
  • TopRight
  • +
+

And where the tile filter is one of: +- Terrain name +- Feature name +- Terrain type (Land/Water)

+

For example: Cliff-Hills-Coast-Top.png

+

The initial name has no bearing on the image used, it is just a way to group images together.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Images-and-Audio/index.html b/Modders/Images-and-Audio/index.html new file mode 100644 index 0000000000..30407a5072 --- /dev/null +++ b/Modders/Images-and-Audio/index.html @@ -0,0 +1,2481 @@ + + + + + + + + + + + + + + + + + + + + + + + Images and Audio - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Images and Audio

+

Images and the texture atlas

+

Images need to be 'packed' before the game can use them. This preparation step needs to happen only once (as long as the original graphics are not changed). +The result one ore more a pairs of files - a texture in png format and a corresponding atlas file. +If you have a single Imagesfolder, the default such pair is named game.png/game.atlas. +For your players, the individual images aren't important - only the combined images actually register to the game, so you need to include them in your repository and keep them up to date. +We still recommend including the originals in a mod, so other developers running from source can access them. +With original images included, you can use a development environment using git, have it linked to your repository, while using a symlink to allow Unciv to see the mod - and pack your atlas for you on every launch. +If you're developing your mod on an Android version of Unciv (not recommended!) you won't be able to generate these packed files directly.

+

Ways to pack texture atlases

+
    +
  • Texture atlases CANNOT BE PACKED on Android (technical reason: TexturePacker uses java.awt to do heavy lifting, which is unavailable on Android 0_0)
  • +
  • Launch the desktop version with your mod (your mod's main folder is a subfolder of the game's "mods" folder, or symlinked there). This uses the packing methods documented here.
  • +
  • You can ask someone in the Discord server to help you out.
  • +
  • You can use external tools, e.g. gdx-texture-packer-gui. Utmost care needs to be taken that the files can be discovered by Unciv and internal relative paths are correct.
  • +
  • The Unciv repo itself has a feature that can pack images on github runners
  • +
+

Multiple texture atlases

+

If your mod has lots of images (or large ones), the textures might 'spill' into additional texture files - 2048x2048 is the limit for a single texture pack. You will see a game2.png, possibly a game3.png or more appear. +This is not good for performance, which is why the base game controls which kinds of images go together into one texture(+atlas). +This works for mods, too: Create not only one Images folder, but several, the additional ones named "Images.xyz", where xyz will become the filename of the additional texture file (So don't use both Images and Images.game - those will clash). Look at the Unciv base game to get a better idea how that works. +To minimize texture swaps, try to group them by the situation where in the game they are needed. You can distibute by folder, but having the same subfolders under several "Images.xyz" and distributing the images between them will also work.

+

A file Atlases.json (uppercase 'A') in the mod root (not in Images or in jsons) controls which atlases to load, which in turn control which texture (.png) files are read. +This file is automatically created by the built-in packer. Only the game.atlas file is read by default for backward compatibility. +If you use external tools and multiple atlases, you will need to maintain this file yourself - it is a simple json array of strings, each a file name without the .atlas extension (saved as UTF-8 without byte order mark).

+

Texture packer settings

+

The texture packers built into Unciv will look for a TexturePacker.settings file in each Images directory (not under jsons). +With this file you can tune the packer - e.g. control pixel interpolation filters. +It is a json of a Gdx TexturePacker.Settings instance. +The default settings are as shown in the Gdx documentation linked above if you do supply a settings file, but without such a file, some fields have different defaults. +To get these changed defaults, start with the following as base for your custom TexturePacker.settings file:

+

{
+    "fast": true,
+    "combineSubdirectories": true,
+    "maxWidth": 2048,
+    "maxHeight": 2048,
+    "paddingX": 8,
+    "paddingY": 8,
+    "duplicatePadding": true,
+    "filterMin": "MipMapLinearLinear",
+    "filterMag": "MipMapLinearLinear",
+}
+
+(change "filterMag" to "Linear" if your atlas name will end in "Icons".)

+

Texture atlas encoding

+

Due to certain circumstances, please make sure names and paths that will be mapped in an atlas use only ascii. Not all parts of the loader enforce strict UTF-8 usage, sorry. +Symptoms if you fail to heed this: mod works on a Chinese Windows box but not on a western one or vice-versa, or mod works on a Chinese Windows box but not a Chinese Linux box or vice-versa, or mod works on a Chinese Windows box with default settings but not on the same box with "Use unicode UTF-8 for worldwide language support" turned on. +This does not technically apply to the atlas name itself when multiple atlases are used (the xyz part in "Images.xyz"), but we nevertheless recommend the same rule for consistency.

+

Permanent audiovisual mods

+

The following chapters describe possibilities that will work while a mod is active. +It is either selected for the current game (during new game creation, cannot be changed after that for saved games), meaning all its rules and resources will be used. +Or it is marked as 'Permanent audiovisual mod' in the mod manager (you must select it in the 'installed' column to get the checkbox). +In that case only graphics and audio will be active, the rule changes will be ignored (if it contains any) unless the first way is also used. +Note that this feature includes graphics or sounds from the selected mod in all games, even those started before installing the mod. +Repeat: In case of a mod bringing both changed rules and audiovisuals, the 'permanent' feature will include only the media on all games, to use the rules you will still need to select the mod for a new game.

+

Note that the Mod author can (and often should) control whether the checkbox appears using ModOptions uniques.

+

Override built-in graphics

+

If a mod supplies an image with the same name and path as one included in the base game (and its atlas is up to date), and the mod is active, the mod's graphics will be used instead of the built-in one.

+

For example, if you include a file named "Images/OtherIcons/Link.png" in your mod, you will be overriding the little chain links icon denoting linked lines in Civilopedia. The first part of the path is not relevant for overriding, it controls which of a set of atlas files will carry the image, but for selection in the game only the rest of the path is relevant. So, to override "Images.Tech/TechIcons/Archery.png" you could place your image as "Images/TechIcons/Archery.png" and it would work because the "TechIcons/Archery" part is the key.

+

Please note, as for adding items, your graphics should keep the size and color choices of the original, or the result may be surprising, e.g. when the game tries to tint such an image.

+

Supply additional graphics

+

You will need to supply the graphics for new elements - a new unit needs its icon just as a new nation does. The rules are:

+
    +
  • The path and name of the image file need to conform to the rule: Image[.AtlasName]/Type-specific/Objectname.png (Type-specific means "TechIcons" for a Technology, "NationIcons" for a Nation and so on. See vanilla game folders. Objectname is the exact name as defined in json, before translation.)
  • +
  • All path parts are case sensitive.
  • +
  • Unit Pixel sprites and Tilesets follow special rules.
  • +
  • If UnitIcons/<UnitName>.png does not exist, we fall back to UnitTypeIcons/<UnitType>.png - this allows setting a single image for an entire type of units without fiddling with each one
  • +
  • Promotions can be named "[Unitname] ability". In such a case, if UnitIcons/Unitname.png exists it will fall back to that unit icon when UnitPromotionIcons/Unitname ability.png is missing.
  • +
  • Promotions can be named "Something I" (or " II" or " III"). The suffix will be removed and painted as little stars, only the base UnitPromotionIcons/Something.png will be loaded.
  • +
  • The special rules for promotions can be combined, e.g. "[Warrior] ability III" will fall back to the Warrior unit icon and paint 3 Stars on it.
  • +
+

Additionally, there there are some kinds of images where the game has display capability but does not supply graphics itself, as described in the next paragraphs:

+

Adding custom Fonts

+

You can add custom .ttf fonts into the game: place .ttf file inside of /fonts/ directory of your mod. The font you have added will be visible and choosable in Options-Advanced tab at the top of font list as <fontname> (<modname>).

+

All fonts are rendered by default at 50 pixel size and rescaled later for the game's needs. Currently fonts are NOT mipmapped on minification.

+

Overriding special characters

+

The textures in the EmojiIcons subfolder and some others are mapped into the font at specific codepoints. They are used by the game, can be used in any text of a mod, and can be overridden by mod textures. +Additionally, some code points are normally provided by the chosen system font, but have EmojiIcons names that will override the font glyph if a mod supplies them (marked 'optional' in the table below). +Note textures provided for such codepoints do respect aspect ratio, they do not need to be square like many built-in icons are!

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymbolCodepointUnicode nameTexture pathOptional
U+26CFpickEmojiIcons/Automate
U+266Aeighth noteEmojiIcons/Culture
U+2620skull and crossbonesEmojiIcons/Death
U+262Epeace symbolEmojiIcons/Faith
U+2042asterismEmojiIcons/Food
¤U+00A4currency signEmojiIcons/Gold
U+266Csixteenth noteEmojiIcons/Great Artist
U+2692hammerEmojiIcons/Great Engineer
U+26E4pentagramEmojiIcons/Great General
U+2696scaleEmojiIcons/Great Merchant
U+269BatomEmojiIcons/Great Scientist
U+2323smileEmojiIcons/Happiness
U+221EinfinityEmojiIcons/Infinity*
U+2699gearEmojiIcons/Production
U+237Ebell symbolEmojiIcons/Science
U+FFEAhalfwidth upwards arrowEmojiIcons/SortedAscending*
U+25C9fisheyeEmojiIcons/SortedByStatus*
U+231AwatchEmojiIcons/SortedByTime*
U+FFEChalfwidth upwards arrowEmojiIcons/SortedDescending*
U+272Fpinwheel starEmojiIcons/Star*
U+23F3hourglassEmojiIcons/Turn
U+2170small roman numeral oneMayaCalendar/0
U+2171small roman numeral twoMayaCalendar/1
U+2172small roman numeral threeMayaCalendar/2
U+2173small roman numeral fourMayaCalendar/3
U+2174small roman numeral fiveMayaCalendar/4
U+2175small roman numeral sixMayaCalendar/5
U+2176small roman numeral sevenMayaCalendar/6
U+2177small roman numeral eightMayaCalendar/7
U+2178small roman numeral nineMayaCalendar/8
U+2179small roman numeral tenMayaCalendar/9
U+217Asmall roman numeral elevenMayaCalendar/10
U+217Bsmall roman numeral twelveMayaCalendar/11
U+217Csmall roman numeral fiftyMayaCalendar/12
U+217Dsmall roman numeral one hundredMayaCalendar/13
U+217Esmall roman numeral five hundredMayaCalendar/14
U+217Fsmall roman numeral one thousandMayaCalendar/15
U+2180roman numeral one thousand cdMayaCalendar/16
U+2181roman numeral five thousandMayaCalendar/17
U+2182roman numeral ten thousandMayaCalendar/18
U+2183roman numeral reversed one hundredMayaCalendar/19
U+0DBAsinhala letter yayannaMayaCalendar/Baktun
U+0DB9sinhala letter amba bayannaMayaCalendar/Katun
U+0DB8sinhala letter mayannaMayaCalendar/Tun
U+27A1black rightwards arrowStatIcons/Movement
U+2026horizontal ellipsisStatIcons/Range
U+2021double daggerStatIcons/RangedStrength
U+2020daggerStatIcons/Strength
+

Adding Wonder Splash Screens

+

You can add wonder images to mods and they'll be displayed instead of the standard icon when a wonder is finished. The image needs to be a .png and 2:1 ratio so for example 200x100 px.

+

Add the images to /Images/WonderImages/. They need to be named according to the name field in Buildings.json, so for example "Temple of Artemis.png" or "Stonehenge.png"

+

Remember, to be compatible with mobile devices, a fresh atlas needs to be generated including these.

+

Adding Leader Portraits

+

The base game comes without Leader Portraits, but is able to display them in greetings, Civilopedia, diplomacy screens, or the nation picker. A mod can supply these, by adding their images to /Images/LeaderIcons/. The file name must correspond exactly with the leader name of a nation as defined in Nations.json, or they will be ignored.

+

These work best if they are square, between 100x100 and 256x256 pixels, and include some transparent border within that area.

+

For example, here is mod showing how to add leader portraits, which can complement the base game.

+

Adding Portraits

+

The base game uses flat icons, surrounded with colored circles as backgrounds (e.g. for units to fit the civilization's flag colors), to denote entities such as: units, buildings, techs, resources, improvements, religions, promotions, uniques, unit actions and nations in the UI. A mod can supply "Portraits" - static images that will remain uncolored - by adding images to /Images/<entityType>Portraits/ (e.g. /Images/BuildingPortraits/, /Images/ResourcePortraits/, etc), which will be used in all UI elements (except for unit icons in the world map). The file name must correspond exactly with the unit/building/tech/resource/etc name defined in corresponding JSONs (e.g. Units.json, Buildings.json, TileResources.json, etc) or have the same name as the file they suppose to replace, or they will be ignored.

+

If mod supplies '/Images/Portraits/Background.png' images, they will be used as a background for corresponding portraits instead of default circle. Portraits and backgrounds work best if they are full RGB square, between 100x100 and 256x256 pixels, and include some transparent border within that area.

+

For example, here is mod showing how to add custom portraits, which can complement the base game.

+

Available <entityType>Portraits/ include:

+
    +
  • UnitPortraits
  • +
  • BuildingPortraits
  • +
  • TechPortraits
  • +
  • ResourcePortraits
  • +
  • ImprovementPortraits
  • +
  • UnitPromotionPortraits
  • +
  • UniquePortraits
  • +
  • NationPortraits
  • +
  • ReligionPortraits
  • +
  • UnitActionPortraits
  • +
+

Adding icons for Unit Types

+

The Unit Types as defined in UnitTypes.json have no icons in the base game, but Civilopedia can decorate their entries if you supply images named 'Images/UnitTypeIcons/.png'. +(while you're at it, you may override the default icon for the Unit Type category header - it's 'UnitTypes.png' in the same folder, or the icons used for the movement domains - 'DomainLand', 'DomainWater', 'DomainAir')

+

Adding icons for Beliefs

+

The individual Beliefs - as opposed to Belief types, as defined in Beliefs.json have no icons in the base game, but Civilopedia can decorate their entries if you supply images named 'Images/ReligionIcons/.png'. +Civilopedia falls back to the icon for the Belief type - as you can see in the base game, but individual icons have precedence if they exist.

+

Adding Victory illustrations

+

You can enable pictures for each of the Victories, illustrating their progress. That could be a Spaceship under construction, showing the parts you've added, or cultural progress as you complete Policy branches. They will be shown on a new tab of the Victory Screen.

+

For this, you need to create a number of images. In the following, <> denote names as they appear in VictoryTypes.json, untranslated, and these file names (like any other in Unciv) are case-sensitive. All files are optional, except Background as noted:

+
    +
  • VictoryIllustrations/<name>/Background.png - this determines overall dimensions, the others must not exceed its size and should ideally have identical size. Mandatory, if this file is missing, no illustrations will be shown for this Victory Type.
  • +
  • VictoryIllustrations/<name>/Won.png - shown if you (the viewing player) won this Victory.
  • +
  • VictoryIllustrations/<name>/Lost.png - shown if a competitor won this Victory - or you have completed this Victory, but have won a different one before.
  • +
  • VictoryIllustrations/<name>/<milestone>.png - One image for each entry in the milestones field without an [amount], name taken verbatim but without square brackets, spaces preserved.
  • +
  • VictoryIllustrations/<name>/<milestone> <index>.png - For entries in the milestones field with an [amount], one image per step, starting at index 1.
  • +
  • VictoryIllustrations/<name>/<component>.png - One image for each unique entry in the requiredSpaceshipParts field, that is, for parts that can only be built once. Spaces in unit names must be preserved.
  • +
  • VictoryIllustrations/<name>/<component> <index>.png - For parts in the requiredSpaceshipParts field that must be built several times, one per instance. Spaces in unit names must be preserved, and there must be one space between the name and the index. Indexes start at 1.
  • +
+

Remember - these are logical names as they are indexed in your atlas file, if you let Unciv pack for you, the VictoryIllustrations folder should be placed under <mod>/Images - or maybe <mod>/Images.Victories if you want these images to occupy a separate Victories.atlas (Do not omit the Images folder even if left empty, the texture packer needs it as marker to do its task).

+

That's almost all there is - no json needed, and works as 'Permanent audiovisual mod'. The Background image is the trigger, and if it's present all part images must be present too, or your spaceship crashes before takeoff, taking Unciv along with it. That was a joke, all other images are optional, it could just look boring if you omit the wrong ones.

+

As for "almost" - all images are overlaid one by one over the Background one, so they must all be the same size. Except for Won and Lost - those, if their condition is met, replace the entire rest, so they can be different sizes than the background. The part images are overlaid over the background image in no guaranteed order, therefore they should use transparency to avoid hiding parts of each other.

+

One way to create a set is to take one final image, select all parts that should be the centerpiece itself not background (use lasso, magic wand or similar tools, use antialiasing and feathering as you see fit), copy and paste as new layer. Then apply desaturation and/or curves to the selection on the background layer to only leave a hint of how the completed victory will look like. Now take apart the centerpiece - do a selection fitting one part name, copy and paste as new layer (in place), then delete the selected part from the original centerpiece layer. Rinse and repeat, then export each layer separately as png with the appropriate filenames. +There's no suggested size, but keep in mind textures are a maximum of 2048x2048 pixels, and if you want your images packed properly, several should fit into one texture. They will be scaled down if needed to no more than 80% screen size, preserving aspect ratio.

+

Sounds

+

Standard values are below. The sounds themselves can be found here.

+
    +
  • arrow, artillery, bombard, bombing, cannon, chimes, choir, click, coin, construction, elephant, fortify, gdrAttack, horse, jetgun, machinegun, metalhit, missile, nonmetalhit, nuke, paper, policy, promote, setup, shipguns, shot, slider, swap, tankshot, throw, torpedo, upgrade, whoosh.
  • +
+

Mods can add their own sounds, as long as any new value in attackSound has a corresponding sound file in the mod's sound folder, using one of the formats mp3, ogg or wav (file name extension must match codec used). Remember, names are case sensitive. Small sizes strongly recommended, Unciv's own sounds use 24kHz joint stereo 8-bit VBR at about 50-100kBps.

+

Override built-in sounds

+

This works like graphics, except no atlas is involved. E.g. you include a sounds/Click.mp3, it will play instead of the normal click sound. These files must stay short and small. A sound larger than 1MB when uncompressed may break or not play at all on mobile devices. Unciv tries to standardize on 24kHz sample rate, joint stereo, low-bitrate VBR (-128kbps) mp3. Only mp3 and ogg formats will be recognized (but an existing mp3 can be overridden with an ogg file).

+

Supply additional music

+

Sound files (mp3 or ogg) in a mod /music folder will be recognized and used when the mod is active. Except for context-specific music as described in the following paragraphs, tracks will play randomly from all available tracks (with a little bias to avoid close repetition of tracks). There is no overriding - a "thatched-villagers.mp3" in a mod will play in addition to and with the same likelihood as the file that the base game offers to download for you. There is no hard technical limit on bitrate or length, but large bandwidth requirements may lead to stuttering (The end of a "next turn", right before the world map is updated, and with very large maps, is the most likely to cause this).

+

Context-sensitive music: Overview

+

The Music Controller will generally play one track after another, with a pause (can be changed in options) between. While the "Leave game?" confirmation dialog is opened playback will fade out and pause and can resume when it is closed.

+

There are various 'triggers' in the game code initiating a choice for a new track. The new track will, if necessary, fade out the currently playing track quickly before it starts playing. Track choice involves context provided by the trigger and a random factor, and an attempt is made to not repeat any track until at least eight others have played.

+

Mods can provide their own music folder, and if they are active its contents will be treated exactly the same as those in the main music folder. Mods should control usage of their tracks by careful choice of file name. Mod developers can watch console output for messages logging track choice with trigger parameters or loading errors.

+

One track is special: The Thatched Villagers (see also credits.md). The game is able to download it if the music folder is empty, and it is played when the music volume slider is used. It is also a fallback track should certain problems occur (a broken file, however, will shut down the player until another trigger happens).

+

Context-sensitive music: List of Triggers

+

Triggers indicate context (call it intent, mood, whatever, it doesn't matter) by optionally providing a prefix and/or suffix to match against the file name. There are a few flags as well influencing choice or behaviour - one flag function is to make prefix or suffix mandatory, meaning if no available file matches the track chooser will do nothing. Otherwise, a next track will always be chosen from the available list by sorting and then picking the first entry. Sorting is done by in order of precedence: Prefix match, Suffix match, Recently played, and a random number. Therefore, as currently no triggers have an empty prefix, files matching none of the prefixes will never play unless there are less than eight files matching the requested prefix.

+

The current list of triggers is as follows:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DescriptionPrefix[^M]Suffix[^X]Flags
Automatic next-track[^0]Ambient
Launch game[^1]Menu
Every 10th turn(player civ name)[^M]Peace or War[^2][^F]
New game: Select a mod(mod name)[^M]Theme[^S]
New game: Pick a nation for a player(nation name)[^M]Theme or Peace[^S]
Diplomacy: Select player(nation name)[^M]Peace or War[^3][^S]
First contact[^4](civ name)[^M]Theme or Peace[^X]
War declaration[^5](civ name)[^M]War[^X]
Civ defeated(civ name)Defeat[^X]
Player wins(civ name)Victory[^X]
Golden Age(civ name)[^M]Golden[^X]
Wonder built(wonder name)[^M]Wonder[^X]
Tech researched(tech name)[^M]Researched[^X]
Map editor: Select nation start location(nation name)[^M]Theme[^S]
Options: Volume slider or Default track downloaded[^D]
Music controls (Options or from Menu) Next trackAmbient
+

Legend:

+
    +
  • [^N]: Not implemented
  • +
  • [^M]: Prefix must match. If no matching file is found, the trigger will do nothing.
  • +
  • [^X]: Suffix must match. If no matching file is found, the trigger will do nothing.
  • +
  • [^S]: Stop after playback. No automatic next choice.
  • +
  • [^F]: Slow fadeout of replaced track.
  • +
  • [^D]: Always plays the default file.
  • +
  • [^0]: Whenever a track finishes and the configured silence has elapsed, an 'Ambient' track without any context is chosen. Also triggered by 'resume' (e.g. switching to another app and back on Android)
  • +
  • [^1]: First opening of the Main Menu (or the initial language picker).
  • +
  • [^2]: Whether the active player is at war with anybody.
  • +
  • [^3]: According to your relation to the picked player.
  • +
  • [^4]: Excluding City States.
  • +
  • [^5]: Both in the alert when another player declares War on you and declaring War yourself in Diplomacy screen.
  • +
+

Supply Leader Voices

+

Sound files named from a Nation name and the corresponding text message's field name, +placed in a mod's voices folder, will play whenever that message is displayed. Nation name and message name must be joined with a dot '.', for example voices/Zulu.defeated.ogg.

+

Leader voice audio clips will be streamed, not cached, so they are allowed to be long - however, if another Leader voice or a city ambient sound needs to be played, they will be cut off without fade-out +Also note that voices for City-State leaders work only for those messages a City-state can actually use: attacked, defeated, and introduction.

+

Modding Easter eggs

+

Here's a list of special dates (or date ranges) Unciv will recognize: +|-----| +| AprilFoolsDay | +| DiaDeLosMuertos | +| Diwali | +| Easter | +| Friday13th | +| LunarNewYear | +| Passover | +| PrideDay | +| Qingming | +| Samhain | +| StarWarsDay | +| TowelDay | +| UncivBirthday | +| Xmas | +| YuleGoat |

+

... When these are or what they mean - look it up, if in doubt in our sources (😈).

+

An audiovisual Mod (which the user must then mark as permanent) can define textures named "EasterEggs/name", where name must correspond exactly to one from the table above, and index starts at 1 counting up. +Example: /Images/EasterEggs/Diwali1.png and so on. +Then, Unciv will display them as "floating art" on the main menu screen, on the corresponding dates. They will from time to time appear from off-screen, slide through the window, and disappear out the other side, with varying angles and speeds.

+

Notes: +- You can test this by launching the jar and including -DeasterEgg=name on the command line. +- In case of overlapping holidays, only one is chosen - and the "impact" of longer holidays is equalized by reducing the chance inversely proportional to the number of days. e.g. DiaDeLosMuertos is two days, so each Unciv launch on these days has 50% chance to show the egg. +- Unciv's "map-based" easter eggs work independently! +- No cultural prejudice is intended. If you know a nice custom we should include the date test for, just ask.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Making-a-new-Civilization/index.html b/Modders/Making-a-new-Civilization/index.html new file mode 100644 index 0000000000..682f45db51 --- /dev/null +++ b/Modders/Making-a-new-Civilization/index.html @@ -0,0 +1,1594 @@ + + + + + + + + + + + + + + + + + + + + + + + 'My first mod' - Making a new Civilization - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

'My first mod' - Making a new Civilization

+

By the end of this tutorial, you should have a working, generally-available mod that adds a new Civilization to the game

+

Create your repository

+
    +
  • Create a Github account, if you don't already have one
  • +
  • Go to the mod example
  • +
  • Click the green Use this template button - Create a new repository
  • +
  • Choose your repository name and click Create repository from template (keep setting on 'public'!)
  • +
  • Your new repository is now available!
  • +
+

Fill in your Nation info

+

Each civ has some basic information - what the civ name is, the leader's name, colors and city names.

+

In addition, each civ has flavor text when declaring war, intoduction etc.

+

All of these need to be filled in in jsons/Nations.json file - see here for the base game file for more examples

+

Get your Civ icon

+

Each civ has an icon, like the wreath for Rome, for instant identification.

+

All of these icons are white on a transparent background, and are 100x100 pixels - see icon considerations for details

+

You'll need to put your icon in the Images/NationIcons folder - you can navigate there and click Add file - Create a new file (top-right corner)

+

Test it out!

+
    +
  • Open Unciv
  • +
  • Click 'Mods' - 'Download mod from URL'
  • +
  • Copy-paste your repository's URL to the textbox, and click 'Download'
  • +
  • Exit the mod screen, and create a new game, selecting your mod - which will be under 'Extension mods' on the left
  • +
+

Congrats, your Civ is now fully playable!

+
+

Note

+

You currently won't see any images from this mod, since it has no texture atlas - see here for more details +If you're on Desktop, you can restart Unciv to generate this atlas and see the images

+
+

But this nation's abilities are exactly those of the base mod. To make it truly unique, we'll need to change some Uniques ;)

+

Adding unique units

+

Units are defined in the jsons/Units.json - for the base game file, see here file, with an icon in the UnitIcons folder.

+

The icons must be 200x200 pixels, white on transparent background - see icon considerations for details - and go in the Images/UnitIcons folder

+

Remember that these are unique units, so search for an existing unique unit to see how they replace their regular counterparts!

+

Adding unique buildings

+

Same as the units - info is in jsons/Buildings.json - for the base game file, see Buildings.json file and icons in the BuildingIcons folder, same rules for the icons apply (200x200 pixels, icon considerations)

+

Icons go in Images/BuildingIcons

+

Civ Unique

+

Check out our list of uniques to see all the cool special effects you can add to your civilization!

+

Make it searchable!

+

To list your mod in the Unciv Mods screen:

+
    +
  • Open your repository
  • +
  • Click the gear icon, to the right of the 'About' label (right side, top)
  • +
  • Under 'Topics', add 'unciv-mod'
  • +
  • 'Save changes'
  • +
+

Congrats, your mod will now be shown in the mods page!

+

The more stars your repo has, the higher towards the top it will appear, so start gaining fans :D

+

Icon considerations

+

ALL icons must be legally acceptable, meaning they either come from from open sources or you act according to their licence (for Creative Commons, for instance, you have to specify the source and the creator).

+

Icons directly from the base game belong to Firaxis, so I'm not sure we're legally allowed to use them - please use other sources!

+

One source I use constantly is The Noun Project - everything there is Creative Commons or open, so they can all be used!

+

Credits for icons should go in a Credits.md page.

+

What's next?

+

You have a working mod, now it's time to go wild!

+
    +
  • Add the atlas files to your repo so your users get images
  • +
  • Install Git locally, so you can change your files on your device and have those changes reflected in your repository
  • +
  • Expand the abilities of your civ by adding new uniques
  • +
  • Add new civs, buildings or units
  • +
  • Expand into other game objects by exploring the rest of the mod file structure
  • +
  • Try creating a base ruleset from this template
  • +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Mod-file-structure/1-Overview/index.html b/Modders/Mod-file-structure/1-Overview/index.html new file mode 100644 index 0000000000..47b3757ddd --- /dev/null +++ b/Modders/Mod-file-structure/1-Overview/index.html @@ -0,0 +1,1538 @@ + + + + + + + + + + + + + + + + + + + + + + + Mod file structure Overview - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Mod file structure Overview

+

These pages are a work in progress. Information they contain may be incomplete.

+

The JSON files that make up mods can have many different fields, and as not all are used in the base game, this wiki page will contain the full information of each. It will also give a short explanation of the syntax of JSON files.

+

Table of Contents

+ +

General Overview of JSON files

+

Resources: json.org, ISO standard

+

Almost all Unciv JSON files start with a "[" and end with a "]". In between these are different objects of the type you are describing, each of which is contained between a "{" and a "}". For example, a very simple units.json may look like:

+
[
+    {
+        "name": "Warrior",
+        "cost": 16
+    },
+    {
+        "name": "Spearman",
+        "cost": 24,
+        "promotions": ["Shock I", "Drill I"]
+    }
+]
+
+

This file contains two unit objects, one for a warrior and one for a spearman. These objects have different attributes, in this case "name", "cost" and "promotions". All these attributes have a certain type, a String (text) for "name", an Integer for "cost" and a List of Strings for "promotions".

+

There are different types of attributes:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typenotes
StringA word or sentence. Should be between double quotes (")
IntegerA number. Can be both positive or negative. Should not be between quotes
BooleanA value that can either be 'true' or 'false'. Should not be between quotes
List of [type]If multiple values could apply (such as with the promotions above), they should be put inside a list. Each element of the list should be written like a normal attribute, separated by commas, and enclosed between square braces. E.g.: ["Shock I", "Shock II"] or [1, 2, 3].
ObjectThe most complicated type of attribute. An object is comprised of multiple attributes, each of which again has a type. These attributes have a key (the part before the ":") and a value (the part behind it). For an example, see below.
+

Example of a Buildings.json adding a new "Cultural Library" building which gives +50% science and +50% culture:

+
[
+    {
+        "name": "Cultural Library"
+        "percentStatBonus" : {"science": 50, "culture": 50}
+    }
+]
+
+

The keys in this example are "science" and "culture", and both have the value "50".

+

In some sense you can see from these types that JSON files themselves are actually a list of objects, each describing a single building, unit or something else.

+

Uniques

+

"Uniques" are a label used by Unciv for extensible and customizable effects. Nearly every "ruleset object" allows a set of them, as a List with the name "uniques".

+

Every Unique follows a general structure: Unique type defining name [placeholder] more name [another placeholder] <condition or trigger> <condition or trigger>... +The entire string, excluding all <>-delimited conditionals or triggers with their separating blanks, and excluding the placeholders but not their [] delimiters, are used to look up the Unique's implementation. +The content of the optional [placeholder]s are implementation-dependant, they are parameters modifying the effect, and described in Unique parameters. +All <condition or trigger>s are optional (but if they are used the spaces separating them are mandatory), and each in turn follows the Unique structure rules for the part between the <> angled brackets, including possible placeholders, but not nested conditionals.

+

Example: "uniques":["[+1 Gold] <with a garrison>"] on a building - does almost the same thing as the "gold":1 attribute does, except it only applies when the city has a garrison. In this example, [] and with a garrison are the keys Unciv uses to look up two Uniques, an effect (of type Stats) and a condition (of type ConditionalWhenGarrisoned).

+

All Unique "types" that have an implementation in Unciv are automatically documented in uniques. Note that file is entirely machine-generated from source code structures. Also kindly note the separate sections for conditionals and trigger conditions. +Uniques that do not correspond to any of those entries (verbatim including upper/lower case!) are called "untyped", will have no direct effect, and may result in the "Ruleset Validator" showing warnings (see the Options Tab "Locate mod errors", it also runs when starting new games). +A legitimate use of "untyped" Uniques is their use as markers that can be recognized elsewhere in filters (example: "Aircraft" in the vanilla rulesets used as Unit filter). +This use is recognized by the "Ruleset Validator" and not flagged as invalid - but a filtering Unique must also use no placeholders or conditionals to pass the test. +If you get the "not found in Unciv's unique types" warning, but are sure you are using a correct filtering Unique, please look for exactly identical spelling in all places, including upper/lower case. +Note: Currently some mods use untyped Uniques not for filtering purposes, but as purely informational tool. The team will try to think of an approach for that use that won't trigger validation warnings without reducing validation quality, but as of now, those are unavoidable.

+

Information on JSON files used in the game

+

Many parts of Unciv are moddable, and for each there is a separate json file. There is a json file for buildings, for units, for promotions units can have, for technologies, etc. The different new buildings or units you define can also have lots of different attributes, though not all are required. Below are tables documenting all the different attributes everything can have. Only the attributes which are noted to be 'required' must be provided. All others have a default value that will be used when it is omitted.

+

The individual files are described on separate pages.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Mod-file-structure/2-Civilization-related-JSON-files/index.html b/Modders/Mod-file-structure/2-Civilization-related-JSON-files/index.html new file mode 100644 index 0000000000..7244e98d92 --- /dev/null +++ b/Modders/Mod-file-structure/2-Civilization-related-JSON-files/index.html @@ -0,0 +1,2399 @@ + + + + + + + + + + + + + + + + + + + + + + + Civilization-related JSON files - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Civilization-related JSON files

+

Beliefs.json

+

link to original

+

This file contains the beliefs that can be chosen for religions in your mod.

+

Each belief has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
typeEnumRequiredType of belief. Value must be Pantheon, Founder, Follower or Enhancer
uniquesList of StringsemptyList of unique abilities this belief adds to cities following it
civilopediaTextListemptySee civilopediaText chapter
+

Buildings.json

+

link to original

+

This file contains all the buildings and wonders you want to use in your mod.

+

Each building has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
costInteger-1Amount of production required to build the building. If -1, the buildingCost from requiredTech column is used
<stats>Float0Per-turn yield produced by the building
maintenanceInteger0Maintenance cost of the building
isWonderBooleanfalseWhether this building is a global wonder
isNationalWonderBooleanfalseWhether this building is a national wonder
requiredBuildingStringnoneA building that has to be built before this building can be built. Must be in Buildings.json
requiredTechStringnoneThe tech that should be researched before this building may be built. Must be in Techs.json
requiredResourceStringnoneThe resource that is consumed when building this building. Must be in TileResources.json
requiredNearbyImprovedResourcesList of StringsemptyThe building can only be built if any of the resources in this list are within the borders of this city and have been improved. Each resource must be in TileResources.json
replacesStringnoneThe name of a building that should be replaced by this building. Must be in Buildings.json
uniqueToStringnoneIf supplied, only the nation with this name can build this building. Must be in Nations.json
cityStrengthInteger0Strength bonus the city in which this building is built receives
cityHealthInteger0Health bonus the city in which this building is built receives
hurryCostModifierInteger0When this building is bought using gold or faith, the price is increased by this much percent
quoteStringnoneIf this building is a (national) wonder, this string will be shown on the completion popup
uniquesList of StringsemptyList of unique abilities this building has
replacementTextForUniquesStringnoneIf provided, this string will be shown instead of all of the uniques
percentStatBonusObjectnonePercentual bonus for stats provided by the building. Same format as specialized stats (numbers are in percent. i.e. [30] represents 30% bonus to a stat)
greatPersonPointsObjectnoneGreat person points by this building generated per turn. Valid keys are the names of units (Great Scientist, Warrior, etc.), valid values are Integers
specialistSlotsObjectnoneSpecialist slots provided by this building. Valid keys are the names of specialists (as defined in Specialists.json), valid values are Integers, the amount of slots provided for this specialist
civilopediaTextListemptySee civilopediaText chapter
+

Nations.json

+

Link to original

+

This file contains all the nations and city states, including Barbarians and Spectator.

+

Each nation has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
leaderNameStringnoneOmit only for city states! If you want LeaderPortraits, the image file names must match exactly, including case
styleStringnoneModifier appended to pixel unit image names
cityStateTypeStringnoneDistinguishes major civilizations from city states (must be in CityStateTypes.json)
startBiasList of stringsemptyZero or more of: terrainFilter or "Avoid [terrainFilter]". [^S]
preferredVictoryTypeStringNeutralThe victory type major civilizations will pursue (need not be specified in VictoryTypes.json)
personalityStringnoneThe name of the personality specified in Personalities.json
favoredReligionStringnoneThe religion major civilization will choose if available when founding a religion. Must be in Religions.json
startIntroPart1StringnoneIntroductory blurb shown to Player on game start...
startIntroPart2Stringnone... second paragraph. NO "TBD"!!! Leave empty to skip that alert.
declaringWarStringnoneAnother greeting, voice hook supported [^V]
attackedStringnoneAnother greeting, voice hook supported [^V]
defeatedStringnoneAnother greeting, voice hook supported [^V]
introductionStringnoneAnother greeting, voice hook supported [^V]
neutralHelloStringnoneAnother greeting, voice hook supported [^V]
hateHelloStringnoneAnother greeting, voice hook supported [^V]
tradeRequestStringnoneAnother greeting, voice hook supported [^V]
innerColorList of 3× IntegerblackRGB color for outer ring of nation icon
outerColorList of 3× IntegerRequiredRGB color for inner circle of nation icon
uniqueNameStringnoneDecorative name for the special characteristic of this nation
uniqueTextStringnoneReplacement text for "uniques". If empty, uniques are listed individually
uniquesListemptyList of unique abilities this civilisation has
citiesListemptyCity names used sequentially for newly founded cities. Required for major civilizations and city states
civilopediaTextListemptySee civilopediaText chapter
+

[^S]: A "Coast" preference (unless combined with "Avoid") is translated to a complex test for +coastal land tiles, tiles next to Lakes, river tiles or near-river tiles, and such civs are +processed first. Other startBias entries are ignored in that case. +Other positive (no "Avoid") startBias are processed next. Multiple positive preferences are treated +equally, but get no "fallback". +Single positive startBias can get a "fallback" region if there is no (or no more) region with that +primary type: any leftover region with as much of the specified terrain as possible will do. +Multiple "Avoid" entries are treated equally (and reduce chance for success - if no region is left +avoiding all specified types that civ gets a random one). +When combining preferred terrain with "Avoid", the latter takes precedence, and preferred terrain +only has minor weight when choosing between regions that are not of a type to avoid. +These notes are only valid when playing on generated maps, loaded maps from map editor get no " +regions" and startBias is processed differently (but you can expect single-entry startBias to work +best). +[^V]: See Supply Leader Voices

+

Personalities.json

+

This file contains all Personalities for computer players.

+

Each personality has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
preferredVictoryTypeStringNeutralThe victory type major civilizations will pursue (need not be specified in VictoryTypes.json)
<stats>, <behaviors>Float5Amount of focus on the stat the computer player will have. Typically ranges from 0 (no focus) to 10 (double focus)
prioritiesObjectnonePriorities for each policy branch [^B]
uniquesListemptyList of unique abilities this personality has
civilopediaTextListemptySee civilopediaText chapter
+

[^B]: Similar to policy priorites The "priorities" object defines the priority +major civilizations' AI give to a policy branch. The AI chooses the policy branch with the highest +number for their preferred victory type. If two or more candidate branches have the same priority, +the AI chooses a random branch among the candidates.

+

The object maps policy branches to priority values for the major civilization using the policy +branches name and integers. Any branches not listed have a default value of 0

+

The code below is an example of a valid "priorities" definition.

+
"priorities": {
+    "Tradition": 30,
+    "Liberty": 20,
+    "Honor": 10
+}
+
+

Personality Behaviours

+

Personality Behaviours are not implemented yet and their names may change. Using them before they +are ready might make the mod unplayable. +[//]: # (There are 6 defining behaviours that influnce an AI Civilization's behaviour. A higher +value means they will behave more like the attribute.)

+

(- Military: Determines how much does the civilization prioritizes building a military, but not necessarily using it. A higher value means more focus on military, a lower value means it is likely more peaceful.)

+

(- Agressive: Determines how the civilization uses it's units while at war and which buildings they prioritise. A higher value means the civilization is more aggressive, a lower value means it is more defensive.)

+

(- War: Determines how likely the civilization is to declare war. A 0 means the civ won't declare war at all)

+

(- Commerce: Determines how open the civilization is to trade, value open borders, and liberate city-states. A higher value means more trading frequency even with civilizations they don't like.)

+

(- Diplomacy: Determines how likely the civilization is to declare friendship, a defensive pact, peace treaty, or other diplomatic actions.)

+

(- Loyal: Determines how much the civilization values a long-lasting alliance, how willing they are to join wars with them, and how much they despise other unreliable civilizations.)

+

(- Expansion: Determines how focused the civilization is on founding or capturing new cities. A lower value means they might focus on culture more.)

+

CityStateTypes.json

+

Link to original

+

This optional file is used for defining new types of city states. These types determine the benefits +major civilizations gets when they befriend or ally the city state with influence. If the file is +ommitted, the following are automatically added: +Cultured, Maritime, Mercantile, Militaristic, Religious.

+

Each city state type has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
friendBonusUniquesList of StringsemptyList of unique abilities granted to major civilizations when friends with this city state
allyBonusUniquesList of StringsemptyList of unique abilities granted to major civilizations when allied to city state
colorList of 3× Integer[255, 255, 255]RGB color of text in civilopedia
+

Policies.json

+

Link to original

+

This file contains all the available social policies that can be "bought" with culture.

+

They are organized in 'branches', each branch has an 'opener', one or more 'member' policies, and +a 'finisher'. Therefore this file is organized using two levels - branch and member policy.

+

The properties of the 'opener' are defined with the branch level, while the 'finisher' is an entry +on the member level which must be named as branch name + " Complete", case sensitive. For +example, the finisher of a policy branch "Tradition" will have the name "Tradition Complete".

+

Branch structure

+

Each policy branch has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
eraStringRequiredUnlocking era as defined in Eras.json
prioritiesObjectnonePriorities for each victory type, see here
uniquesListemptyList of unique abilities this policy branch grants upon adopting it
policiesListemptyList of member policies and branch 'finisher' - pay attention to the nesting of {} and []
+

Member policy structure

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
rowIntegerRequiredPlacement in UI, each unit approximately half the icon size
columnIntegerRequiredPlacement in UI, each unit approximately half the icon size
requiresListemptyList of prerequisite policy names
uniquesListemptyList of unique abilities this policy member grants upon adopting it
+

Branch finisher structure

+ + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
uniquesListemptyList of unique abilities this finisher grants upon adopting all the policy members in the branch
+

Branch priorities

+

The "priorities" object defines the priority major civilizations' AI give to a policy branch. The AI +chooses the policy branch with the highest sum of the peferred victory type listed here and the +number flisted in the personality's priority. If two or more candidate branches have the same +priority, the AI chooses a random branch among the candidates.

+

The object maps victory types to priority values for the major civilization using strings and +integers. If the preferred victory type is not specified, the default priority value is set to 0.

+

The code below is an example of a valid "priorities" definition.

+
"priorities": {
+"Neutral": 0,
+"Cultural": 10,
+"Diplomatic": 0,
+"Domination": 0,
+"Scientific": 10
+}
+
+

Quests.json

+

Link to original

+

This file contains the quests that may be given to major civilizations by city states.

+

Each quest has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequiredDefines criteria of quest, see below
descriptionStringRequiredDescription of the quest shown to players. Can add extra information based on name, see below
typeEnumIndividualIndividual or Global
influenceFloat40Influence reward gained on quest completion
durationInteger0Maximum number of turns to complete the quest. If 0, there is no turn limit
minimumCivsInteger1Minimum number of Civs needed to start the quest. It is meaningful only for type = Global
weightForCityStateTypeObjectnoneRelative weight multiplier to this quest for each city state type or city state personality (Friendly, Neutral, Hostile, Irrational), see below
+

Quest name

+

The name of the quest defines the criteria for the quest. If they are not defined in the predefined +enum, they will have no behavior. In the description, square brackets [] in the description of the +quest is replaced with extra information (except for Invest). The list of predefined quest names +are as follows:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameCriteriaAdditional info
RouteConnect the city state to the major civilization's capital using roads or railways
Clear Barbarian CampDestroy the target barbarian camp
Construct WonderConstruct the target wondertarget wonder
Connect ResourceConnect the target resource to the major civilization's trade networktarget tileResource
Acquire Great PersonAcquire the target great persontarget greatPerson
Conquer City StateDefeat the target city statetarget cityState
Find PlayerMeet the target major civilizationtarget civName
Find Natural WonderFind the target natural wondertarget naturalWonder
Give GoldDonate gold to the city state (amount does not matter)civName "bully" for city state
Pledge to ProtectPledge to protect city statecivName "bully" for city state
Contest CultureBe the major civilization with the highest increase to culture during the durationmajor civilization's cultureGrowth
Contest FaithBe the major civilization with the highest increase to faith during the durationmajor civilization's faithGrowth
Contest TechnologyBe the major civilization with the most technologies researched during the durationmajor civilization's techsResearched
InvestDonating gold yield extra Influence based on value providedIMPORTANT: value in square brackets is the extra influence in percent. i.e. [50] means 50%
Bully City StateDemand tribute from the target city statetarget city state
Denounce CivilizationDenounce the major civilization which "bullied" the city statecivName "bully" for city state
Spread ReligionSpread major civilization's religion to the city statemajor civilization's religionName
+

Quest weight

+

The "weightForCityStateType" object determines the quest's weight multiplier. When a city state +initiates a quest, the initial weight is 1, and it is multiplied by values based +on city state type and personality (Friendly, Neutral, Hostile, Irrational). +The AI then randomly selects a quest based on the final weighted values.

+

The object maps city state type and personality to the weight multipliers for the city state using +strings to floats. If the preferred victory type is not found, the default multiplier is 1.

+

The code below is an example of a valid "weightForCityStateType" definition. In this case, a +friendly militaristic city state will be 0.4 (0.2 × 2) times as likely to pick this quest than a +quest with weight 1.

+
"weightForCityStateType": {
+"Hostile": 2,
+"Friendly": 0.2,
+"Militaristic": 2
+}
+
+

Religions.json

+

Link to original

+

This is just a list of Strings specifying all predefined religion names. Corresponding icons must +exist, that's all to it. After all, they're just containers for beliefs.

+

Specialists.json

+

Link to original

+

This file should contain a list of all possible specialists that citizens can be assigned to.

+

Each specialist has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
<stats>Float0Per-turn yield produced by the specialist
colorList of 3× IntegerRequiredColor of the image for this specialist
greatPersonPointsObjectnoneGreat person points generated by this specialist per turn. Valid keys are the names of units (Great Scientist, Warrior, etc.), valid values are Integers
+

Techs.json

+

Link to original

+

This file contains all the technologies that can be researched with science. It is organized into an +outer list of 'columns', which in turn contains one or more tech each.

+

Column structure

+

Each tech column has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
columnNumberIntegerRequiredHorizontal placement in the Tech Tree
eraStringRequiredDetermines era reached after researching any technologies in this column. Must be in Eras.json
techCostInteger0Default cost of the techs in this column
buildingCostIntegerRequiredDefault cost of buildings requiring this tech
wonderCostIntegerRequiredDefault cost of wonders requiring this tech
techsListRequiredList of techs - pay attention to the nesting of {} and []
+

Tech structure

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
rowInteger0Vertical placement in the Tech Tree, must be unique per column
costIntegerColumn techCostThe amount of science required to research this tech
prerequisitesList of StringsemptyA list of the names of techs that are prerequisites of this tech. Only direct prerequisites are necessary
quoteStringnoneA nice story presented to the player when they research this tech
uniquesList of StringsemptyList of unique abilities this technology grants
civilopediaTextListemptySee civilopediaText chapter
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Mod-file-structure/3-Map-related-JSON-files/index.html b/Modders/Mod-file-structure/3-Map-related-JSON-files/index.html new file mode 100644 index 0000000000..115eb423cf --- /dev/null +++ b/Modders/Mod-file-structure/3-Map-related-JSON-files/index.html @@ -0,0 +1,2042 @@ + + + + + + + + + + + + + + + + + + + + + + + Map-related JSON files - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Map-related JSON files

+

Terrains.json

+

Link to original

+

This file contains the base terrains, terrain features and natural wonders that can appear on the map.

+

Each terrain entry has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired[^A]
typeEnumRequiredLand, Water, TerrainFeature, NaturalWonder [^B]
occursOnList of StringsnoneOnly for terrain features and Natural Wonders: The baseTerrain it can be placed on
turnsIntoStringnoneOnly for NaturalWonder: optional mandatory base terrain [^C]
weightInteger10Only for NaturalWonder: relative weight of being picked by the map generator
<stats>Float0Per-turn yield or bonus yield for the tile
overrideStatsBooleanfalseIf true, a feature's yields replace any yield from underlying terrain instead of adding to it
unbuildableBooleanfalseIf true, nothing can be built here - not even resource improvements
impassableBooleanfalseNo unit can enter unless it has a special unique
movementCostInteger1Base movement cost
defenceBonusFloat0Combat bonus for units being attacked here
RGBList of 3× IntegerGoldRGB color for 'Default' tileset display
uniquesList of StringsemptyList of unique abilities this terrain has
civilopediaTextListemptySee civilopediaText chapter
+

[^A]: Some names have special meanings. Grassland is used as fallback in some cases - e.g. Civilopedia prefers to displays a TerrainFeature on top of it, unless occursOn is not empty and does not contain it. + River is hardcoded to be used to look up a Stats unique to determine the bonuses an actual River provides (remember, rivers live on the edges not as terrain). + River should always be a TerrainFeature and have the same uniques the one in the vanilla rulesets has - if you change that, expect surprises. +[^B]: A base ruleset mod is always expected to provide at least one Land and at least one Water terrain. We do not support Land-only or Water-only mods, even if they might be possible to pull off. +[^C]: If set, the base terrain is changed to this after placing the Natural Wonder, and terrain features cleared. Otherwise, terrain features are reduced to only those present in occursOn.

+

TileImprovements.json

+

Link to original

+

This file lists the improvements that can be constructed or created on a map tile by a unit having the appropriate unique.

+

Note that improvements have two visual representations - icon and pixel graphic in the tileset. Omitting the icon results in a horribly ugly user interface, while omitting tileset graphics will just miss out on an optional visualization. If you provide a pixel graphic for FantasyHex, please be aware of the layering system and the ruleVariants in the tileset json. A single graphic may suffice if it has lots of transparency, as it will be drawn on top of all other terrain elements.

+

Each improvement has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired[^A]
terrainsCanBeBuiltOnList of StringsemptyTerrains that this improvement can be built on [^B]. Removable terrain features will need to be removed before building an improvement [^C]. Must be in Terrains.json
techRequiredStringnoneThe name of the technology required to build this improvement
replacesStringnoneThe name of a improvement that should be replaced by this improvement. Must be in TileImprovements.json
uniqueToStringnoneThe name of the nation this improvement is unique for
<stats>Integer0Per-turn bonus yield for the tile
turnsToBuildInteger-1Number of turns a worker spends building this. If -1, the improvement is unbuildable [^D]. If 0, the improvement is always built in one turn
uniquesList of StringsemptyList of unique abilities this improvement has
shortcutKeyStringnoneKeyboard binding. Currently, only a single character is allowed (no function keys or Ctrl combinations)
civilopediaTextListemptySee civilopediaText chapter
+

[^A]: Special improvements: Road, Railroad, Remove *, Cancel improvement order, City ruins, City center, Barbarian encampment - these have special meanings hardcoded to their names. +[^B]: Improvements with an empty terrainsCanBeBuiltOn list and positive turnsToBuild value can only be built on resources with improvedBy or improvement that contains the corresponding improvement. +[^C]: The removal of terrain features is optional if the feature is named in terrainsCanBeBuiltOn or the unique Does not need removal of [tileFilter] is used (e.g. Camp allowed by resource). +[^D]: They can still be created with the UnitAction unique Can instantly construct a [improvementFilter] improvement.

+

TileResources.json

+

Link to original

+

This file lists the resources that a map tile can have.

+

Note the predefined resourceType enum cannot be altered in a json.

+

Note also that resources have two visual representations - icon and pixel graphic in the tileset. Omitting the icon results in a horribly ugly user interface, while omitting tileset graphics will miss out on a visualization on the map. If you provide a pixel graphic for FantasyHex, please be aware of the layering system and the ruleVariants in the tileset json. A single graphic may suffice if it has lots of transparency, as it will be drawn on top of terrain and features but below an improvement - if the single improvement graphic exists at all.

+

Each resource has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
resourceTypeEnumBonusBonus, Luxury or Strategic
terrainsCanBeFoundOnList of StringsemptyTerrains that this resource can be found on. Must be in Terrains.json
<stats>Integer0Per-turn bonus yield for the tile
improvementStatsObjectnoneThe additional yield when improved, see specialized stats
revealedByStringnoneThe technology name required to see, work and improve this resource
improvedByList of stringsemptyThe improvements required for obtaining this resource. Must be in TileImprovements.json
improvementStringnoneThe improvement required to obtain this resource. Must be in TileImprovements.json (redundant due to improvedBy)
uniqueList of StringsemptyList of unique abilities this resource has
civilopediaTextListemptySee civilopediaText chapter
+

Ruins.json

+

Link to original

+

This optional file contains the possible rewards ancient ruins give. If omitted, the default file for the game is used, even in baseRuleSet mods.

+

Each of the objects in the file represents a single reward you can get from ruins. It has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequiredName of the ruins. Never shown to the user, but they have to be distinct
notificationStringRequiredNotification added to the user when this reward is chosen. If omitted, an empty notification is shown. Some notifications may have parameters, refer to the table below.
weightInteger (≥0)1Relative weight this reward is chosen next [^E]
uniquesList of StringsemptyList of unique abilities that will trigger when entering the ruins. If more than 1 unique is added, the notification will be shown multiple times due to a bug (may be outdated)
excludedDifficultiesList of StringsemptyA list of all difficulties on which this reward may not be awarded
+

[^E]: The exact algorithm for choosing a reward is the following:

+
    +
  • Create a list of all possible rewards. Each reward's frequency in the list corresponds to its weight, a reward with weight one will appear once, a reward with weight two will appear twice, etc.
  • +
  • Shuffle this list
  • +
  • Try give rewards starting from the top of the list. If any of the uniques of the rewards is valid in this context, reward it and stop trying more rewards.
  • +
+

Notifications

+

Some of the rewards ruins can give will have results that are not deterministic when writing it in the JSON, so creating a good notification for it would be impossible. An example for this would be the "Gain [50]-[100] [Gold]" unique, which will give a random amount of gold. For this reason, we allow some notifications to have parameters, in which values will be filled, such as "You found [goldAmount] gold in the ruins!". All the uniques which have this property can be found below.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UniqueParameters
Free [] found in the ruinsThe name of the unit will be filled in the notification, including unique units of the nation
[] population in a random cityThe name of the city to which the population is added will be filled in the notification
Gain []-[] []The exact amount of the stat gained will be filled in the notification
[] free random reasearchable Tech(s) from the []The notification must have placeholders equal to the number of techs granted this way. Each of the names of these free techs will be filled in the notification
Gain enough Faith for a PantheonThe amount of faith gained is filled in the notification
Gain enough Faith for []% of a Great ProphetThe amount of faith gained is filled in the notification
+

Specific uniques

+

A few uniques can be added to ancient ruin effects to modify when they can be earned. These are:

+
    +
  • "Only available after [amount] turns"
  • +
  • "Only available "
  • +
  • "Hidden after a great prophet has been earned"
  • +
+

Tileset-specific json

+

Link to original FantasyHex

+

A mod can define new Tilesets or add to existing ones, namely FantasyHex. There is one json file per Tileset, named same as the Tileset, and placed in a subfolder named "TileSets" relative to the other json files. This is called TileSetConfig and has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
useColorAsBaseTerrainBooleanfalse
useSummaryImagesBooleanfalse
unexploredTileColorColorDark Gray{"r":0.25,"g":0.25,"b":0.25,"a":1}
fogOfWarColorColorBlack{"r":0,"g":0,"b":0,"a":1}
fallbackTileSetString"FantasyHex"null to disable
tileScaleFloat1.0The scale of all tiles. Can be used to increase or decrease the size of every tile
tileScalesObjectemptyUsed by the "Minimal" tileset to scale all its tiles except the base terrain down. Overrides tileScale value for specified terrain
ruleVariantsObjectemptySee here
+

Layering images

+

ruleVariants control substitutions when layering images for a tile, they are list looking like:

+
"ruleVariants": {
+    "Grassland+Forest": ["Grassland", "GrasslandForest"],
+    "Plains+Forest": ["Plains", "PlainsForest"],
+    "Plains+Jungle": ["Plains", "PlainsJungle"],
+    // . . .
+}
+
+

Each line means "if the tile content is this... then combine the following png images". The key part follows a specific order and must match in its entirety, meaning "Plains+Forest" is not valid for "Plains+Forest+Deer", and when it matches no other image layering is done except roads and units (I think - WIP).

+

When TileSetConfig's for the same Tileset are combined, for the first three properties the last mod wins, while ruleVariants are merged, meaning only an entry with the same key overwrites an earlier entry. (TODO)

+

Stats

+

Terrains, features, resources and improvements may list yield statistics. The statistics can be one of the following:

+
    +
  • production
  • +
  • food
  • +
  • gold
  • +
  • science
  • +
  • culture
  • +
  • happiness
  • +
  • faith
  • +
+

General stat

+

If an object carries general stat(s), it contains any combination (or none) of the above stats, each mapping to a corresponding number [^1]. For Example:

+
"gold": 2,
+"improvement": "Quarry",
+
+

Specialized stats

+

For specialized stats, they might come as sub-object in a named field. The sub-object contains any combination (or none) of the above stats, each mapping to a corresponding number [^1]. For Example:

+
"improvement": "Quarry",
+"improvementStats": { "gold": 1, "production": 1 },
+
+

[^1]: The values are usually integers, though the underlying code supports floating point. The effects are, however, insufficiently tested and therefore -so far- using fractional stats is unsupported. Go ahead and thoroughly test that in a mod and help out with feedback 😁.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Mod-file-structure/4-Unit-related-JSON-files/index.html b/Modders/Mod-file-structure/4-Unit-related-JSON-files/index.html new file mode 100644 index 0000000000..14ad8c526f --- /dev/null +++ b/Modders/Mod-file-structure/4-Unit-related-JSON-files/index.html @@ -0,0 +1,1654 @@ + + + + + + + + + + + + + + + + + + + + + + + Unit-related JSON files - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Unit-related JSON files

+

Units.json

+

Link to original

+

This file should contain a list of all the units, both military and civilian, that you want to use in your mod.

+

Each unit has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
unitTypeStringRequiredThe type of the unit. Must be in UnitTypes.json
costInteger-1The amount of production required to build this unit. The production needed is always positive
movementInteger0The amount of movement points the unit has by
strengthInteger0The melee attack and defensive strength of the unit. If this and rangedStrength are omitted or 0, the unit will be a civilian
rangedStrengthInteger0The ranged attack and defensive strength of the unit. If omitted, the unit cannot ranged attack. If used, strength must be set too.
religiousStrengthInteger0The religious attack and defensive strength of the unit
rangeInteger2The range from which ranged attacks can be preformed
interceptRangeInteger0Air units attacking within in this range will be intercepted
requiredTechStringnoneThe tech required to build this unit. Must be in Techs.json
obsoleteTechStringnoneAfter researching this tech, the unit can no longer be build. Must be in Techs.json
requiredResourceStringnoneResource that is consumed by building this unit. Must be in TileResources.json
upgradesToStringnoneUnit that this unit can upgrade to when it is available. Must be in Units.json
replacesStringnoneIf this unit is unique to a nation, this is the unit it replaces. Must be in Units.json
uniqueToStringnoneThe nation that this unit is unique to. Must be in Nations.json
hurryCostModifierInteger0If this unit is bought for gold, its price is increased by so much percent
promotionsList of StringsemptyA list of all the promotions the unit automatically receives upon being built. Each promotion must be in UnitPromotions.json
uniquesList of StringsemptyList of unique abilities this unit has
replacementTextForUniquesStringnoneIf provided, this will be displayed instead of the list of uniques. Can be used for better formatting.
attackSoundStringnoneThe sound that is to be played when this unit attacks. For possible values, see Sounds
civilopediaTextListemptySee civilopediaText chapter
+

UnitPromotions.json

+

Link to original

+

This file lists the available unit promotions.

+

Each promotion must have an icon, except progressions ending in " I", " II", " III" (no IV V VI allowed) are rendered by looking up an icon without those suffixes and adding stars.

+

Remember, promotions can be "bought" with XP, but also granted by the unit type, buildings, wonders and such. They are preserved when a unit upgrades, therefore special properties of nation unique units that can be inherited when they upgrade should be in a promotion, not uniques/stats in the units json (example: Slinger withdraw).

+

Each promotion has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequiredSee above for "I, II, III" progressions
prerequisitesList of StringsemptyPrerequisite promotions
columnIntegerOptionalDetermines placement order on the promotion picker screen. Name is historical, these coordinates no longer control placement directly. Promotions without coordinates are ensured to be placed last. (…)
rowIntegerOptional… In base mods without any coordinates, promotions without prerequisites are sorted alphabetically and placed top down, the rest of the screen will structure the dependencies logically. If your mod has a "Heal instantly", it is suggested to use row=0 to place it on top
unitTypesList of StringsemptyThe unit types for which this promotion applies as specified in UnitTypes.json
uniquesList of StringsemptyList of unique abilities this promotion grants to the units
civilopediaTextListemptySee civilopediaText chapter
innerColorListemptyColor of the icon
outerColorListemptyColor of the background
+

UnitTypes.json

+

Link to original

+

This optional file is used for defining new types of units. The names of these can be used in unitFilters, and these types determine what domain the unit moves in: over land, over water or through the air. If the file is omitted, the following are automatically added: +Civilian, Melee, Ranged, Scout, Mounted, Armor, Siege, WaterCivilian, WaterMelee, WaterRanged, WaterSubmarine, WaterAircraftCarrier, Fighter, Bomber, AtomicBomber, and Missile.

+

Each unit type has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
movementTypeEnumRequiredThe domain through which the unit moves. Allowed values: "Water", "Land", "Air"
uniquesList of StringnoneList of unique abilities this promotion grants to units of this type
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Mod-file-structure/5-Miscellaneous-JSON-files/index.html b/Modders/Mod-file-structure/5-Miscellaneous-JSON-files/index.html new file mode 100644 index 0000000000..76d0256c76 --- /dev/null +++ b/Modders/Mod-file-structure/5-Miscellaneous-JSON-files/index.html @@ -0,0 +1,2915 @@ + + + + + + + + + + + + + + + + + + + + + + + Miscellaneous JSON files - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Miscellaneous JSON files

+

Difficulties.json

+

Link to original

+

This file defines the difficulty levels a player can choose when starting a new game.

+

Each difficulty level has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
baseHappinessInteger0
extraHappinessPerLuxuryFloat0
researchCostModifierFloat1
unitCostModifierFloat1
unitSupplyBaseInteger5
unitSupplyPerCityInteger2
buildingCostModifierFloat1
policyCostModifierFloat1
unhappinessModifierFloat1
barbarianBonusFloat0
barbarianSpawnDelayInteger0
playerBonusStartingUnitsList of StringsemptyCan also be 'Era Starting Unit', maps to startingMilitaryUnit of the Eras file. All other units must be in Units.json. Applies only to human player civs
aiCityGrowthModifierFloat1
aiUnitCostModifierFloat1
aiBuildingCostModifierFloat1
aiWonderCostModifierFloat1
aiBuildingMaintenanceModifierFloat1
aiUnitMaintenanceModifierFloat1
aiUnitSupplyModifierInteger5
aiFreeTechsList of StringsemptyMust be in Techs.json
aiMajorCivBonusStartingUnitsList of StringsemptySame rules as playerBonusStartingUnits, See above. Applies only to AI major civs
aiCityStateBonusStartingUnitsList of StringsemptySame rules as playerBonusStartingUnits, See above. Applies only to city-state civs
aiUnhappinessModifierFloat1
turnBarbariansCanEnterPlayerTilesInteger0
clearBarbarianCampRewardInteger25
+

Eras.json

+

Link to original

+

This file should contain all the era's you want to use in your mod.

+

Each era can have the following attributes:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequired
researchAgreementCostInteger (≥0)300Cost of research agreements when the most technologically advanced civ is in this era
iconRGBList of 3× IntegerwhiteRGB color that icons for technologies of this era should have in the Tech screen
startingSettlerCountInteger (≥0)1Amount of settler units that should be spawned when starting a game in this era (setting this to zero is discouraged [^1])
startingSettlerUnitString"Settler"Name of the unit that should be used for the previous field. Must be in Units.json, or a unit with the "Founds a new city" unique must exist
startingWorkerCountInteger (≥0)0Amount of worker units that should be spawned when starting a game in this era
startingWorkerUnitString"Worker"Name of the unit that should be used for the previous field. If startingWorkerCount>0, then it must exist in Units.json, or a unit with the "Can build [filter] improvements on tiles" unique must exist
startingMilitaryUnitCountInteger (≥0)1Amount of military units that should be spawned when starting a game in this era
startingMilitaryUnitString"Warrior"Name of the unit that should be used for the previous field. Must be in Units.json
startingGoldInteger (≥0)0Amount of gold each civ should receive when starting a game in this era
startingCultureInteger (≥0)0Amount of culture each civ should receive when starting a game in this era
settlerPopulationInteger (>0)1Amount of population each city should have when settled when starting a game in this era
settlerBuildingsList of StringsemptyBuildings that should automatically be built whenever a city is settled when starting a game in this era
startingObsoleteWondersList of StringsemptyWonders (and technically buildings) that should be impossible to built when starting a game in this era. Used in the base game to remove all wonders older than 2 era's
baseUnitBuyCostInteger200Default value used for the unique Can be purchased with [stat] [cityFilter]
embarkDefenseInteger3Default defense for embarked unit in this era
startPercentInteger0When starting, percentage ([0]%-[100]%) of turns skipped in total turns specified in Speed.json
citySoundString"cityClassical"Sound used when city is founded in this era
+

[^1]: Successfully setting startingSettlerCount to zero in a mod (idea: conquer or die) is not easy. Some player-controlled settings require at least one Settler, through any source (see difficulties for other possible settler sources), or you won't be able to start a game: Once City Challenge requires one for all players, and allowing any city-states requires one for those. Would also affect defeat rules.

+

Speeds.json

+

Link to original

+

This file should contain all the speeds you want to use in your mod.

+

Each speed can have the following attributes:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequiredName of the speed
modifierFloat (≥0)1.0Overall game speed modifier
productionCostModifierFloat (≥0)modifier valueScales production cost of units and buildings
goldCostModifierFloat (≥0)modifier valueScales gold costs
scienceCostModifierFloat (≥0)modifier valueScales science costs
cultureCostModifierFloat (≥0)modifier valueScales culture costs
faithCostModifierFloat (≥0)modifier valueScales faith costs
improvementBuildLengthModifierFloat (≥0)modifier valueScales the time it takes for a worker to build tile improvements
barbarianModifierFloat (≥0)modifier valueScales the time between barbarian spawns
goldGiftModifierFloat (≥0)modifier valueScales the influence gained from gifting gold to city-states
cityStateTributeScalingIntervalFloat (≥0)6.5The number of turns it takes for the amount of gold a player demands from city-states to increase by 5 gold
goldenAgeLengthModifierFloat (≥0)modifier valueScales the length of golden ages
religiousPressureAdjacentCityInteger (≥0)6Defines how much religious pressure a city exerts on nearby cities
peaceDealDurationInteger (≥0)10The number of turns a peace deal lasts
dealDurationInteger (≥0)30The number of turns a non-peace deal (research agreement, open borders, etc.) lasts
startYearFloat-4000The start year of the game (negative is BC/BCE)
turnsListRequiredList of time interval per turn, see below
+

Time interval per turn

+

The "turns" attribute defines the number of years passed between turns. The attribute consists of a list of hashmaps, each hashmaps in turn having 2 required attributes: "yearsPerTurn" (Float) and "untilTurn" (Integer)

+ + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
yearsPerTurnIntegerRequiredNumber of years passed between turns
untilTurnIntegerRequiredWhich turn that this "speed" is active until (if it is the last object, this is ignored)
+

The code below is an example of a valid "turns" definition and it specifies that the first 50 turns of a game last for 60 years each, then the next 30 turns (and any played after the 80th) last for 40 years each.

+
"turns": [
+    {"yearsPerTurn": 60, "untilTurn":  50},
+    {"yearsPerTurn": 40, "untilTurn":  80}
+]
+
+

Events.json

+

Events allow users to choose between options of triggers to activate.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequiredUsed for triggering via "Triggers a [event] event" unique
textStringNoneFlavor text displayed to user
presentationOne of: "None", "Alert", "Floating"Alert"Alert" indicates a regular popup, "None" means the choice is made randomly, "Floating" is for tutorial-style indicators
civilopediaTextListOptionalSee civilopediaText chapter
choicesList of EventChoicesUser can choose to trigger one of the viable choices
+

You can use text and/or civilopediaText, if both are present both are shown (but why would you?)

+

Event choices are comprised of:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
textStringRequiredDisplayed to user as button. Should be an action name - "Do X"
triggeredUniquesList of trigger uniquesRequiredThe triggers that this choice activates upon being chosen
conditionsList of conditional uniquesEmpty listIf any conditional is not met, this option becomes unpickable (not shown)
keyShortcutkey to select (name)noneKey names see Gdx.Input.Keys
civilopediaTextListOptionalSee civilopediaText chapter
+

Here, civilopediaText is shown outside the active Button, before the triggeredUniques.

+

ModOptions.json

+ + +

This file is a little different:

+
    +
  • Does not exist in Vanilla ruleset
  • +
  • Is entirely optional but will be created after downloading a mod
  • +
+

Note that this file controls declarative mod compatibility (Work in progress) - e.g. there's uniques to say your Mod should only or never be used as 'Permanent audiovisual mod'. +Incompatibility filtering works so far between extension and base mods, but feel free to document known extension-to-extension incompatibilities using the same Unique now. Stay tuned!

+

The file can have the following attributes, not including the values Unciv sets automatically:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypedefaultNotes
isBaseRulesetBooleanfalseReplaces vanilla ruleset if true
uniquesListemptyMod-wide specials, see here
techsToRemoveListemptyList of Technologies or technologyFilter to remove (isBaseRuleset=false only)
buildingsToRemoveListemptyList of Buildings or Wonders or buildingFilter to remove (isBaseRuleset=false only)
unitsToRemoveListemptyList of Units or unitFilter to remove (isBaseRuleset=false only)
nationsToRemoveListemptyList of Nations or nationFilter to remove (isBaseRuleset=false only)
constantsObjectemptySee ModConstants
tilesetStringemptyOnly applicable for base rulesets
unitsetStringemptyOnly applicable for base rulesets
+

The values normally set automatically from github metadata are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeNotes
modUrlStringThe github page the mod was downloaded from, or empty if a freely hosted zip was used
defaultBranchStringmasterThe repo's default branch
authorStringRepo owner
lastUpdatedStringISO date
modSizeInteger0Size in kB
topicsListemptyA list of "unciv-mod-*" github topics
+

To clarify: When your Mod is distributed via github, including these in the Mod repo has no effect. +However, when a Mod is distributed without a github repository, these values can and should be set by the author in the distributed ModOptions.json.

+

ModConstants

+

Stored in ModOptions.constants, this is a collection of constants used internally in Unciv. +This is the only structure that is merged field by field from mods, not overwritten, so you can change XP from Barbarians in one mod +and city distance in another. In case of conflicts, there is no guarantee which mod wins, only that default values are ignored.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
maxXPfromBarbariansInt30[^A]
cityStrengthBaseFloat8.0[^B]
cityStrengthPerPopFloat0.4[^B]
cityStrengthFromTechsMultiplierFloat5.5[^B]
cityStrengthFromTechsExponentFloat2.8[^B]
cityStrengthFromTechsFullMultiplierFloat1.0[^B]
cityStrengthFromGarrisonFloat0.2[^B]
baseCityBombardRangeInt2[^S]
cityWorkRangeInt3[^T]
cityExpandRangeInt5[^U]
unitSupplyPerPopulationFloat0.5[^C]
minimalCityDistanceInt3[^D]
minimalCityDistanceOnDifferentContinentsInt2[^D]
unitUpgradeCostObjectSee below[^J]
naturalWonderCountMultiplierFloat0.124[^E]
naturalWonderCountAddedConstantFloat0.1[^E]
ancientRuinCountMultiplierFloat0.02[^F]
spawnIceBelowTemperatureFloat-0.8[^G]
maxLakeSizeInt10[^H]
riverCountMultiplierFloat0.01[^I]
minRiverLengthInt5[^I]
maxRiverLengthInt666[^I]
religionLimitBaseInt1[^K]
religionLimitMultiplierFloat0.5[^K]
pantheonBaseInt10[^L]
pantheonGrowthInt5[^L]
workboatAutomationSearchMaxTilesInt20[^M]
maxSpyRankInt3[^N]
spyRankSkillPercentBonusFloat30[^O]
minimumWarDurationInt10[^P]
baseTurnsUntilRevoltInt4[^Q]
cityStateElectionTurnsInt15[^R]
maxImprovementTechErasForwardIntNone[^S]
goldGiftMultiplierFloat1[^T]
goldGiftTradeMultiplierFloat0.8[^U]
goldGiftDegradationMultiplierFloat1.0[^V]
+

Legend:

+
    +
  • [^A]: Max amount of experience that can be gained from combat with barbarians
  • +
  • [^B]: Formula for city Strength: + Strength = baseStrength + strengthPerPop + strengthFromTiles + + ((%techs * multiplier) ^ exponent) * fullMultiplier + + (garrisonBonus * garrisonUnitStrength * garrisonUnitHealth/100) + + defensiveBuildingStrength + where %techs is the percentage of techs in the tech tree that are complete + If no techs exist in this ruleset, %techs = 0.5 (=50%)
  • +
  • [^S]: The distance that cities can attack
  • +
  • [^T]: The tiles in distance that population in cities can work on. Note: Higher values may lead to performace issues and may cause bugs. cityWorkRange may be greater than cityExpandRange.
  • +
  • [^U]: The distance that cities can expand their borders to. Note: Higher values may lead to performace issues and may cause bugs.
  • +
  • [^C]: Formula for Unit Supply: + Supply = unitSupplyBase (difficulties.json) + unitSupplyPerCity * amountOfCities + (difficulties.json) + unitSupplyPerPopulation * amountOfPopulationInAllCities + unitSupplyBase and unitSupplyPerCity can be found in difficulties.json + unitSupplyBase, unitSupplyPerCity and unitSupplyPerPopulation can also be increased through uniques
  • +
  • [^D]: The minimal distance that must be between any two cities, not counting the tiles cities are on + The number is the amount of tiles between two cities, not counting the tiles the cities are on. + e.g. "C__C", where "C" is a tile with a city and "_" is a tile without a city, has a distance of 2. + First constant is for cities on the same landmass, the second is for cities on different continents.
  • +
  • [^E]: NaturalWonderGenerator uses these to determine the number of Natural Wonders to spawn for a given map size. The number scales linearly with map radius: #wonders = radius * naturalWonderCountMultiplier + naturalWonderCountAddedConstant. The defaults effectively mean Tiny - 1, Small - 2, Medium - 3, Large - 4, Huge - 5, Custom radius >=109 - all G&K wonders.
  • +
  • [^F]: MapGenerator.spreadAncientRuins: number of ruins = suitable tile count * this
  • +
  • [^G]: MapGenerator.spawnIce: spawn Ice where T < this, with T calculated from temperatureExtremeness, latitude and perlin noise.
  • +
  • [^H]: MapGenerator.spawnLakesAndCoasts: Water bodies up to this tile count become Lakes
  • +
  • [^I]: RiverGenerator: river frequency and length bounds
  • +
  • [^J]: A UnitUpgradeCost sub-structure.
  • +
  • [^K]: Maximum foundable Religions = religionLimitBase + floor(MajorCivCount * religionLimitMultiplier)
  • +
  • [^L]: Cost of pantheon = pantheonBase + CivsWithReligion * pantheonGrowth
  • +
  • [^M]: When the AI decides whether to build a work boat, how many tiles to search from the city center for an improvable tile
  • +
  • [^N]: The maximum rank any spy can reach
  • +
  • [^O]: How much skill bonus each rank gives
  • +
  • [^P]: The number of turns a civ has to wait before negotiating for peace
  • +
  • [^Q]: The number of turns before a revolt is spawned
  • +
  • [^R]: The number of turns between city-state elections
  • +
  • [^S]: If set, the Improvement picker will silently skip improvements whose tech requirement is more advanced than your current Era + this value. Example: With a 0, Trade posts will not show until the Medieval Era, with a 1 they will already show in the CLassical Era.
  • +
  • [^T]: The multiplier of the gold value of a one-sided trade to be stored as gifts.
  • +
  • [^U]: The multiplier of the gold value of a regular trade to be stored as gifts. Set to 0 to disable gold gifting in two-sided trades.
  • +
  • [^U]: Modifies how quickly the GaveUsGifts dimplomacy modifier runs out. A higher value makes it run out quicker. Normally the gifts reduced by ~2.5% per turn depending on the diplomatic relations with the default value.
  • +
+

UnitUpgradeCost

+

These values are not merged individually, only the entire sub-structure is.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeNotes
baseFloat10
perProductionFloat2
eraMultiplierFloat0
exponentFloat1
roundToInt5
+

The formula for the gold cost of a unit upgrade is (rounded down to a multiple of roundTo): + ( + max((base + perProduction * (new_unit_cost - old_unit_cost)), 0) + * (1 + eraNumber * eraMultiplier) * civModifier + ) ^ exponent +With civModifier being the multiplicative aggregate of "[relativeAmount]% Gold cost of upgrading" uniques that apply.

+

GlobalUniques.json

+

link to original

+

GlobalUniques defines uniques that apply globally. e.g. Vanilla rulesets define the effects of Unhappiness here. +Only the uniques field is used, but a name must still be set (the Ruleset validator might display it). +When extension rulesets define GlobalUniques, all uniques are merged. At the moment there is no way to change/remove uniques set by a base mod.

+

Tutorials.json

+

link to original

+

Note a Base Ruleset mod can define a "welcome page" here by adding a "Tutorial" with a name equal to the name of the mod! +As an exception to the general rule, this file in a Base Ruleset mod will not replace the default, but add to it like extension mods do. +Also, place it under <mod>/jsons/ normally even if the original is found one level above the vanilla jsons.

+

Each tutorial has the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequiredEntry name
civilopediaTextListOptionalSee civilopediaText chapter
stepsList of StringsOptionalPlain text
+

If an entry contains both steps and civilopediaText attributes, the civilopediaText is shown first. +Tutorials shown as Popup can show an show an external image (not part of the texture atlases) if there is an image unter ExtraImages (directly under assets or the Mod folder) having the same name. +This is searched for, meaning the mod defining the Tutorial is irrelevant, mods can override builtin ExtraImages, and case sensitivity depends on the OS.

+

VictoryTypes.json

+

link to original

+

These files contain which victories this mod provides, and what milestones must be reached for someone to win a victory. +Most of the file contains of strings that are shown to the user in the victory screen, with the rest being the requirements for winning.

+

Each victory have the following structure:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDefaultNotes
nameStringRequiredName of the victory
victoryScreenHeaderStringnoneShown in the footer of the victory in the our status in the victory screen
victoryStringStringnoneShown in the footer of the victory screen when you won the game with this victory
defeatStringStringnoneShown in the footer of the victory screen when someone else won the game with this victory
hiddenInVictoryScreenBooleanfalseWhether progress of this victory is hidden in the victory screen
requiredSpaceshipPartsList of StringsemptyWhat spaceship parts must be added to the capital for the corresponding milestone
MilestonesList of StringsRequiredList of milestones that must be accomplished to win, see below
+

Milestones

+

Currently the following milestones are supported:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MilestoneRequirement
Build [building]Build the building [building] in any city
Anyone should build [building]Anyone must build the building [building] for all players to have this milestone
Add all [comment] in capitalAdd all units in the requiredSpaceshipParts field of this victory to the capital
Destroy all playersYou must be the only major civilization with any cities left
Capture all capitalsCapture all the original capitals of major civilizations in the game
Complete [amount] Policy branchesFully complete at least [amount] policy branches
Win diplomatic voteAt any point in the game win a diplomatic vote (UN). You may lose afterwards and still retain this milestone
Become the world religionHave your religion be the majority religion in a majority of cities of all major civs
Have highest score after max turnsBasically time victory. Enables the 'max turn' slider and calculates score when that amount is reached
+

Civilopedia text

+

Any 'thing' defined in json and listed in the Civilopedia can supply extra text, specifically for the Civilopedia. This can be used to explain special considerations better when the automatically generated display is insufficient, or for 'flavour', background stories and the like. Such text can be formatted and linked to other Civilopedia entries, within limits.

+

An example of the format is:

+
"civilopediaText": [
+    { "text": "Ancient ruins provide a one-time random bonus when explored" },
+    { "separator": true },
+    {
+        "text": "This line is red and links to the Scout including icons",
+        "link": "Unit/Scout",
+        "color": "red"
+    },
+    {
+        "text": "A big fat header sporting a golden star",
+        "header": 1,
+        "starred": true,
+        "color": "#ffeb7f"
+    },
+],
+
+

List of attributes - note not all combinations are valid:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeTypeDescription
textStringText to display
linkStringCreate link and icon, format: Category/Name or external link ('http://','https://','mailto:')
iconStringShow icon without linking, format: Category/Name
extraImageStringDisplay an Image instead of text. Can be a path found in a texture atlas or or the name of a png or jpg in the ExtraImages folder
imageSizeFloatSize in world units of the [extraImage], the smaller coordinate is calculated preserving aspect ratio. available width
headerIntegerHeader level. 1 means double text size and decreases from there
sizeIntegerText size, is 18. Use size or header but not both
indentIntegerIndent level. 0 means text will follow icons, 1 aligns to the right of all icons, each further step is 30 units
paddingFloatVertical padding between rows, 5 units
colorStringSets text color, accepts names or 6/3-digit web colors (e.g. #FFA040)
separatorBooleanRenders a separator line instead of text. Can be combined only with color and size (line width, default 2)
starredBooleanDecorates text with a star icon - if set, it receives the color instead of the text
centeredBooleanCenters the line (and turns off automatic wrap). For an extraImage, turns on crop-to-content to equalize transparent borders
+

The lines from json will 'surround' the automatically generated lines such that the latter are inserted just above the first json line carrying a link, if any. If no json lines have links, they will be inserted between the automatic title and the automatic info. This method may, however, change in the future.

+

Note: text now also supports inline color markup. Insert «color» to start coloring text, «» to stop. color can be a name or 6/8-digit hex notation like #ffa040 (different from the color attribute notation only by not allowing 3-digit codes, but allowing the alpha channel). +Effectively, the «» markers are replaced with [] after translation and then passed to gdx markup language.

+

Note: Using an ExtraImages folder in a mod was not working until version 4.11.5

+

RGB colors list

+

Certain objects can be specified to have its own unique color. The colors are defined by a list of 3× Integer in this order: red, green, blue. The range of color is from [0, 0, 0] (black) to [255, 255, 255] (white).

+

Note: The default of some objects are gdx color classes. The values of the constants are as follows:

+ + + + + + + + + + + + + + + + + + + + + +
namevalue
gold[225, 215, 0]
white[255, 255, 255]
black[0, 0, 0]
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Mods/index.html b/Modders/Mods/index.html new file mode 100644 index 0000000000..a8f866bb75 --- /dev/null +++ b/Modders/Mods/index.html @@ -0,0 +1,1620 @@ + + + + + + + + + + + + + + + + + + + + + + + Introduction to Mods - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Introduction to Mods

+

What are mods?

+

Everyone has that thing they wish could be in the game. +Unfortunately, the game only understands code, so mods are our way to give a degree of freedom to those of us who don't code.

+

Mods can add, replace and remove basic game definitions, such as units, nations, buildings, improvements, resources and terrains. +Games loaded with these mods will function according to the mod definition.

+

The game only knows how to recognize existing definitions, so you can't add new unique abilities to nations/units/buildings/etc, only play around with existing ones

+

There are three main kinds of mods:

+
    +
  • Extension mods - these add new nations/units/buildings/resources to a base ruleset - can be either to the default ruleset, or to a base ruleset mod. Easy to do and probably the better place to get started - for example, creating a new Civilization
  • +
  • Base Ruleset mods - these replace the entire existing ruleset - tech tree, units, policies, nations etc - to give an entirely different experience than the base game. These generally require quite a bit of work, but give a whole new experience, and so are the most popular. A minimal example can be found here as a template to build off of ("Use this template" green button in top right, "Create a new repository")
  • +
  • Ruleset-agnostic mods - these do not contain any ruleset-related jsons, but instead contain other affects. Audiovisual mods (including tilesets, unitsets, and UI skins) and map mods are in this category.
  • +
+

Creating and editing mods from your phone is NOT RECOMMENDED - it's much easier using a desktop device!

+

Mod names

+

Mods need to conform to github repo naming rules, but best stay simple and use only letters, digits, and dashes -. +Dashes are automatically converted to spaces for display and use within Unciv.

+

Many punctuation or extended unicode characters might work, but at best potential users won't find them attractive, at worst we'll refuse support when you run into problems :smiling_imp:

+

Mod components

+

Mods are located in a /mods directory, on Desktop that should be next to your .jar file.

+

Mods typically have 2 subfolders:

+
    +
  • jsons - here you should put files that alter the data of game objects, the order of the files is as in the base json files. More information on these can be found here
  • +
  • Images - here you should put game images, as in the base image files.
  • +
+

In order to remove objects from the game, you'll need to create a ModOptions file in the /jsons subfolder - there's an example here.

+

Base Ruleset Mods are mods that 'start from scratch' - ALL the original objects are removed, and only the objects of the mod in question are used.

+

This is done by adding a "isBaseRuleset":true configuration to your modOptions file, like so.

+

Audiovisual components

+

In addition to changing the rules - or even without doing so - mods can override existing graphics or sounds, or add music tracks. For details, see Audiovisual Mods.

+

Custom tilesets and unitsets are a subgroup of these - see Creating a custom tileset - as are UI skin mods, see Creating a UI skin.

+

Such mods are candidates for the "Permanent audiovisual mod" switch available on the Mod Management Screen, see Permanent audiovisual mods.

+

Images need to be 'packed' before the game can use them, which the desktop version can do for you. Please make sure to read the Texture atlas chapter!

+

Adding maps to mods

+

You can also add maps to mods, so they'll be available to players who download your mod.

+

A mod can also be maps-only, if all you want to do is share your maps.

+

When you've finished making your map in the Map Editor, save it, and it will be in the /maps folder of your game.

+

Copy it to a /maps folder in your mod, and you're done!

+

Getting your mod out there

+

In order to make your mod downloadable by anyone, you need to create a Github repository (instructions here)

+

The Images and jsons folders need to be in the root directory of the repo - see here for example.

+

You can then manually download the mod from within the Mod Manager in Unciv:

+
    +
  • From Unciv's main screen, click "Mods"
  • +
  • Click "Download mod from URL", and enter the location of your Github page
  • +
  • The game will automatically download and extract your mod, and it'll be ready to use!
  • +
+

Once you've tested that your mod CAN be downloaded, and that it works well once downloaded, you're ready for the final stage - GETTING IT TO THE USERS AUTOMATICALLY.

+

In order to do this, all you need to do is:

+
    +
  • Go to your Github page
  • +
  • Click the gear icon next to the About (top-right part of the page)
  • +
  • In 'Topics', add "unciv-mod"
  • +
+

Optionally add one or more of the following topics to mark your mod as belonging to specific categories:

+
    +
  • unciv-mod-rulesets (for base ruleset mods)
  • +
  • unciv-mod-expansions (for mods extending vanilla rulesets - please use this, not unciv-mod-expansion)
  • +
  • unciv-mod-graphics (for mods altering graphics - icons, portraits, tilesets)
  • +
  • unciv-mod-audio (for mods supplying music or modifying sounds)
  • +
  • unciv-mod-maps (for mods containing maps)
  • +
  • unciv-mod-fun (for mods mainly tweaking mechanics or other gameplay aspects)
  • +
  • unciv-mod-modsofmods (for mods extending another mod's ruleset)
  • +
+

When you open Unciv's Mod Manager, it will query Github's list of repos with that topic, and now YOUR repo will appear there! +The categories will appear als annotations on the mod buttons, and the user can filter for them. They are not required for the game to use the content - e.g. you can still load maps from mods lacking the unciv-mod-maps topic. +If you want new categories, github will accept any topic, but you'll have to ask the Unciv team to enable them in the game.

+

If you feel there should be additional topics supported in-game, then the course of action is as follows:

+
    +
  • You can add topics to your repository as you please, subject to github's terms, but if you whish them to become Unciv-supported they must begin with "unciv-mod-".
  • +
  • Once done, you can either:
      +
    • Wait at least one release, check that your topic appeared in ModCategories.json, and open a change PR for that file, removing the "hidden" attribute, telling us exactly why that topic would benefit the entire community.
    • +
    • Or, open an issue pointing us to your Mod with the new topics, asking us to do the above for you, again telling us why.
    • +
    +
  • +
+

I have the mod, now what?

+

The primary use of mods is to add them when starting a new game, or configuring a map. This will mean that both the ruleset of the mod, and the images, will be in use for that specific game/map.

+

For mods which are primarily visual or audio, there is a second use - through the mod manager, you can enable them as permanent audiovisual mods. This means that the images and/or sounds from the mod will replace the original media everywhere in the game, and contained music will be available - see here.

+

Mod location for manual loading of mods

+

In general, you should never be manually-loading your mods - not only is this clunky, it's also more error-prone. Unless you have a very specific use-case, you probably shouldn't be doing this

+

When loading a mod, it needs to be in its own folder in /mods - this is how you will work when you're editing your mod.

+

In Android, you can copy them into the Android/data/com.unciv.app/files/mods directory.

+

When the app starts, they will be auto-copied into the /data/data/com.unciv.app/files/mods, directory, that is inaccessible to users.

+

In Chromebook, go to "Play files", should be on the sidebar on the left side of the window under "My files". Click the 3 vertical dots on the top right-hand corner of the window below the "X". +If the option "Show all Play folders" does not have a check next to it click it. You should see some new files that appear on your screen. Now navigate to Android/data/com.unciv.app/files/mods

+

Other

+

You can add an image that will be displayed to users in the mod management screen by adding a "preview.jpg" or "preview.png" file.

+

Existing mods can be found here!

+

What's next?

+

Now you should try to create your first mod!

+

We recommend you start off by adding a new civilization as a mod, to get a hang of the process :)

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Scenarios/index.html b/Modders/Scenarios/index.html new file mode 100644 index 0000000000..8bb35aae54 --- /dev/null +++ b/Modders/Scenarios/index.html @@ -0,0 +1,1400 @@ + + + + + + + + + + + + + + + + + + + + + + + Scenarios - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Scenarios

+

Scenarios are specific game states, set up so a player has a specific experience.

+

These can range from just having cities and units in specific places, to having full-blown custom rulesets to support them.

+

When creating a mod, we differentiate the ruleset from the scenario - the scenario is just a specific game state, or in other words - a saved game.

+

To create a scenario:

+
    +
  • Create a new game with the players you want, AND a spectator
  • +
  • Enter the game as the spectator, and edit the save using the console
  • +
  • Save the game, copy the game save file to a "scenarios" folder in your mod
  • +
+

Console

+

To open the console from the world screen, click the `` button on your keyboard.

+

On mobile:

+
    +
  • Long-click menu hamburger (3 lines)
  • +
  • Click "developer console" button
  • +
+

To see available commands, click enter. This works for subcommands as well (e.g. when you entered tile).

+

Object names (units buildings civs etc) are case-insensitive.

+

Unit and building names with spaces in them, like "Great General", can be inputted in 2 ways:

+
    +
  • "great general" - with quotation marks around them
  • +
  • great-general - with dashes instead of spaces
  • +
+

The console has autocompletion:

+
    +
  • Enter a partial command, subcommand or argument and hit Tab...
  • +
  • When the entered part, compared from the start, matches exactly one of the possible options, that option is completed and a space added for the next subcommand or parameter.
  • +
  • When no option matches, nothing happens.
  • +
  • When two or more options match, the possibilities are displayed, and the longest common substring is entered for you (e.g. you enter 'c', Tab: commands 'city' or 'civ' match, the console partially completes 'ci' for you).
  • +
  • When you haven't yet entered a partial text (console input is empty or ends in a space), autocompletion will display all options.
  • +
+

Some commands operate on a tile or unit you need to select on the map before opening the console.

+

The console does intentionally not follow all rules defined by the ruleset - e.g. it allows Farms on hills without fresh water or the Mobility promotion on a Worker. Any unexpected consequences are your responsibility.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Type-checking/index.html b/Modders/Type-checking/index.html new file mode 100644 index 0000000000..9199da4d04 --- /dev/null +++ b/Modders/Type-checking/index.html @@ -0,0 +1,1491 @@ + + + + + + + + + + + + + + + + + + + + + + + Type checking - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Type checking

+

Mistakes happen. Misnamed fields, things we forgot to add, or even stuff we didn't know existed.

+

Computers can handle a lot of that themselves, so we can let them do the work to ensure that our json files are correct, by using json schemas.

+

This also allows autocompletion when writing jsons!

+

As of now, only Buildings and Units have proper schema

+

Using Android Studio

+
    +
  • Double-click space, search "json schema mappings", enter
  • +
  • Click the small '+' (top, under 'language & frameworks' text)
  • +
  • Put the URL as https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/buildings.json
  • +
  • Click the '+' under the 'Schema version' text, add 'File pattern', put pattern as */Buildings.json
  • +
+

Tada! Now Android Studio will recognize all Buildings.json files as belonging to that schema, and will warn you of inconsistencies!

+

Using VSCode

+
    +
  • ctrl-shift-p, search "open user settings (json)", enter
  • +
  • Copy this into the settings: +
        "json.schemas": [
    +        {
    +            "fileMatch": [
    +                "*/Buildings.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/buildings.json"
    +        },
    +        {
    +            "fileMatch": [
    +              "*/Units.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/units.json"
    +        },
    +        {
    +            "fileMatch": [
    +              "*/Nations.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/nations.json"
    +        },
    +        {
    +            "fileMatch": [
    +              "*/TileImprovements.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/tileImprovements.json"
    +        },
    +        {
    +            "fileMatch": [
    +              "*/Techs.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/techs.json"
    +        },
    +        {
    +            "fileMatch": [
    +              "*/UnitTypes.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/unitTypes.json"
    +        },
    +        {
    +            "fileMatch": [
    +              "*/UnitPromotions.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/unitPromotions.json"
    +        },
    +        {
    +            "fileMatch": [
    +              "*/TileResources.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/tileResources.json"
    +        },
    +        {
    +            "fileMatch": [
    +              "*/Events.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/events.json"
    +        },
    +        {
    +            "fileMatch": [
    +              "*/Terrains.json"
    +            ],
    +            "url": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/terrains.json"
    +        }
    +    ]
    +
  • +
+

Using an online tool

+

If you don't use any of these tools, you can check your file online using this tool

+

However, it can't handle the missing commas that vscode and Android Studio handle, so you may need to get your json up to spec to use it.

+

The schema you want to validate against is: +

{
+    "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/buildings.json"
+}
+

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/Unique-parameters/index.html b/Modders/Unique-parameters/index.html new file mode 100644 index 0000000000..e3e20e4ad6 --- /dev/null +++ b/Modders/Unique-parameters/index.html @@ -0,0 +1,2001 @@ + + + + + + + + + + + + + + + + + + + + + + + Unique parameters - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Unique parameters

+

This page contains an overview of all different parameters used in uniques and what values can be filled into them. +Each of the different parameter types is described below, including all possible values for them. +These are split into two categories:

+
    +
  • descriptions: e.g., "the name of any unit"
  • +
  • Text that looks like this. This last one must be used exactly the same
  • +
+

Note that all of these are case-sensitive!

+

General Filter Rules

+

All filters except for populationFilter and resourceFilter accept multiple values in the format: {A} {B} {C} etc, meaning "the object must match ALL of these filters"

+
+

Example: [{Military} {Water}] units, [{Wounded} {Armor}] units, etc.

+
+

No space or other text is allowed between the [ and the first {, nor between the last } and the ending ]. The space in } {, however, is mandatory.

+

All filters accept non-[filter] as a possible value

+
+

Example: [non-[Wounded]] units

+
+

These can be combined by nesting, with the exception that an "ALL" filter cannot contain another "ALL" filter, even with a NON-filter in between.

+
+

Example: [{non-[Wounded]} {Armor}] units means unit is type Armor and at full health. +Example: [non-[{Wounded} {Armor}]] units means unit is neither wounded nor an Armor one.

+
+

[{non-[{Wounded} {Armor}]} {Embarked}] units WILL FAIL because the game will treat both "} {" at the same time and see non-[{Wounded and Armor}], both invalid.

+

Display of complex filters in Civilopedia may become unreadable. If so, consider hiding that unique and provide a better wording using the Comment [] unique separately.

+

civFilter

+

Allows filtering for specific civs.

+

Allowed values:

+ +

nationFilter

+

Allows filtering for specific nations. Used by ModOptions.nationsToRemove.

+

Allowed values:

+
    +
  • All
  • +
  • City-States, City-State
  • +
  • Major
  • +
  • Nation name
  • +
  • A unique a Nation has (verbatim, no placeholders)
  • +
+

baseUnitFilter

+

Unit filters can be divided up into two parts: baseUnitFilters and mapUnitFilters. +The former is tested against the unit as it appears in the json. +This means it doesn't have an owning civ or tile it stands on, just its base properties. +The latter is tested against the unit as it appears on the map, including its nation, tile, health and all other properties.

+

Allowed values:

+
    +
  • unit name
  • +
  • unit type - e.g. Melee, Ranged, WaterSubmarine, etc.
  • +
  • Land, Water, Air
  • +
  • land units, water units, air units
  • +
  • non-air for non-air non-missile units
  • +
  • Military, military units
  • +
  • Civilian, civilian units
  • +
  • All
  • +
  • Melee
  • +
  • Ranged
  • +
  • Nuclear Weapon
  • +
  • Great Person, Great
  • +
  • Embarked
  • +
  • Matching technologyfilter for the tech this unit requires - e.g. Modern Era
  • +
  • Any exact unique the unit has
  • +
  • Any exact unique the unit type has
  • +
  • Any combination of the above (will match only if all match). The format is {filter1} {filter2} and can match any number of filters. For example: [{Modern era} {Land}] units
  • +
+

mapUnitFilter

+

This indicates a unit as placed on the map. Compare with baseUnitFilter.

+

Allowed values:

+
    +
  • Any matching baseUnitFilter
  • +
  • Any civFilter matching the owner
  • +
  • Any unique the unit has - also includes uniques not caught by the baseUnitFilter, for example promotions
  • +
  • Any promotion name
  • +
  • Wounded
  • +
  • Embarked
  • +
  • City-State
  • +
  • Barbarians, Barbarian
  • +
  • Again, any combination of the above is also allowed, e.g. [{Wounded} {Water}] units.
  • +
+

You can check this in-game using the console with the unit checkfilter <filter> command

+

buildingFilter

+

Allows to only activate a unique for certain buildings.

+

Allowed values:

+
    +
  • All
  • +
  • Buildings, Building
  • +
  • Wonder, Wonders
  • +
  • National Wonder, National
  • +
  • World Wonder, World -- All wonders that are not national wonders
  • +
  • building name
  • +
  • The name of the building it replaces (so for example uniques for libraries will apply to paper makers as well)
  • +
  • Matching technologyfilter for the tech this building requires - e.g. Modern Era
  • +
  • An exact unique the building has (e.g.: spaceship part)
  • +
  • Culture, Gold, etc. if the building is stat-related for that stat. Stat-related buildings are defined as one of the following:
      +
    • Provides that stat directly (e.g. +1 Culture)
    • +
    • Provides a percentage bonus for that stat (e.g. +10% Production)
    • +
    • Provides that stat as a bonus for resources (e.g. +1 Food from every Wheat)
    • +
    • Provides that stat per some amount of population (e.g. +1 Science for every 2 population [cityFilter])
    • +
    +
  • +
  • Any combination of the above (will match only if all match). The format is {filter1} {filter2} up to any number of filters. For example [{Ancient era} {Food}] buildings.
  • +
+

cityFilter

+

cityFilters allow us to choose the range of cities affected by this unique:

+
    +
  • in this city
  • +
  • in all cities
  • +
  • in your cities, Your
  • +
  • in all coastal cities, Coastal
  • +
  • in capital, Capital
  • +
  • in all non-occupied cities, Non-occupied - all cities that are not puppets and don't have extra unhappiness from being recently conquered
  • +
  • in all cities with a world wonder
  • +
  • in all cities connected to capital
  • +
  • in all cities with a garrison, Garrisoned
  • +
  • in non-enemy foreign cities - In all cities owned by civs other than you that you are not at war with
  • +
  • in enemy cities, Enemy
  • +
  • in foreign cities, Foreign
  • +
  • in annexed cities, Annexed
  • +
  • in puppeted cities, Puppeted
  • +
  • in cities being razed, Razing
  • +
  • in holy cities, Holy
  • +
  • in City-State cities
  • +
  • in cities following this religion - Should only be used in pantheon/follower uniques for religions
  • +
  • in cities following our religion
  • +
  • in all cities in which the majority religion is a major religion
  • +
  • in all cities in which the majority religion is an enhanced religion
  • +
  • [civFilter]
  • +
+

You can check this in-game using the console with the city checkfilter <filter> command

+

improvementFilter

+

For filtering a specific improvement.

+

Allowed values:

+
    +
  • improvement name
  • +
  • All
  • +
  • Great Improvements, Great
  • +
  • All Road - for Roads & Railroads
  • +
+

populationFilter

+

A filter determining a part of the population of a city.

+

Allowed values:

+
    +
  • Population
  • +
  • Specialists
  • +
  • Unemployed
  • +
  • Followers of the Majority Religion or Followers of this Religion, both of which only apply when this religion is the majority religion in that city
  • +
+

policyFilter

+

Allowed values:

+
    +
  • All or all
  • +
  • [policyBranchName] branch
  • +
  • The name of the policy
  • +
  • A unique the Policy has (verbatim, no placeholders)
  • +
+

combatantFilter

+

Allowed values:

+ +

Since mapUnitFilter contains civFilter, that means civFilter can be applied to combatantFilter for both units and cities.

+

regionType

+

Used for dividing the world into regions in each of which a single player is placed at the start of the game. +Allowed values are Hybrid and the name of any terrain that has one of the following two uniques:

+
    +
  • A Region is formed with at least [amount]% [simpleTerrain] tiles, with priority [amount]
  • +
  • A Region is formed with at least [amount]% [simpleTerrain] tiles and [simpleTerrain] tiles, with priority [amount]
  • +
+

simpleTerrain

+

Used by NaturalWonderGenerator to place natural wonders

+

Allowed values:

+
    +
  • Land
  • +
  • Water
  • +
  • Elevated
  • +
  • The name of any terrain
  • +
+

stats

+

This indicates a text comprised of specific stats and is slightly more complex.

+

Each stats is comprised of several stat changes, each in the form of +{amount} {stat}, +where 'stat' is one of the seven major stats +(eg Production, Food, Gold, Science, Culture, Happiness and Faith). +For example: +1 Science.

+

These can be strung together with ", " between them, for example: +2 Production, +3 Food.

+

resourceFilter

+

At the moment, only used for the "Improves [resourceFilter] resource in this tile" Unique on Improvements. +Allows filtering resources by their name, their type, or by any Stat listed in their improvementStats property. The all keyword works too.

+

stockpiledResource

+

This indicates a text that corresponds to a custom Stockpile Resource.

+

These are global civilization resources that act similar to the main Civ-wide resources like Gold and Faith. +You can generate them and consume them. And actions that would consume them are blocked if you +don't have enough left in stock.

+

To use, you need to first define a TileResources with the "Stockpiled" Unique. Then you can reference +them in other Uniques.

+

technologyFilter

+

At the moment only implemented for ModOptions.techsToRemove.

+

Allowed values:

+
    +
  • All
  • +
  • The name of an Era
  • +
  • The name of a Technology
  • +
  • A unique a Technology has (verbatim, no placeholders)
  • +
+

terrainFilter

+

This indicates the terrain on a single tile.

+

Allowed values:

+
    +
  • A filter names a specific json attribute (by name):
      +
    • Base terrain
    • +
    • Terrain features
    • +
    • Base terrain uniques
    • +
    • Terrain feature uniques
    • +
    • Resource
    • +
    • Natural wonder
    • +
    • A nationFilter matching the tile owner
    • +
    +
  • +
  • Or the filter is a constant string choosing a derived test:
      +
    • All
    • +
    • Terrain
    • +
    • Water, Land
    • +
    • Coastal (at least one direct neighbor is a coast)
    • +
    • River (as in all 'river on tile' contexts, it means 'adjacent to a river on at least one side')
    • +
    • Open terrain, Rough terrain (note all terrain not having the rough unique is counted as open)
    • +
    • Friendly Land - land belonging to you, or other civs with open borders to you
    • +
    • Foreign Land - any land that isn't friendly land
    • +
    • Enemy Land - any land belonging to a civ you are at war with
    • +
    • your - land belonging to you
    • +
    • Water resource, Strategic resource, Luxury resource, Bonus resource, resource
    • +
    • Natural Wonder (as opposed to above which means testing for a specific Natural Wonder by name, this tests for any of them)
    • +
    +
  • +
+

Please note all of these are case-sensitive.

+

Note: Resource filters depend on whether a viewing civ is known in the context where the filter runs. Water and specific tests require a viewing civ, and if the resource needs a tech to be visible, that tech to be researched by the viewing civ. The other resource category tests can succeed without a known viewing civ only for resources not requiring any tech. So - test your mod!

+

So for instance, the unique "[stats] from [tileFilter] tiles [cityFilter]" can match several cases:

+

tileFilter

+

Allowed values:

+
    +
  • terrainFilter for this tile
  • +
  • improvementFilter for this tile
  • +
  • Improvement or improved for tiles with any improvements
  • +
  • unimproved for tiles with no improvement
  • +
+

You can check this in-game using the console with the tile checkfilter <filter> command

+

terrainQuality

+

Used to indicate for what use the terrain should be viewed when dividing the world into regions, in each of which a single player is placed at the start of the game.

+

Allowed values:

+
    +
  • Undesirable
  • +
  • Food
  • +
  • Desirable
  • +
  • Production
  • +
+

countable

+

Indicates something that can be counted, used both for comparisons and for multiplying uniques

+

Allowed values:

+
    +
  • year, turns
  • +
  • Cities, [cityFilter] Cities
  • +
  • City-States - counts all undefeated city-states
  • +
  • Units, [mapUnitFilter] Units
  • +
  • [buildingFilter] Buildings
  • +
  • Remaining [civFilter] Civilizations
  • +
  • Owned [tileFilter] Tiles
  • +
  • Stat name - gets the stat reserve, not the amount per turn (can be city stats or civilization stats, depending on where the unique is used)
  • +
  • Resource name (can be city stats or civilization stats, depending on where the unique is used)
  • +
+

For example: If a unique is placed on a building, then the retrieved resources will be of the city. If placed on a policy, they will be of the civilization.

+

This can make a difference for e.g. local resources, which are counted per city.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Modders/schemas/buildings.json b/Modders/schemas/buildings.json new file mode 100644 index 0000000000..4d06c0d122 --- /dev/null +++ b/Modders/schemas/buildings.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + + "production": { "type": "number" }, + "food": { "type": "number" }, + "gold": { "type": "number" }, + "science": { "type": "number" }, + "culture": { "type": "number" }, + "happiness": { "type": "number" }, + "faith": { "type": "number" }, + + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" }, + + "requiredTech": { "type": "string" }, + "cost": { "type": "integer" }, + "maintenance": { + "type": "integer", + "description": "Gold upkeep per turn for this building" + }, + "percentStatBonus": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/stats.json" }, + "specialistSlots": { + "type": "object", + "additionalProperties": { "type": "integer" }, + "description": "How many specialists of each type does this building allow for. Key must be name of existing specialist." + }, + "greatPersonPoints": { + "type": "object", + "additionalProperties": { "type": "integer" }, + "description": "Which Great Person Points this building generates per turn. Key must be name of Great Person unit." + }, + "hurryCostModifier": { "type": "number" }, + "isWonder": { "type": "boolean" }, + "isNationalWonder": { "type": "boolean" }, + "requiredBuilding": { "type": "string" }, + "requiredResource": { "type": "string" }, + "requiredNearbyImprovedResources": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + }, + "cityStrength": { "type": "integer" }, + "cityHealth": { "type": "integer" }, + + "uniqueTo": { + "type": "string", + "description": "Designates that only this nation can build this unit. Must be exact name of existing nation." + }, + "replaces": { + "type": "string", + "description": "For unique units: the name of the original unit that this unit replaces for the uniqueTo nation." + }, + + "quote": { + "type": "string", + "description": "Quote that will be displayed for Wonders in the 'construction completed' popup" + }, + "replacementTextForUniques": { + "type": "string", + "description": "Freeform text that will be shown to users, overriding the list of uniques." + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } +} diff --git a/Modders/schemas/civilopediaText.json b/Modders/schemas/civilopediaText.json new file mode 100644 index 0000000000..d7245cbf0d --- /dev/null +++ b/Modders/schemas/civilopediaText.json @@ -0,0 +1,22 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties": { + "text": { "type": "string" }, + "link": { "type": "string" }, + "icon": { "type": "string" }, + "extraImage": { "type": "string" }, + "imageSize": { "type": "number" }, + "size": { "type": "number" }, + "header": { "type": "number" }, + "indent": { "type": "number" }, + "padding": { "type": "number" }, + "color": { "type": "string" }, + "separator": { "type": "boolean" }, + "starred": { "type": "boolean" }, + "centered": { "type": "boolean" }, + "iconCrossed": { "type": "boolean" } + } + } +} diff --git a/Modders/schemas/color.json b/Modders/schemas/color.json new file mode 100644 index 0000000000..87b56f5c34 --- /dev/null +++ b/Modders/schemas/color.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "minLength": 3, + "maxLength": 3, + "items": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } +} diff --git a/Modders/schemas/events.json b/Modders/schemas/events.json new file mode 100644 index 0000000000..1737783043 --- /dev/null +++ b/Modders/schemas/events.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "text": { "type": "string" }, + "presentation": { "enum": ["None", "Alert", "Floating"] }, + + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" }, + + "choices": { + "type": "array", + "items": { + "properties": { + "text": { "type": "string" }, + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" } + }, + "required": ["text", "uniques"], + "additionalProperties": false + } + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } +} diff --git a/Modders/schemas/nations.json b/Modders/schemas/nations.json new file mode 100644 index 0000000000..47da81e8e2 --- /dev/null +++ b/Modders/schemas/nations.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "leaderName": { "type": "string" }, + "adjective": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "description": "Currently not in use" + }, + "style": { "type": "string" }, + "cityStateType": { "type": "string" }, + "outerColor": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/color.json" }, + "innerColor": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/color.json" }, + + "preferredVictoryType": { "type": "string" }, + "startBias": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "description": "Types of tiles where the civ will be more likely to start" + }, + + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + + + // All of these are cosmetic + "introduction": { "type": "string" }, + "tradeRequest": { "type": "string" }, + "neutralHello": { "type": "string" }, + "hateHello": { "type": "string" }, + + "declaringWar": { "type": "string" }, + "attacked": { "type": "string" }, + "defeated": { "type": "string" }, + "startIntroPart1": { "type": "string" }, + "startIntroPart2": { "type": "string" }, + "cities": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + }, + "spyNames": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + }, + "favoredReligion": { "type": "string" }, + + "uniqueName": { "type": "string" }, + "uniqueText": { + "type": "string", + "description": "Freeform text that will be shown to users, overriding the list of uniques." + }, + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" } + }, + "required": [ + "name", "outerColor", "cities" + ], + "additionalProperties": false + } +} diff --git a/Modders/schemas/stats.json b/Modders/schemas/stats.json new file mode 100644 index 0000000000..7fe07273fc --- /dev/null +++ b/Modders/schemas/stats.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "properties": { + "production": { "type": "number" }, + "food": { "type": "number" }, + "gold": { "type": "number" }, + "science": { "type": "number" }, + "culture": { "type": "number" }, + "happiness": { "type": "number" }, + "faith": { "type": "number" } + }, + "additionalProperties": false +} diff --git a/Modders/schemas/techs.json b/Modders/schemas/techs.json new file mode 100644 index 0000000000..f87e48b67c --- /dev/null +++ b/Modders/schemas/techs.json @@ -0,0 +1,58 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "columnNumber": { + "type": "integer", + "minimum": 0, + "description": "The number of this column in the techs matrix - 0 being the leftmost column" + }, + "era": { "type": "string" }, + "techCost": { "type": "integer" }, + "buildingCost": { "type": "integer" }, + "wonderCost": { "type": "integer" }, + "techs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "row": { + "type": "integer", + "minimum": 1, + "description": "The row, within the column, where this tech appears - 1 being the topmost tech" + }, + "cost": { + "type": "integer", + "description": "Science cost of this tech - overrides the column techCost" + }, + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + "prerequisites": { + "type": "array", + "items": { "type": "string" }, + "description": "The names of techs which must be researched before this tech can be researched", + "uniqueItems": true + }, + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" }, + "quote": { + "type": "string", + "description": "The quote that is displayed when you have researched the tech - flavor text" + } + }, + "required": ["name", "row"], + "additionalProperties": false + } + }, + + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" } + }, + "required": [ + "columnNumber", "era" + ], + "additionalProperties": false + } +} diff --git a/Modders/schemas/terrains.json b/Modders/schemas/terrains.json new file mode 100644 index 0000000000..0f54fd29c2 --- /dev/null +++ b/Modders/schemas/terrains.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "enum": ["Land", "Water", "TerrainFeature", "NaturalWonder"] }, + + "production": { "type": "number" }, + "food": { "type": "number" }, + "gold": { "type": "number" }, + "science": { "type": "number" }, + "culture": { "type": "number" }, + "happiness": { "type": "number" }, + "faith": { "type": "number" }, + + "movementCost": { "type": "number" }, + "impassable": { "type": "boolean" }, + "defenceBonus": { "type": "number", "description": "In percentages - so 0.1 is 10% bonus" }, + "unbuildable": { "type": "boolean", "description": "If true, nothing can be built here - not even resource improvements" }, + + "RGB": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/color.json" }, + + "occursOn": { "type": "array", "items": { "type": "string" }, + "description": "For terrain features - List of terrains that this feature can appear on" }, + "overrideStats": { "type": "boolean", "description": "For terrain features - indicates the stats of this terrain override those of all previous layers" }, + + "turnsInto": { "type": "string", "description": "Used by Natural Wonders: it is the baseTerrain on top of which the Natural Wonder is placed" }, + "weight": { "type": "number", "description": "For natural wonders - the chance this nat wonder will be picked" }, + + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" } + }, + "required": [ + "name", "type" + ], + "additionalProperties": false + } +} diff --git a/Modders/schemas/tileImprovements.json b/Modders/schemas/tileImprovements.json new file mode 100644 index 0000000000..5d90ab0675 --- /dev/null +++ b/Modders/schemas/tileImprovements.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "terrainsCanBeBuiltOn": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + }, + "turnsToBuild": { "type": "integer" }, + "techRequired": { "type": "string" }, + "uniqueTo": { "type": "string" }, + "shortcutKey": { + "type": "string", + "maxLength": 1 + }, + + "production": { "type": "number" }, + "food": { "type": "number" }, + "gold": { "type": "number" }, + "science": { "type": "number" }, + "culture": { "type": "number" }, + "happiness": { "type": "number" }, + "faith": { "type": "number" }, + + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" } + }, + "required": [ + "name", + ], + "additionalProperties": false + } +} diff --git a/Modders/schemas/tileResources.json b/Modders/schemas/tileResources.json new file mode 100644 index 0000000000..76f8fbbab9 --- /dev/null +++ b/Modders/schemas/tileResources.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "terrainsCanBeFoundOn": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + }, + "improvement": { "type": "string" }, + "resourceType": { "enum": ["Strategic", "Luxury", "Bonus"]}, + "revealedBy": { "type": "string" }, + "improvedBy": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true + }, + "improvementStats": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/stats.json" }, + + "production": { "type": "number" }, + "food": { "type": "number" }, + "gold": { "type": "number" }, + "science": { "type": "number" }, + "culture": { "type": "number" }, + "happiness": { "type": "number" }, + "faith": { "type": "number" }, + + "majorDepositAmount": {"$ref": "#/definitions/resourceAmount"}, + "minorDepositAmount": {"$ref": "#/definitions/resourceAmount"}, + + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" } + }, + "required": [ + "name", + ], + "additionalProperties": false, + "definitions": { + "resourceAmount": { + "type": "object", + "properties": { + "sparse": {"type": "number"}, + "default": {"type": "number"}, + "abundant": {"type": "number"} + }, + "required": ["sparse","default","abundant"] + } + } + } +} diff --git a/Modders/schemas/uniques.json b/Modders/schemas/uniques.json new file mode 100644 index 0000000000..2282ccb121 --- /dev/null +++ b/Modders/schemas/uniques.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "description": "https://yairm210.github.io/Unciv/Modders/uniques/", + "items": { + "type": "string", + "allOf": [ + { + "type": "string", + "pattern": "^[^><]*(\\<[^>]*\\>[^><]*)*$" + }, + { + "type": "string", + "pattern": "^[^\\[\\]]*(\\[[^\\[\\]]*\\][^\\[\\]]*)*$" + } + ] + } +} diff --git a/Modders/schemas/unitPromotions.json b/Modders/schemas/unitPromotions.json new file mode 100644 index 0000000000..e2cfc6674d --- /dev/null +++ b/Modders/schemas/unitPromotions.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "prerequisites": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, + "unitTypes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, + + "row": { "type": "integer", "minimum": 0, + "description": "Used as **column** hint in the promotion picker screen. Not a direct position, it is used to sort before an automatic distribution." + }, + "column": { "type": "integer", "minimum": 0, + "description": "Used as **row** hint in the promotion picker screen. Not a direct position, it is used to sort before an automatic distribution." + }, + + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" }, + + "outerColor": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/color.json" }, + "innerColor": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/color.json" } + }, + "required": [ + "name" + ], + "additionalProperties": false + } +} diff --git a/Modders/schemas/unitTypes.json b/Modders/schemas/unitTypes.json new file mode 100644 index 0000000000..665c699012 --- /dev/null +++ b/Modders/schemas/unitTypes.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "movementType": { + "enum": ["Land", "Water", "Air"] + }, + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" } + }, + "required": [ + "name", "movementType" + ], + "additionalProperties": false + } +} diff --git a/Modders/schemas/units.json b/Modders/schemas/units.json new file mode 100644 index 0000000000..b837cf0c72 --- /dev/null +++ b/Modders/schemas/units.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "unitType": { "type": "string" }, + + "movement": { "type": "number" }, + + "strength": { "type": "number" }, + "rangedStrength": { "type": "number" }, + "religiousStrength": { "type": "number" }, + "range": { "type": "number" }, + "interceptRange": { "type": "number" }, + + "cost": { "type": "number" }, + "hurryCostModifier": { "type": "number" }, + + "requiredResource": { "type": "string" }, + "requiredTech": { "type": "string" }, + "obsoleteTech": { "type": "string" }, + "upgradesTo": { "type": "string" }, + + "uniqueTo": { + "type": "string", + "description": "Designates that only this nation can build this unit. Must be exact name of existing nation." + }, + "replaces": { + "type": "string", + "description": "For unique units: the name of the original unit that this unit replaces for the uniqueTo nation." + }, + + "uniques": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/uniques.json" }, + + "promotions": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "description": "Promotions that the unit gets on creation. Must be exact names of existing promotions." + }, + + "attackSound": { "type": "string" }, + "replacementTextForUniques": { + "type": "string", + "description": "Freeform text that will be shown to users, overriding the list of uniques." + }, + "civilopediaText": { "$ref": "https://raw.githubusercontent.com/yairm210/Unciv/master/docs/Modders/schemas/civilopediaText.json" } + }, + "required": [ + "name", "unitType" + ], + "additionalProperties": false + } +} diff --git a/Modders/uniques/index.html b/Modders/uniques/index.html new file mode 100644 index 0000000000..501b0af764 --- /dev/null +++ b/Modders/uniques/index.html @@ -0,0 +1,5556 @@ + + + + + + + + + + + + + + + + + + + + + + + Uniques - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Uniques

+

An overview of uniques can be found here

+

Simple unique parameters are explained by mouseover. Complex parameters are explained in Unique parameter types

+

Triggerable uniques

+
+

Uniques that have immediate, one-time effects. These can be added to techs to trigger when researched, to policies to trigger when adopted, to eras to trigger when reached, to buildings to trigger when built. Alternatively, you can add a TriggerCondition to them to make them into Global uniques that activate upon a specific event.They can also be added to units to grant them the ability to trigger this effect as an action, which can be modified with UnitActionModifier and UnitTriggerCondition conditionals.

+
+
+Gain a free [buildingName] [cityFilter] +

Free buildings CANNOT be self-removing - this leads to an endless loop of trying to add the building +Example: "Gain a free [Library] [in all cities]"

+

Applicable to: Triggerable, Global

+
+
+Remove [buildingFilter] [cityFilter] +

Example: "Remove [Culture] [in all cities]"

+

Applicable to: Triggerable, Global

+
+
+Sell [buildingFilter] buildings [cityFilter] +

Example: "Sell [Culture] buildings [in all cities]"

+

Applicable to: Triggerable, Global

+
+
+Free [unit] appears +

Example: "Free [Musketman] appears"

+

Applicable to: Triggerable

+
+
+[positiveAmount] free [unit] units appear +

Example: "[3] free [Musketman] units appear"

+

Applicable to: Triggerable

+
+
+Free Social Policy +

Applicable to: Triggerable

+
+
+[positiveAmount] Free Social Policies +

Example: "[3] Free Social Policies"

+

Applicable to: Triggerable

+
+
+Empire enters golden age +

Applicable to: Triggerable

+
+
+Empire enters a [positiveAmount]-turn Golden Age +

Example: "Empire enters a [3]-turn Golden Age"

+

Applicable to: Triggerable

+
+
+Free Great Person +

Applicable to: Triggerable

+
+
+[amount] population [cityFilter] +

Example: "[3] population [in all cities]"

+

Applicable to: Triggerable

+
+
+[amount] population in a random city +

Example: "[3] population in a random city"

+

Applicable to: Triggerable

+
+
+Discover [tech] +

Example: "Discover [Agriculture]"

+

Applicable to: Triggerable

+
+
+Adopt [policy] +

Example: "Adopt [Oligarchy]"

+

Applicable to: Triggerable

+
+
+Remove [policy] +

Example: "Remove [Oligarchy]"

+

Applicable to: Triggerable

+
+
+Remove [policy] and refund [amount]% of its cost +

Example: "Remove [Oligarchy] and refund [3]% of its cost"

+

Applicable to: Triggerable

+
+
+Free Technology +

Applicable to: Triggerable

+
+
+[positiveAmount] Free Technologies +

Example: "[3] Free Technologies"

+

Applicable to: Triggerable

+
+
+[positiveAmount] free random researchable Tech(s) from the [era] +

Example: "[3] free random researchable Tech(s) from the [Ancient era]"

+

Applicable to: Triggerable

+
+
+Reveals the entire map +

Applicable to: Triggerable

+
+
+Gain a free [beliefType] belief +

Example: "Gain a free [Follower] belief"

+

Applicable to: Triggerable

+
+
+Triggers voting for the Diplomatic Victory +

Applicable to: Triggerable

+
+
+Instantly consumes [positiveAmount] [stockpiledResource] +

Example: "Instantly consumes [3] [Mana]"

+

Applicable to: Triggerable

+
+
+Instantly provides [positiveAmount] [stockpiledResource] +

Example: "Instantly provides [3] [Mana]"

+

Applicable to: Triggerable

+
+
+Instantly gain [amount] [stockpile] +

Example: "Instantly gain [3] [Mana]"

+

This unique's effect can be modified with <(modified by game speed)> +Applicable to: Triggerable

+
+
+Gain [amount] [stat] +

Example: "Gain [3] [Culture]"

+

This unique's effect can be modified with <(modified by game speed)> +Applicable to: Triggerable

+
+
+Gain [amount]-[amount] [stat] +

Example: "Gain [3]-[3] [Culture]"

+

Applicable to: Triggerable

+
+
+Gain enough Faith for a Pantheon +

Applicable to: Triggerable

+
+
+Gain enough Faith for [amount]% of a Great Prophet +

Example: "Gain enough Faith for [3]% of a Great Prophet"

+

Applicable to: Triggerable

+
+
+Gain control over [tileFilter] tiles in a [amount]-tile radius +

Example: "Gain control over [Farm] tiles in a [3]-tile radius"

+

Applicable to: Triggerable

+
+
+Reveal up to [positiveAmount/'all'] [tileFilter] within a [positiveAmount] tile radius +

Example: "Reveal up to [3] [Farm] within a [3] tile radius"

+

Applicable to: Triggerable

+
+
+Triggers the following global alert: [comment] +

Example: "Triggers the following global alert: [comment]"

+

Applicable to: Triggerable

+
+
+Promotes all spies [amount] time(s) +

Example: "Promotes all spies [3] time(s)"

+

Applicable to: Triggerable

+
+
+Gain an extra spy +

Applicable to: Triggerable

+
+
+Turn this tile into a [terrainName] tile +

Example: "Turn this tile into a [Forest] tile"

+

Applicable to: Triggerable

+
+
+[mapUnitFilter] units gain the [promotion] promotion +

Works only with promotions that are valid for the unit's type - or for promotions that do not specify any. +Example: "[Wounded] units gain the [Shock I] promotion"

+

Applicable to: Triggerable

+
+
+Provides the cheapest [stat] building in your first [positiveAmount] cities for free +

Example: "Provides the cheapest [Culture] building in your first [3] cities for free"

+

Applicable to: Triggerable

+
+
+Provides a [buildingName] in your first [positiveAmount] cities for free +

Example: "Provides a [Library] in your first [3] cities for free"

+

Applicable to: Triggerable

+
+
+Triggers a [event] event +

Example: "Triggers a [Inspiration] event"

+

Applicable to: Triggerable

+
+
+Mark tutorial [comment] complete +

Example: "Mark tutorial [comment] complete"

+

Applicable to: Triggerable

+
+
+Suppress warning [validationWarning] +

Allows suppressing specific validation warnings. Errors, deprecation warnings, or warnings about untyped and non-filtering uniques should be heeded, not suppressed, and are therefore not accepted. Note that this can be used in ModOptions, in the uniques a warning is about, or as modifier on the unique triggering a warning - but you still need to be specific. Even in the modifier case you will need to specify a sufficiently selective portion of the warning text as parameter. +Example: "Suppress warning [Tinman is supposed to automatically upgrade at tech Clockwork, and therefore Servos for its upgrade Mecha may not yet be researched! -or- is supposed to automatically upgrade]"

+

Applicable to: Triggerable, Terrain, Speed, ModOptions, MetaModifier

+
+

UnitTriggerable uniques

+
+

Uniques that have immediate, one-time effects on a unit.They can be added to units (on unit, unit type, or promotion) to grant them the ability to trigger this effect as an action, which can be modified with UnitActionModifier and UnitTriggerCondition conditionals.

+
+
+[unitTriggerTarget] heals [positiveAmount] HP +

Example: "[This Unit] heals [3] HP"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] takes [positiveAmount] damage +

Example: "[This Unit] takes [3] damage"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] gains [amount] XP +

Example: "[This Unit] gains [3] XP"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] upgrades for free +

Example: "[This Unit] upgrades for free"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] upgrades for free including special upgrades +

Example: "[This Unit] upgrades for free including special upgrades"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] gains the [promotion] promotion +

Example: "[This Unit] gains the [Shock I] promotion"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] loses the [promotion] promotion +

Example: "[This Unit] loses the [Shock I] promotion"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] gains [amount] movement +

Example: "[This Unit] gains [3] movement"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] loses [amount] movement +

Example: "[This Unit] loses [3] movement"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] gains the [promotion] status for [positiveAmount] turn(s) +

Statuses are temporary promotions. They do not stack, and reapplying a specific status take the highest number - so reapplying a 3-turn on a 1-turn makes it 3, but doing the opposite will have no effect. Turns left on the status decrease at the start of turn, so bonuses applied for 1 turn are stll applied during other civ's turns. +Example: "[This Unit] gains the [Shock I] status for [3] turn(s)"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] loses the [promotion] status +

Example: "[This Unit] loses the [Shock I] status"

+

Applicable to: UnitTriggerable

+
+
+[unitTriggerTarget] is destroyed +

Example: "[This Unit] is destroyed"

+

Applicable to: UnitTriggerable

+
+

Global uniques

+
+

Uniques that apply globally. Civs gain the abilities of these uniques from nation uniques, reached eras, researched techs, adopted policies, built buildings, religion 'founder' uniques, owned resources, and ruleset-wide global uniques.

+
+
+[stats] +

Example: "[+1 Gold, +2 Production]"

+

Applicable to: Global, Terrain, Improvement

+
+
+[stats] [cityFilter] +

Example: "[+1 Gold, +2 Production] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from every specialist [cityFilter] +

Example: "[+1 Gold, +2 Production] from every specialist [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] per [amount] population [cityFilter] +

Example: "[+1 Gold, +2 Production] per [3] population [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] per [amount] social policies adopted +

Only works for civ-wide stats +Example: "[+1 Gold, +2 Production] per [3] social policies adopted"

+

Applicable to: Global

+
+
+[stats] per every [amount] [civWideStat] +

Example: "[+1 Gold, +2 Production] per every [3] [Gold]"

+

Applicable to: Global

+
+
+[stats] in cities on [terrainFilter] tiles +

Example: "[+1 Gold, +2 Production] in cities on [Fresh Water] tiles"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from all [buildingFilter] buildings +

Example: "[+1 Gold, +2 Production] from all [Culture] buildings"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from [tileFilter] tiles [cityFilter] +

Example: "[+1 Gold, +2 Production] from [Farm] tiles [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from [tileFilter] tiles without [tileFilter] [cityFilter] +

Example: "[+1 Gold, +2 Production] from [Farm] tiles without [Farm] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from every [tileFilter/specialist/buildingFilter] +

Example: "[+1 Gold, +2 Production] from every [Farm]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from each Trade Route +

Example: "[+1 Gold, +2 Production] from each Trade Route"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% [stat] +

Example: "[+20]% [Culture]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% [stat] [cityFilter] +

Example: "[+20]% [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% [stat] from every [tileFilter/buildingFilter] +

Example: "[+20]% [Culture] from every [Farm]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Yield from every [tileFilter/buildingFilter] +

Example: "[+20]% Yield from every [Farm]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% [stat] from City-States +

Example: "[+20]% [Culture] from City-States"

+

Applicable to: Global

+
+
+[relativeAmount]% [stat] from Trade Routes +

Example: "[+20]% [Culture] from Trade Routes"

+

Applicable to: Global

+
+
+Nullifies [stat] [cityFilter] +

Example: "Nullifies [Culture] [in all cities]"

+

Applicable to: Global

+
+
+Nullifies Growth [cityFilter] +

Example: "Nullifies Growth [in all cities]"

+

Applicable to: Global

+
+
+[relativeAmount]% Production when constructing [buildingFilter] buildings [cityFilter] +

Example: "[+20]% Production when constructing [Culture] buildings [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Production when constructing [baseUnitFilter] units [cityFilter] +

Example: "[+20]% Production when constructing [Melee] units [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Production when constructing [buildingFilter] wonders [cityFilter] +

Example: "[+20]% Production when constructing [Culture] wonders [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Production towards any buildings that already exist in the Capital +

Example: "[+20]% Production towards any buildings that already exist in the Capital"

+

Applicable to: Global, FollowerBelief

+
+
+Military Units gifted from City-States start with [amount] XP +

Example: "Military Units gifted from City-States start with [3] XP"

+

Applicable to: Global

+
+
+Militaristic City-States grant units [amount] times as fast when you are at war with a common nation +

Example: "Militaristic City-States grant units [3] times as fast when you are at war with a common nation"

+

Applicable to: Global

+
+
+Gifts of Gold to City-States generate [relativeAmount]% more Influence +

Example: "Gifts of Gold to City-States generate [+20]% more Influence"

+

Applicable to: Global

+
+
+Can spend Gold to annex or puppet a City-State that has been your ally for [amount] turns. +

Example: "Can spend Gold to annex or puppet a City-State that has been your ally for [3] turns."

+

Applicable to: Global

+
+
+City-State territory always counts as friendly territory +

Applicable to: Global

+
+
+Allied City-States will occasionally gift Great People +

Applicable to: Global

+
+
+[relativeAmount]% City-State Influence degradation +

Example: "[+20]% City-State Influence degradation"

+

Applicable to: Global

+
+
+Resting point for Influence with City-States is increased by [amount] +

Example: "Resting point for Influence with City-States is increased by [3]"

+

Applicable to: Global

+
+
+Allied City-States provide [stat] equal to [relativeAmount]% of what they produce for themselves +

Example: "Allied City-States provide [Culture] equal to [+20]% of what they produce for themselves"

+

Applicable to: Global

+
+
+[relativeAmount]% resources gifted by City-States +

Example: "[+20]% resources gifted by City-States"

+

Applicable to: Global

+
+
+[relativeAmount]% Happiness from luxury resources gifted by City-States +

Example: "[+20]% Happiness from luxury resources gifted by City-States"

+

Applicable to: Global

+
+
+City-State Influence recovers at twice the normal rate +

Applicable to: Global

+
+
+[relativeAmount]% growth [cityFilter] +

Example: "[+20]% growth [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[amount]% Food is carried over after population increases [cityFilter] +

Example: "[3]% Food is carried over after population increases [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Food consumption by specialists [cityFilter] +

Example: "[+20]% Food consumption by specialists [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% unhappiness from the number of cities +

Example: "[+20]% unhappiness from the number of cities"

+

Applicable to: Global

+
+
+[relativeAmount]% Unhappiness from [populationFilter] [cityFilter] +

Example: "[+20]% Unhappiness from [Followers of this Religion] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[amount] Happiness from each type of luxury resource +

Example: "[3] Happiness from each type of luxury resource"

+

Applicable to: Global

+
+
+Retain [relativeAmount]% of the happiness from a luxury after the last copy has been traded away +

Example: "Retain [+20]% of the happiness from a luxury after the last copy has been traded away"

+

Applicable to: Global

+
+
+[relativeAmount]% of excess happiness converted to [stat] +

Example: "[+20]% of excess happiness converted to [Culture]"

+

Applicable to: Global

+
+
+Cannot build [baseUnitFilter] units +

Example: "Cannot build [Melee] units"

+

Applicable to: Global

+
+
+Enables construction of Spaceship parts +

Applicable to: Global

+
+
+May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount]) +

Example: "May buy [Melee] units for [3] [Culture] [in all cities] at an increasing price ([3])"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [buildingFilter] buildings for [amount] [stat] [cityFilter] at an increasing price ([amount]) +

Example: "May buy [Culture] buildings for [3] [Culture] [in all cities] at an increasing price ([3])"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] +

Example: "May buy [Melee] units for [3] [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [buildingFilter] buildings for [amount] [stat] [cityFilter] +

Example: "May buy [Culture] buildings for [3] [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [baseUnitFilter] units with [stat] [cityFilter] +

Example: "May buy [Melee] units with [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [buildingFilter] buildings with [stat] [cityFilter] +

Example: "May buy [Culture] buildings with [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [baseUnitFilter] units with [stat] for [amount] times their normal Production cost +

Example: "May buy [Melee] units with [Culture] for [3] times their normal Production cost"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [buildingFilter] buildings with [stat] for [amount] times their normal Production cost +

Example: "May buy [Culture] buildings with [Culture] for [3] times their normal Production cost"

+

Applicable to: Global, FollowerBelief

+
+
+[stat] cost of purchasing items in cities [relativeAmount]% +

Example: "[Culture] cost of purchasing items in cities [+20]%"

+

Applicable to: Global, FollowerBelief

+
+
+[stat] cost of purchasing [buildingFilter] buildings [relativeAmount]% +

Example: "[Culture] cost of purchasing [Culture] buildings [+20]%"

+

Applicable to: Global, FollowerBelief

+
+
+[stat] cost of purchasing [baseUnitFilter] units [relativeAmount]% +

Example: "[Culture] cost of purchasing [Melee] units [+20]%"

+

Applicable to: Global, FollowerBelief

+
+
+Enables conversion of city production to [civWideStat] +

Example: "Enables conversion of city production to [Gold]"

+

Applicable to: Global

+
+
+Production to [civWideStat] conversion in cities changed by [relativeAmount]% +

Example: "Production to [Gold] conversion in cities changed by [+20]%"

+

Applicable to: Global

+
+
+Improves movement speed on roads +

Applicable to: Global

+
+
+Roads connect tiles across rivers +

Applicable to: Global

+
+
+[relativeAmount]% maintenance on road & railroads +

Example: "[+20]% maintenance on road & railroads"

+

Applicable to: Global

+
+
+No Maintenance costs for improvements in [tileFilter] tiles +

Example: "No Maintenance costs for improvements in [Farm] tiles"

+

Applicable to: Global

+
+
+[relativeAmount]% construction time for [improvementFilter] improvements +

Example: "[+20]% construction time for [All Road] improvements"

+

Applicable to: Global, Unit

+
+
+Gain a free [buildingName] [cityFilter] +

Free buildings CANNOT be self-removing - this leads to an endless loop of trying to add the building +Example: "Gain a free [Library] [in all cities]"

+

Applicable to: Triggerable, Global

+
+
+[relativeAmount]% maintenance cost for buildings [cityFilter] +

Example: "[+20]% maintenance cost for buildings [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+Remove [buildingFilter] [cityFilter] +

Example: "Remove [Culture] [in all cities]"

+

Applicable to: Triggerable, Global

+
+
+Sell [buildingFilter] buildings [cityFilter] +

Example: "Sell [Culture] buildings [in all cities]"

+

Applicable to: Triggerable, Global

+
+
+[relativeAmount]% Culture cost of natural border growth [cityFilter] +

Example: "[+20]% Culture cost of natural border growth [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Gold cost of acquiring tiles [cityFilter] +

Example: "[+20]% Gold cost of acquiring tiles [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+Each city founded increases culture cost of policies [relativeAmount]% less than normal +

Example: "Each city founded increases culture cost of policies [+20]% less than normal"

+

Applicable to: Global

+
+
+[relativeAmount]% Culture cost of adopting new Policies +

Example: "[+20]% Culture cost of adopting new Policies"

+

Applicable to: Global

+
+
+[stats] for every known Natural Wonder +

Example: "[+1 Gold, +2 Production] for every known Natural Wonder"

+

Applicable to: Global

+
+
+[stats] for discovering a Natural Wonder (bonus enhanced to [stats] if first to discover it) +

Example: "[+1 Gold, +2 Production] for discovering a Natural Wonder (bonus enhanced to [+1 Gold, +2 Production] if first to discover it)"

+

Applicable to: Global

+
+
+[relativeAmount]% Great Person generation [cityFilter] +

Example: "[+20]% Great Person generation [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Gold from Great Merchant trade missions +

Example: "[+20]% Gold from Great Merchant trade missions"

+

Applicable to: Global

+
+
+Great General provides double combat bonus +

Applicable to: Global, Unit

+
+
+Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once. +

Example: "Receive a free Great Person at the end of every [comment] (every 394 years), after researching [Agriculture]. Each bonus person can only be chosen once."

+

Applicable to: Global

+
+
+Once The Long Count activates, the year on the world screen displays as the traditional Mayan Long Count. +

Applicable to: Global

+
+
+[amount] Unit Supply +

Example: "[3] Unit Supply"

+

Applicable to: Global

+
+
+[amount] Unit Supply per [amount] population [cityFilter] +

Example: "[3] Unit Supply per [3] population [in all cities]"

+

Applicable to: Global

+
+
+[amount] Unit Supply per city +

Example: "[3] Unit Supply per city"

+

Applicable to: Global

+
+
+[amount] units cost no maintenance +

Example: "[3] units cost no maintenance"

+

Applicable to: Global

+
+
+Units in cities cost no Maintenance +

Applicable to: Global

+
+
+Enables embarkation for land units +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Global

+
+
+Enables [mapUnitFilter] units to enter ocean tiles +

Example: "Enables [Wounded] units to enter ocean tiles"

+

Applicable to: Global

+
+
+Land units may cross [terrainName] tiles after the first [baseUnitFilter] is earned +

Example: "Land units may cross [Forest] tiles after the first [Melee] is earned"

+

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Global

+
+
+Enemy [mapUnitFilter] units must spend [amount] extra movement points when inside your territory +

Example: "Enemy [Wounded] units must spend [3] extra movement points when inside your territory"

+

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Global

+
+
+New [baseUnitFilter] units start with [amount] Experience [cityFilter] +

Example: "New [Melee] units start with [3] Experience [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+All newly-trained [baseUnitFilter] units [cityFilter] receive the [promotion] promotion +

Example: "All newly-trained [Melee] units [in all cities] receive the [Shock I] promotion"

+

Applicable to: Global, FollowerBelief

+
+
+[mapUnitFilter] Units adjacent to this city heal [amount] HP per turn when healing +

Example: "[Wounded] Units adjacent to this city heal [3] HP per turn when healing"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% City Strength from defensive buildings +

Example: "[+20]% City Strength from defensive buildings"

+

Applicable to: Global

+
+
+[relativeAmount]% Strength for cities +

Example: "[+20]% Strength for cities"

+

Applicable to: Global, FollowerBelief

+
+
+Provides [amount] [resource] +

Example: "Provides [3] [Iron]"

+

Applicable to: Global, FollowerBelief, Improvement

+
+
+Quantity of strategic resources produced by the empire +[relativeAmount]% +

Example: "Quantity of strategic resources produced by the empire +[+20]%"

+

Applicable to: Global

+
+
+Double quantity of [resource] produced +

Example: "Double quantity of [Iron] produced"

+

Applicable to: Global

+
+
+Enables Open Borders agreements +

Applicable to: Global

+
+
+Enables Research agreements +

Applicable to: Global

+
+
+Science gained from research agreements [relativeAmount]% +

Example: "Science gained from research agreements [+20]%"

+

Applicable to: Global

+
+
+Enables Defensive Pacts +

Applicable to: Global

+
+
+When declaring friendship, both parties gain a [relativeAmount]% boost to great person generation +

Example: "When declaring friendship, both parties gain a [+20]% boost to great person generation"

+

Applicable to: Global

+
+
+Influence of all other civilizations with all city-states degrades [relativeAmount]% faster +

Example: "Influence of all other civilizations with all city-states degrades [+20]% faster"

+

Applicable to: Global

+
+
+Gain [amount] Influence with a [baseUnitFilter] gift to a City-State +

Example: "Gain [3] Influence with a [Melee] gift to a City-State"

+

Applicable to: Global

+
+
+Resting point for Influence with City-States following this religion [amount] +

Example: "Resting point for Influence with City-States following this religion [3]"

+

Applicable to: Global

+
+
+Notified of new Barbarian encampments +

Applicable to: Global

+
+
+Receive triple Gold from Barbarian encampments and pillaging Cities +

Applicable to: Global

+
+
+When conquering an encampment, earn [amount] Gold and recruit a Barbarian unit +

Example: "When conquering an encampment, earn [3] Gold and recruit a Barbarian unit"

+

Applicable to: Global

+
+
+When defeating a [mapUnitFilter] unit, earn [amount] Gold and recruit it +

Example: "When defeating a [Wounded] unit, earn [3] Gold and recruit it"

+

Applicable to: Global

+
+
+May choose [amount] additional [beliefType] beliefs when [foundingOrEnhancing] a religion +

Example: "May choose [3] additional [Follower] beliefs when [founding] a religion"

+

Applicable to: Global

+
+
+May choose [amount] additional belief(s) of any type when [foundingOrEnhancing] a religion +

Example: "May choose [3] additional belief(s) of any type when [founding] a religion"

+

Applicable to: Global

+
+
+[stats] when a city adopts this religion for the first time +

Example: "[+1 Gold, +2 Production] when a city adopts this religion for the first time"

+

This unique's effect can be modified with <(modified by game speed)> +Applicable to: Global

+
+
+[relativeAmount]% Natural religion spread [cityFilter] +

Example: "[+20]% Natural religion spread [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+Religion naturally spreads to cities [amount] tiles away +

Example: "Religion naturally spreads to cities [3] tiles away"

+

Applicable to: Global, FollowerBelief

+
+
+May not generate great prophet equivalents naturally +

Applicable to: Global

+
+
+[relativeAmount]% Faith cost of generating Great Prophet equivalents +

Example: "[+20]% Faith cost of generating Great Prophet equivalents"

+

Applicable to: Global

+
+
+[relativeAmount]% spy effectiveness [cityFilter] +

Example: "[+20]% spy effectiveness [in all cities]"

+

Applicable to: Global

+
+
+[relativeAmount]% enemy spy effectiveness [cityFilter] +

Example: "[+20]% enemy spy effectiveness [in all cities]"

+

Applicable to: Global

+
+
+New spies start with [amount] level(s) +

Example: "New spies start with [3] level(s)"

+

Applicable to: Global

+
+
+Triggers victory +

Applicable to: Global

+
+
+Triggers a Cultural Victory upon completion +

Applicable to: Global

+
+
+May buy items in puppet cities +

Applicable to: Global

+
+
+May not annex cities +

Applicable to: Global

+
+
+"Borrows" city names from other civilizations in the game +

Applicable to: Global

+
+
+Cities are razed [amount] times as fast +

Example: "Cities are razed [3] times as fast"

+

Applicable to: Global

+
+
+Receive a tech boost when scientific buildings/wonders are built in capital +

Applicable to: Global

+
+
+[relativeAmount]% Golden Age length +

Example: "[+20]% Golden Age length"

+

Applicable to: Global

+
+
+Population loss from nuclear attacks [relativeAmount]% [cityFilter] +

Example: "Population loss from nuclear attacks [+20]% [in all cities]"

+

Applicable to: Global

+
+
+Damage to garrison from nuclear attacks [relativeAmount]% [cityFilter] +

Example: "Damage to garrison from nuclear attacks [+20]% [in all cities]"

+

Applicable to: Global

+
+
+Rebel units may spawn +

Applicable to: Global

+
+
+[relativeAmount]% Strength +

Example: "[+20]% Strength"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% Strength decreasing with distance from the capital +

Example: "[+20]% Strength decreasing with distance from the capital"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% to Flank Attack bonuses +

Example: "[+20]% to Flank Attack bonuses"

+

Applicable to: Global, Unit

+
+
+[amount] additional attacks per turn +

Example: "[3] additional attacks per turn"

+

Applicable to: Global, Unit

+
+
+[amount] Movement +

Example: "[3] Movement"

+

Applicable to: Global, Unit

+
+
+[amount] Sight +

Example: "[3] Sight"

+

Applicable to: Global, Unit, Terrain

+
+
+[amount] Range +

Example: "[3] Range"

+

Applicable to: Global, Unit

+
+
+[relativeAmount] Air Interception Range +

Example: "[+20] Air Interception Range"

+

Applicable to: Global, Unit

+
+
+[amount] HP when healing +

Example: "[3] HP when healing"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% Spread Religion Strength +

Example: "[+20]% Spread Religion Strength"

+

Applicable to: Global, Unit

+
+
+When spreading religion to a city, gain [amount] times the amount of followers of other religions as [stat] +

Example: "When spreading religion to a city, gain [3] times the amount of followers of other religions as [Culture]"

+

Applicable to: Global, Unit

+
+
+Ranged attacks may be performed over obstacles +

Applicable to: Global, Unit

+
+
+No defensive terrain bonus +

Applicable to: Global, Unit

+
+
+No defensive terrain penalty +

Applicable to: Global, Unit

+
+
+No damage penalty for wounded units +

Applicable to: Global, Unit

+
+
+Unable to capture cities +

Applicable to: Global, Unit

+
+
+Unable to pillage tiles +

Applicable to: Global, Unit

+
+
+No movement cost to pillage +

Applicable to: Global, Unit

+
+
+May heal outside of friendly territory +

Applicable to: Global, Unit

+
+
+All healing effects doubled +

Applicable to: Global, Unit

+
+
+Heals [amount] damage if it kills a unit +

Example: "Heals [3] damage if it kills a unit"

+

Applicable to: Global, Unit

+
+
+Can only heal by pillaging +

Applicable to: Global, Unit

+
+
+[relativeAmount]% maintenance costs +

Example: "[+20]% maintenance costs"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% Gold cost of upgrading +

Example: "[+20]% Gold cost of upgrading"

+

Applicable to: Global, Unit

+
+
+Earn [amount]% of the damage done to [combatantFilter] units as [civWideStat] +

Example: "Earn [3]% of the damage done to [City] units as [Gold]"

+

Applicable to: Global, Unit

+
+
+Upon capturing a city, receive [amount] times its [stat] production as [civWideStat] immediately +

Example: "Upon capturing a city, receive [3] times its [Culture] production as [Gold] immediately"

+

Applicable to: Global, Unit

+
+
+Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [civWideStat] +

Example: "Earn [3]% of killed [Wounded] unit's [Cost] as [Gold]"

+

Applicable to: Global, Unit

+
+
+[amount] XP gained from combat +

Example: "[3] XP gained from combat"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% XP gained from combat +

Example: "[+20]% XP gained from combat"

+

Applicable to: Global, Unit

+
+
+[greatPerson] is earned [relativeAmount]% faster +

Example: "[Great General] is earned [+20]% faster"

+

Applicable to: Global, Unit

+
+
+[amount] Movement point cost to disembark +

Example: "[3] Movement point cost to disembark"

+

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Global, Unit

+
+
+[amount] Movement point cost to embark +

Example: "[3] Movement point cost to embark"

+

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Global, Unit

+
+

Nation uniques

+
+Will not be chosen for new games +

Applicable to: Nation

+
+
+Starts with [tech] +

Example: "Starts with [Agriculture]"

+

Applicable to: Nation

+
+
+Starts with [policy] adopted +

Example: "Starts with [Oligarchy] adopted"

+

Applicable to: Nation

+
+
+All units move through Forest and Jungle Tiles in friendly territory as if they have roads. These tiles can be used to establish City Connections upon researching the Wheel. +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Nation

+
+
+Units ignore terrain costs when moving into any tile with Hills +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Nation

+
+
+Excluded from map editor +

Applicable to: Nation, Terrain, Improvement, Resource

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

Personality uniques

+
+Will not build [baseUnitFilter/buildingFilter] +

Example: "Will not build [Melee]"

+

Applicable to: Personality

+
+

Era uniques

+
+Starting in this era disables religion +

Applicable to: Era

+
+
+Every major Civilization gains a spy once a civilization enters this era +

Applicable to: Era

+
+

Tech uniques

+
+Starting tech +

Applicable to: Tech

+
+
+Can be continually researched +

Applicable to: Tech

+
+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Unavailable +

Meant to be used together with conditionals, like "Unavailable ". +Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Cannot be hurried +

Applicable to: Tech, Building

+
+
+[relativeAmount]% weight to this choice for AI decisions +

Example: "[+20]% weight to this choice for AI decisions"

+

Applicable to: Tech, Policy, Promotion

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

Policy uniques

+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Unavailable +

Meant to be used together with conditionals, like "Unavailable ". +Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+[relativeAmount]% weight to this choice for AI decisions +

Example: "[+20]% weight to this choice for AI decisions"

+

Applicable to: Tech, Policy, Promotion

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

FounderBelief uniques

+
+

Uniques for Founder and Enhancer type Beliefs, that will apply to the founder of this religion

+
+
+[stats] for each global city following this religion +

Example: "[+1 Gold, +2 Production] for each global city following this religion"

+

Applicable to: FounderBelief

+
+
+[stats] from every [amount] global followers [cityFilter] +

Example: "[+1 Gold, +2 Production] from every [3] global followers [in all cities]"

+

Applicable to: FounderBelief

+
+
+[relativeAmount]% [stat] from every follower, up to [relativeAmount]% +

Example: "[+20]% [Culture] from every follower, up to [+20]%"

+

Applicable to: FounderBelief, FollowerBelief

+
+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

FollowerBelief uniques

+
+

Uniques for Pantheon and Follower type beliefs, that will apply to each city where the religion is the majority religion

+
+
+[stats] [cityFilter] +

Example: "[+1 Gold, +2 Production] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from every specialist [cityFilter] +

Example: "[+1 Gold, +2 Production] from every specialist [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] per [amount] population [cityFilter] +

Example: "[+1 Gold, +2 Production] per [3] population [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] in cities on [terrainFilter] tiles +

Example: "[+1 Gold, +2 Production] in cities on [Fresh Water] tiles"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from all [buildingFilter] buildings +

Example: "[+1 Gold, +2 Production] from all [Culture] buildings"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from [tileFilter] tiles [cityFilter] +

Example: "[+1 Gold, +2 Production] from [Farm] tiles [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from [tileFilter] tiles without [tileFilter] [cityFilter] +

Example: "[+1 Gold, +2 Production] from [Farm] tiles without [Farm] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from every [tileFilter/specialist/buildingFilter] +

Example: "[+1 Gold, +2 Production] from every [Farm]"

+

Applicable to: Global, FollowerBelief

+
+
+[stats] from each Trade Route +

Example: "[+1 Gold, +2 Production] from each Trade Route"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% [stat] +

Example: "[+20]% [Culture]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% [stat] [cityFilter] +

Example: "[+20]% [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% [stat] from every [tileFilter/buildingFilter] +

Example: "[+20]% [Culture] from every [Farm]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Yield from every [tileFilter/buildingFilter] +

Example: "[+20]% Yield from every [Farm]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% [stat] from every follower, up to [relativeAmount]% +

Example: "[+20]% [Culture] from every follower, up to [+20]%"

+

Applicable to: FounderBelief, FollowerBelief

+
+
+[relativeAmount]% Production when constructing [buildingFilter] buildings [cityFilter] +

Example: "[+20]% Production when constructing [Culture] buildings [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Production when constructing [baseUnitFilter] units [cityFilter] +

Example: "[+20]% Production when constructing [Melee] units [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Production when constructing [buildingFilter] wonders [cityFilter] +

Example: "[+20]% Production when constructing [Culture] wonders [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Production towards any buildings that already exist in the Capital +

Example: "[+20]% Production towards any buildings that already exist in the Capital"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% growth [cityFilter] +

Example: "[+20]% growth [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[amount]% Food is carried over after population increases [cityFilter] +

Example: "[3]% Food is carried over after population increases [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Food consumption by specialists [cityFilter] +

Example: "[+20]% Food consumption by specialists [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Unhappiness from [populationFilter] [cityFilter] +

Example: "[+20]% Unhappiness from [Followers of this Religion] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount]) +

Example: "May buy [Melee] units for [3] [Culture] [in all cities] at an increasing price ([3])"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [buildingFilter] buildings for [amount] [stat] [cityFilter] at an increasing price ([amount]) +

Example: "May buy [Culture] buildings for [3] [Culture] [in all cities] at an increasing price ([3])"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] +

Example: "May buy [Melee] units for [3] [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [buildingFilter] buildings for [amount] [stat] [cityFilter] +

Example: "May buy [Culture] buildings for [3] [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [baseUnitFilter] units with [stat] [cityFilter] +

Example: "May buy [Melee] units with [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [buildingFilter] buildings with [stat] [cityFilter] +

Example: "May buy [Culture] buildings with [Culture] [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [baseUnitFilter] units with [stat] for [amount] times their normal Production cost +

Example: "May buy [Melee] units with [Culture] for [3] times their normal Production cost"

+

Applicable to: Global, FollowerBelief

+
+
+May buy [buildingFilter] buildings with [stat] for [amount] times their normal Production cost +

Example: "May buy [Culture] buildings with [Culture] for [3] times their normal Production cost"

+

Applicable to: Global, FollowerBelief

+
+
+[stat] cost of purchasing items in cities [relativeAmount]% +

Example: "[Culture] cost of purchasing items in cities [+20]%"

+

Applicable to: Global, FollowerBelief

+
+
+[stat] cost of purchasing [buildingFilter] buildings [relativeAmount]% +

Example: "[Culture] cost of purchasing [Culture] buildings [+20]%"

+

Applicable to: Global, FollowerBelief

+
+
+[stat] cost of purchasing [baseUnitFilter] units [relativeAmount]% +

Example: "[Culture] cost of purchasing [Melee] units [+20]%"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% maintenance cost for buildings [cityFilter] +

Example: "[+20]% maintenance cost for buildings [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Culture cost of natural border growth [cityFilter] +

Example: "[+20]% Culture cost of natural border growth [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Gold cost of acquiring tiles [cityFilter] +

Example: "[+20]% Gold cost of acquiring tiles [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Great Person generation [cityFilter] +

Example: "[+20]% Great Person generation [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+New [baseUnitFilter] units start with [amount] Experience [cityFilter] +

Example: "New [Melee] units start with [3] Experience [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+All newly-trained [baseUnitFilter] units [cityFilter] receive the [promotion] promotion +

Example: "All newly-trained [Melee] units [in all cities] receive the [Shock I] promotion"

+

Applicable to: Global, FollowerBelief

+
+
+[mapUnitFilter] Units adjacent to this city heal [amount] HP per turn when healing +

Example: "[Wounded] Units adjacent to this city heal [3] HP per turn when healing"

+

Applicable to: Global, FollowerBelief

+
+
+[relativeAmount]% Strength for cities +

Example: "[+20]% Strength for cities"

+

Applicable to: Global, FollowerBelief

+
+
+Provides [amount] [resource] +

Example: "Provides [3] [Iron]"

+

Applicable to: Global, FollowerBelief, Improvement

+
+
+[relativeAmount]% Natural religion spread [cityFilter] +

Example: "[+20]% Natural religion spread [in all cities]"

+

Applicable to: Global, FollowerBelief

+
+
+Religion naturally spreads to cities [amount] tiles away +

Example: "Religion naturally spreads to cities [3] tiles away"

+

Applicable to: Global, FollowerBelief

+
+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [civWideStat] when killed within 4 tiles of a city following this religion +

Example: "Earn [3]% of [Wounded] unit's [Cost] as [Gold] when killed within 4 tiles of a city following this religion"

+

Applicable to: FollowerBelief

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

Building uniques

+
+Consumes [amount] [resource] +

Example: "Consumes [3] [Iron]"

+

Applicable to: Building, Unit, Improvement

+
+
+Costs [amount] [stockpiledResource] +

These resources are removed when work begins on the construction. Do not confuse with "costs [amount] [stockpiledResource]" (lowercase 'c'), the Unit Action Modifier. +Example: "Costs [3] [Mana]"

+

Applicable to: Building, Unit, Improvement

+
+
+Unbuildable +

Blocks from being built, possibly by conditional. However it can still appear in the menu and be bought with other means such as Gold or Faith +Applicable to: Building, Unit, Improvement

+
+
+Cannot be purchased +

Applicable to: Building, Unit

+
+
+Can be purchased with [stat] [cityFilter] +

Example: "Can be purchased with [Culture] [in all cities]"

+

Applicable to: Building, Unit

+
+
+Can be purchased for [amount] [stat] [cityFilter] +

Example: "Can be purchased for [3] [Culture] [in all cities]"

+

Applicable to: Building, Unit

+
+
+Limited to [amount] per Civilization +

Example: "Limited to [3] per Civilization"

+

Applicable to: Building, Unit

+
+
+Hidden until [amount] social policy branches have been completed +

Example: "Hidden until [3] social policy branches have been completed"

+

Applicable to: Building, Unit

+
+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Unavailable +

Meant to be used together with conditionals, like "Unavailable ". +Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Excess Food converted to Production when under construction +

Applicable to: Building, Unit

+
+
+Requires at least [amount] population +

Example: "Requires at least [3] population"

+

Applicable to: Building, Unit

+
+
+Triggers a global alert upon build start +

Applicable to: Building, Unit

+
+
+Triggers a global alert upon completion +

Applicable to: Building, Unit

+
+
+Cost increases by [amount] per owned city +

Example: "Cost increases by [3] per owned city"

+

Applicable to: Building, Unit

+
+
+Cost increases by [amount] when built +

Example: "Cost increases by [3] when built"

+

Applicable to: Building, Unit

+
+
+[amount]% production cost +

Intended to be used with conditionals to dynamically alter construction costs +Example: "[3]% production cost"

+

Applicable to: Building, Unit

+
+
+Can only be built +

Meant to be used together with conditionals, like "Can only be built ". Only allows Building when ALL conditionals are met. Will also NOT block Upgrade and Transform actions. See also OnlyAvailable. +Applicable to: Building, Unit

+
+
+Must have an owned [tileFilter] within [amount] tiles +

Example: "Must have an owned [Farm] within [3] tiles"

+

Applicable to: Building

+
+
+Enables nuclear weapon +

Applicable to: Building

+
+
+Must be on [tileFilter] +

Example: "Must be on [Farm]"

+

Applicable to: Building

+
+
+Must not be on [tileFilter] +

Example: "Must not be on [Farm]"

+

Applicable to: Building

+
+
+Must be next to [tileFilter] +

Example: "Must be next to [Farm]"

+

Applicable to: Building, Improvement

+
+
+Must not be next to [tileFilter] +

Example: "Must not be next to [Farm]"

+

Applicable to: Building

+
+
+Unsellable +

Applicable to: Building

+
+
+Obsolete with [tech] +

Example: "Obsolete with [Agriculture]"

+

Applicable to: Building, Improvement, Resource

+
+
+Indicates the capital city +

Applicable to: Building

+
+
+Moves to new capital when capital changes +

Applicable to: Building

+
+
+Provides 1 extra copy of each improved luxury resource near this City +

Applicable to: Building

+
+
+Destroyed when the city is captured +

Applicable to: Building

+
+
+Never destroyed when the city is captured +

Applicable to: Building

+
+
+Doubles Gold given to enemy if city is captured +

Applicable to: Building

+
+
+Remove extra unhappiness from annexed cities +

Applicable to: Building

+
+
+Connects trade routes over water +

Applicable to: Building

+
+
+Automatically built in all cities where it is buildable +

Applicable to: Building

+
+
+Creates a [improvementName] improvement on a specific tile +

Example: "Creates a [Trading Post] improvement on a specific tile"

+

Applicable to: Building

+
+
+Spaceship part +

Applicable to: Building, Unit

+
+
+Cannot be hurried +

Applicable to: Tech, Building

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Shown while unbuilable +

Applicable to: Building, Unit

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

UnitAction uniques

+
+

Uniques that affect a unit's actions, and can be modified by UnitActionModifiers

+
+
+Founds a new city +

Applicable to: UnitAction

+
+
+Can instantly construct a [improvementFilter] improvement +

Example: "Can instantly construct a [All Road] improvement"

+

Applicable to: UnitAction

+
+
+Can Spread Religion +

Applicable to: UnitAction

+
+
+Can remove other religions from cities +

Applicable to: UnitAction

+
+
+May found a religion +

Applicable to: UnitAction

+
+
+May enhance a religion +

Applicable to: UnitAction

+
+
+Can transform to [unit] +

By default consumes all movement +Example: "Can transform to [Musketman]"

+

Applicable to: UnitAction

+
+

Unit uniques

+
+

Uniques that can be added to units, unit types, or promotions

+
+
+[relativeAmount]% construction time for [improvementFilter] improvements +

Example: "[+20]% construction time for [All Road] improvements"

+

Applicable to: Global, Unit

+
+
+Great General provides double combat bonus +

Applicable to: Global, Unit

+
+
+Consumes [amount] [resource] +

Example: "Consumes [3] [Iron]"

+

Applicable to: Building, Unit, Improvement

+
+
+Costs [amount] [stockpiledResource] +

These resources are removed when work begins on the construction. Do not confuse with "costs [amount] [stockpiledResource]" (lowercase 'c'), the Unit Action Modifier. +Example: "Costs [3] [Mana]"

+

Applicable to: Building, Unit, Improvement

+
+
+Unbuildable +

Blocks from being built, possibly by conditional. However it can still appear in the menu and be bought with other means such as Gold or Faith +Applicable to: Building, Unit, Improvement

+
+
+Cannot be purchased +

Applicable to: Building, Unit

+
+
+Can be purchased with [stat] [cityFilter] +

Example: "Can be purchased with [Culture] [in all cities]"

+

Applicable to: Building, Unit

+
+
+Can be purchased for [amount] [stat] [cityFilter] +

Example: "Can be purchased for [3] [Culture] [in all cities]"

+

Applicable to: Building, Unit

+
+
+Limited to [amount] per Civilization +

Example: "Limited to [3] per Civilization"

+

Applicable to: Building, Unit

+
+
+Hidden until [amount] social policy branches have been completed +

Example: "Hidden until [3] social policy branches have been completed"

+

Applicable to: Building, Unit

+
+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Unavailable +

Meant to be used together with conditionals, like "Unavailable ". +Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Excess Food converted to Production when under construction +

Applicable to: Building, Unit

+
+
+Requires at least [amount] population +

Example: "Requires at least [3] population"

+

Applicable to: Building, Unit

+
+
+Triggers a global alert upon build start +

Applicable to: Building, Unit

+
+
+Triggers a global alert upon completion +

Applicable to: Building, Unit

+
+
+Cost increases by [amount] per owned city +

Example: "Cost increases by [3] per owned city"

+

Applicable to: Building, Unit

+
+
+Cost increases by [amount] when built +

Example: "Cost increases by [3] when built"

+

Applicable to: Building, Unit

+
+
+[amount]% production cost +

Intended to be used with conditionals to dynamically alter construction costs +Example: "[3]% production cost"

+

Applicable to: Building, Unit

+
+
+Can only be built +

Meant to be used together with conditionals, like "Can only be built ". Only allows Building when ALL conditionals are met. Will also NOT block Upgrade and Transform actions. See also OnlyAvailable. +Applicable to: Building, Unit

+
+
+May create improvements on water resources +

Applicable to: Unit

+
+
+Can build [improvementFilter/terrainFilter] improvements on tiles +

Example: "Can build [All Road] improvements on tiles"

+

Applicable to: Unit

+
+
+Can be added to [comment] in the Capital +

Example: "Can be added to [comment] in the Capital"

+

Applicable to: Unit

+
+
+Prevents spreading of religion to the city it is next to +

Applicable to: Unit

+
+
+Removes other religions when spreading religion +

Applicable to: Unit

+
+
+May Paradrop up to [amount] tiles from inside friendly territory +

Example: "May Paradrop up to [3] tiles from inside friendly territory"

+

Applicable to: Unit

+
+
+Can perform Air Sweep +

Applicable to: Unit

+
+
+Can speed up construction of a building +

Applicable to: Unit

+
+
+Can speed up the construction of a wonder +

Applicable to: Unit

+
+
+Can hurry technology research +

Applicable to: Unit

+
+
+Can generate a large amount of culture +

Applicable to: Unit

+
+
+Can undertake a trade mission with City-State, giving a large sum of gold and [amount] Influence +

Example: "Can undertake a trade mission with City-State, giving a large sum of gold and [3] Influence"

+

Applicable to: Unit

+
+
+Automation is a primary action +

Applicable to: Unit

+
+
+[relativeAmount]% Strength +

Example: "[+20]% Strength"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% Strength decreasing with distance from the capital +

Example: "[+20]% Strength decreasing with distance from the capital"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% to Flank Attack bonuses +

Example: "[+20]% to Flank Attack bonuses"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% Strength for enemy [mapUnitFilter] units in adjacent [tileFilter] tiles +

Example: "[+20]% Strength for enemy [Wounded] units in adjacent [Farm] tiles"

+

Applicable to: Unit

+
+
+[relativeAmount]% Strength when stacked with [mapUnitFilter] +

Example: "[+20]% Strength when stacked with [Wounded]"

+

Applicable to: Unit

+
+
+[relativeAmount]% Strength bonus for [mapUnitFilter] units within [amount] tiles +

Example: "[+20]% Strength bonus for [Wounded] units within [3] tiles"

+

Applicable to: Unit

+
+
+[amount] additional attacks per turn +

Example: "[3] additional attacks per turn"

+

Applicable to: Global, Unit

+
+
+[amount] Movement +

Example: "[3] Movement"

+

Applicable to: Global, Unit

+
+
+[amount] Sight +

Example: "[3] Sight"

+

Applicable to: Global, Unit, Terrain

+
+
+[amount] Range +

Example: "[3] Range"

+

Applicable to: Global, Unit

+
+
+[relativeAmount] Air Interception Range +

Example: "[+20] Air Interception Range"

+

Applicable to: Global, Unit

+
+
+[amount] HP when healing +

Example: "[3] HP when healing"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% Spread Religion Strength +

Example: "[+20]% Spread Religion Strength"

+

Applicable to: Global, Unit

+
+
+When spreading religion to a city, gain [amount] times the amount of followers of other religions as [stat] +

Example: "When spreading religion to a city, gain [3] times the amount of followers of other religions as [Culture]"

+

Applicable to: Global, Unit

+
+
+Can only attack [combatantFilter] units +

Example: "Can only attack [City] units"

+

Applicable to: Unit

+
+
+Can only attack [tileFilter] tiles +

Example: "Can only attack [Farm] tiles"

+

Applicable to: Unit

+
+
+Cannot attack +

Applicable to: Unit

+
+
+Must set up to ranged attack +

Applicable to: Unit

+
+
+Self-destructs when attacking +

Applicable to: Unit

+
+
+Eliminates combat penalty for attacking across a coast +

Applicable to: Unit

+
+
+May attack when embarked +

Applicable to: Unit

+
+
+Eliminates combat penalty for attacking over a river +

Applicable to: Unit

+
+
+Blast radius [amount] +

Example: "Blast radius [3]"

+

Applicable to: Unit

+
+
+Ranged attacks may be performed over obstacles +

Applicable to: Global, Unit

+
+
+Nuclear weapon of Strength [amount] +

Example: "Nuclear weapon of Strength [3]"

+

Applicable to: Unit

+
+
+No defensive terrain bonus +

Applicable to: Global, Unit

+
+
+No defensive terrain penalty +

Applicable to: Global, Unit

+
+
+No damage penalty for wounded units +

Applicable to: Global, Unit

+
+
+Uncapturable +

Applicable to: Unit

+
+
+Withdraws before melee combat +

Applicable to: Unit

+
+
+Unable to capture cities +

Applicable to: Global, Unit

+
+
+Unable to pillage tiles +

Applicable to: Global, Unit

+
+
+No movement cost to pillage +

Applicable to: Global, Unit

+
+
+Can move after attacking +

Applicable to: Unit

+
+
+Transfer Movement to [mapUnitFilter] +

Example: "Transfer Movement to [Wounded]"

+

Applicable to: Unit

+
+
+Can move immediately once bought +

Applicable to: Unit

+
+
+May heal outside of friendly territory +

Applicable to: Global, Unit

+
+
+All healing effects doubled +

Applicable to: Global, Unit

+
+
+Heals [amount] damage if it kills a unit +

Example: "Heals [3] damage if it kills a unit"

+

Applicable to: Global, Unit

+
+
+Can only heal by pillaging +

Applicable to: Global, Unit

+
+
+Unit will heal every turn, even if it performs an action +

Applicable to: Unit

+
+
+All adjacent units heal [amount] HP when healing +

Example: "All adjacent units heal [3] HP when healing"

+

Applicable to: Unit

+
+
+No Sight +

Applicable to: Unit

+
+
+Can see over obstacles +

Applicable to: Unit

+
+
+Can carry [amount] [mapUnitFilter] units +

Example: "Can carry [3] [Wounded] units"

+

Applicable to: Unit

+
+
+Can carry [amount] extra [mapUnitFilter] units +

Example: "Can carry [3] extra [Wounded] units"

+

Applicable to: Unit

+
+
+Cannot be carried by [mapUnitFilter] units +

Example: "Cannot be carried by [Wounded] units"

+

Applicable to: Unit

+
+
+[relativeAmount]% chance to intercept air attacks +

Example: "[+20]% chance to intercept air attacks"

+

Applicable to: Unit

+
+
+Damage taken from interception reduced by [relativeAmount]% +

Example: "Damage taken from interception reduced by [+20]%"

+

Applicable to: Unit

+
+
+[relativeAmount]% Damage when intercepting +

Example: "[+20]% Damage when intercepting"

+

Applicable to: Unit

+
+
+[amount] extra interceptions may be made per turn +

Example: "[3] extra interceptions may be made per turn"

+

Applicable to: Unit

+
+
+Cannot be intercepted +

Applicable to: Unit

+
+
+Cannot intercept [mapUnitFilter] units +

Example: "Cannot intercept [Wounded] units"

+

Applicable to: Unit

+
+
+[relativeAmount]% Strength when performing Air Sweep +

Example: "[+20]% Strength when performing Air Sweep"

+

Applicable to: Unit

+
+
+[relativeAmount]% maintenance costs +

Example: "[+20]% maintenance costs"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% Gold cost of upgrading +

Example: "[+20]% Gold cost of upgrading"

+

Applicable to: Global, Unit

+
+
+Earn [amount]% of the damage done to [combatantFilter] units as [civWideStat] +

Example: "Earn [3]% of the damage done to [City] units as [Gold]"

+

Applicable to: Global, Unit

+
+
+Upon capturing a city, receive [amount] times its [stat] production as [civWideStat] immediately +

Example: "Upon capturing a city, receive [3] times its [Culture] production as [Gold] immediately"

+

Applicable to: Global, Unit

+
+
+Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [civWideStat] +

Example: "Earn [3]% of killed [Wounded] unit's [Cost] as [Gold]"

+

Applicable to: Global, Unit

+
+
+May capture killed [mapUnitFilter] units +

Example: "May capture killed [Wounded] units"

+

Applicable to: Unit

+
+
+[amount] XP gained from combat +

Example: "[3] XP gained from combat"

+

Applicable to: Global, Unit

+
+
+[relativeAmount]% XP gained from combat +

Example: "[+20]% XP gained from combat"

+

Applicable to: Global, Unit

+
+
+Can be earned through combat +

Applicable to: Unit

+
+
+[greatPerson] is earned [relativeAmount]% faster +

Example: "[Great General] is earned [+20]% faster"

+

Applicable to: Global, Unit

+
+
+Invisible to others +

Applicable to: Unit

+
+
+Invisible to non-adjacent units +

Applicable to: Unit

+
+
+Can see invisible [mapUnitFilter] units +

Example: "Can see invisible [Wounded] units"

+

Applicable to: Unit

+
+
+May upgrade to [unit] through ruins-like effects +

Example: "May upgrade to [Musketman] through ruins-like effects"

+

Applicable to: Unit

+
+
+Can upgrade to [unit] +

Example: "Can upgrade to [Musketman]"

+

Applicable to: Unit

+
+
+Destroys tile improvements when attacking +

Applicable to: Unit

+
+
+Cannot move +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+Double movement in [terrainFilter] +

Example: "Double movement in [Fresh Water]"

+

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+All tiles cost 1 movement +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+May travel on Water tiles without embarking +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+Can pass through impassable tiles +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+Ignores terrain cost +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+Ignores Zone of Control +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+Rough terrain penalty +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+Can enter ice tiles +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+Cannot enter ocean tiles +

Applicable to: Unit

+
+
+May enter foreign tiles without open borders +

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+May enter foreign tiles without open borders, but loses [amount] religious strength each turn it ends there +

Example: "May enter foreign tiles without open borders, but loses [3] religious strength each turn it ends there"

+

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Unit

+
+
+[amount] Movement point cost to disembark +

Example: "[3] Movement point cost to disembark"

+

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Global, Unit

+
+
+[amount] Movement point cost to embark +

Example: "[3] Movement point cost to embark"

+

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Global, Unit

+
+
+Never appears as a Barbarian unit +

Applicable to: Unit

+
+
+Religious Unit +

Applicable to: Unit

+
+
+Spaceship part +

Applicable to: Building, Unit

+
+
+Takes your religion over the one in their birth city +

Applicable to: Unit

+
+
+Great Person - [comment] +

Example: "Great Person - [comment]"

+

Applicable to: Unit

+
+
+Is part of Great Person group [comment] +

Great people in the same group increase teach other's costs when gained. Gaining one will make all others in the same group cost more GPP. +Example: "Is part of Great Person group [comment]"

+

Applicable to: Unit

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Shown while unbuilable +

Applicable to: Building, Unit

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

UnitType uniques

+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

Promotion uniques

+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Unavailable +

Meant to be used together with conditionals, like "Unavailable ". +Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Doing so will consume this opportunity to choose a Promotion +

Applicable to: Promotion

+
+
+This Promotion is free +

Applicable to: Promotion

+
+
+[relativeAmount]% weight to this choice for AI decisions +

Example: "[+20]% weight to this choice for AI decisions"

+

Applicable to: Tech, Policy, Promotion

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

Terrain uniques

+
+[stats] +

Example: "[+1 Gold, +2 Production]"

+

Applicable to: Global, Terrain, Improvement

+
+
+[amount] Sight +

Example: "[3] Sight"

+

Applicable to: Global, Unit, Terrain

+
+
+Must be adjacent to [amount] [simpleTerrain] tiles +

Example: "Must be adjacent to [3] [Elevated] tiles"

+

Applicable to: Terrain

+
+
+Must be adjacent to [amount] to [amount] [simpleTerrain] tiles +

Example: "Must be adjacent to [3] to [3] [Elevated] tiles"

+

Applicable to: Terrain

+
+
+Must not be on [amount] largest landmasses +

Example: "Must not be on [3] largest landmasses"

+

Applicable to: Terrain

+
+
+Must be on [amount] largest landmasses +

Example: "Must be on [3] largest landmasses"

+

Applicable to: Terrain

+
+
+Occurs on latitudes from [amount] to [amount] percent of distance equator to pole +

Example: "Occurs on latitudes from [3] to [3] percent of distance equator to pole"

+

Applicable to: Terrain

+
+
+Occurs in groups of [amount] to [amount] tiles +

Example: "Occurs in groups of [3] to [3] tiles"

+

Applicable to: Terrain

+
+
+Neighboring tiles will convert to [baseTerrain/terrainFeature] +

Supports conditionals that need only a Tile as context and nothing else, like <with [n]% chance>, and applies them per neighbor. +If your mod renames Coast or Lakes, do not use this with one of these as parameter, as the code preventing artifacts won't work. +Example: "Neighboring tiles will convert to [Grassland]"

+

Applicable to: Terrain

+
+
+Grants [stats] to the first civilization to discover it +

Example: "Grants [+1 Gold, +2 Production] to the first civilization to discover it"

+

Applicable to: Terrain

+
+
+Units ending their turn on this terrain take [amount] damage +

Example: "Units ending their turn on this terrain take [3] damage"

+

Due to performance considerations, this unique is cached, thus conditionals that may change within a turn may not work. +Applicable to: Terrain

+
+
+Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game +

Example: "Grants [Shock I] ([comment]) to adjacent [Wounded] units for the rest of the game"

+

Applicable to: Terrain

+
+
+[amount] Strength for cities built on this terrain +

Example: "[3] Strength for cities built on this terrain"

+

Applicable to: Terrain

+
+
+Provides a one-time Production bonus to the closest city when cut down +

Applicable to: Terrain

+
+
+Vegetation +

Applicable to: Terrain, Improvement

+
+
+Tile provides yield without assigned population +

Applicable to: Terrain, Improvement

+
+
+Nullifies all other stats this tile provides +

Applicable to: Terrain

+
+
+Only [improvementFilter] improvements may be built on this tile +

Example: "Only [All Road] improvements may be built on this tile"

+

Applicable to: Terrain

+
+
+Blocks line-of-sight from tiles at same elevation +

Applicable to: Terrain

+
+
+Has an elevation of [amount] for visibility calculations +

Example: "Has an elevation of [3] for visibility calculations"

+

Applicable to: Terrain

+
+
+Always Fertility [amount] for Map Generation +

Example: "Always Fertility [3] for Map Generation"

+

Applicable to: Terrain

+
+
+[amount] to Fertility for Map Generation +

Example: "[3] to Fertility for Map Generation"

+

Applicable to: Terrain

+
+
+A Region is formed with at least [amount]% [simpleTerrain] tiles, with priority [amount] +

Example: "A Region is formed with at least [3]% [Elevated] tiles, with priority [3]"

+

Applicable to: Terrain

+
+
+A Region is formed with at least [amount]% [simpleTerrain] tiles and [simpleTerrain] tiles, with priority [amount] +

Example: "A Region is formed with at least [3]% [Elevated] tiles and [Elevated] tiles, with priority [3]"

+

Applicable to: Terrain

+
+
+A Region can not contain more [simpleTerrain] tiles than [simpleTerrain] tiles +

Example: "A Region can not contain more [Elevated] tiles than [Elevated] tiles"

+

Applicable to: Terrain

+
+
+Base Terrain on this tile is not counted for Region determination +

Applicable to: Terrain

+
+
+Starts in regions of this type receive an extra [resource] +

Example: "Starts in regions of this type receive an extra [Iron]"

+

Applicable to: Terrain

+
+
+Never receives any resources +

Applicable to: Terrain

+
+
+Becomes [terrainName] when adjacent to [terrainFilter] +

Example: "Becomes [Forest] when adjacent to [Fresh Water]"

+

Applicable to: Terrain

+
+
+Considered [terrainQuality] when determining start locations +

Example: "Considered [Undesirable] when determining start locations"

+

Applicable to: Terrain

+
+
+Doesn't generate naturally +

Applicable to: Terrain, Resource

+
+
+Occurs at temperature between [fraction] and [fraction] and humidity between [fraction] and [fraction] +

Example: "Occurs at temperature between [0.5] and [0.5] and humidity between [0.5] and [0.5]"

+

Applicable to: Terrain, Resource

+
+
+Occurs in chains at high elevations +

Applicable to: Terrain

+
+
+Occurs in groups around high elevations +

Applicable to: Terrain

+
+
+Every [amount] tiles with this terrain will receive a major deposit of a strategic resource. +

Example: "Every [3] tiles with this terrain will receive a major deposit of a strategic resource."

+

Applicable to: Terrain

+
+
+Rare feature +

Applicable to: Terrain

+
+
+[amount]% Chance to be destroyed by nukes +

Example: "[3]% Chance to be destroyed by nukes"

+

Applicable to: Terrain

+
+
+Fresh water +

Applicable to: Terrain

+
+
+Rough terrain +

Applicable to: Terrain

+
+
+Excluded from map editor +

Applicable to: Nation, Terrain, Improvement, Resource

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Suppress warning [validationWarning] +

Allows suppressing specific validation warnings. Errors, deprecation warnings, or warnings about untyped and non-filtering uniques should be heeded, not suppressed, and are therefore not accepted. Note that this can be used in ModOptions, in the uniques a warning is about, or as modifier on the unique triggering a warning - but you still need to be specific. Even in the modifier case you will need to specify a sufficiently selective portion of the warning text as parameter. +Example: "Suppress warning [Tinman is supposed to automatically upgrade at tech Clockwork, and therefore Servos for its upgrade Mecha may not yet be researched! -or- is supposed to automatically upgrade]"

+

Applicable to: Triggerable, Terrain, Speed, ModOptions, MetaModifier

+
+

Improvement uniques

+
+[stats] +

Example: "[+1 Gold, +2 Production]"

+

Applicable to: Global, Terrain, Improvement

+
+
+Consumes [amount] [resource] +

Example: "Consumes [3] [Iron]"

+

Applicable to: Building, Unit, Improvement

+
+
+Provides [amount] [resource] +

Example: "Provides [3] [Iron]"

+

Applicable to: Global, FollowerBelief, Improvement

+
+
+Costs [amount] [stockpiledResource] +

These resources are removed when work begins on the construction. Do not confuse with "costs [amount] [stockpiledResource]" (lowercase 'c'), the Unit Action Modifier. +Example: "Costs [3] [Mana]"

+

Applicable to: Building, Unit, Improvement

+
+
+Unbuildable +

Blocks from being built, possibly by conditional. However it can still appear in the menu and be bought with other means such as Gold or Faith +Applicable to: Building, Unit, Improvement

+
+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Unavailable +

Meant to be used together with conditionals, like "Unavailable ". +Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Must be next to [tileFilter] +

Example: "Must be next to [Farm]"

+

Applicable to: Building, Improvement

+
+
+Obsolete with [tech] +

Example: "Obsolete with [Agriculture]"

+

Applicable to: Building, Improvement, Resource

+
+
+Vegetation +

Applicable to: Terrain, Improvement

+
+
+Tile provides yield without assigned population +

Applicable to: Terrain, Improvement

+
+
+Excluded from map editor +

Applicable to: Nation, Terrain, Improvement, Resource

+
+
+Can also be built on tiles adjacent to fresh water +

Applicable to: Improvement

+
+
+[stats] from [tileFilter] tiles +

Example: "[+1 Gold, +2 Production] from [Farm] tiles"

+

Applicable to: Improvement

+
+
+[stats] for each adjacent [tileFilter] +

Example: "[+1 Gold, +2 Production] for each adjacent [Farm]"

+

Applicable to: Improvement

+
+
+Ensures a minimum tile yield of [stats] +

Example: "Ensures a minimum tile yield of [+1 Gold, +2 Production]"

+

Applicable to: Improvement

+
+
+Can be built outside your borders +

Applicable to: Improvement

+
+
+Can be built just outside your borders +

Applicable to: Improvement

+
+
+Can only be built on [tileFilter] tiles +

Example: "Can only be built on [Farm] tiles"

+

Applicable to: Improvement

+
+
+Cannot be built on [tileFilter] tiles +

Example: "Cannot be built on [Farm] tiles"

+

Applicable to: Improvement

+
+
+Can only be built to improve a resource +

Applicable to: Improvement

+
+
+Does not need removal of [tileFilter] +

Example: "Does not need removal of [Farm]"

+

Applicable to: Improvement

+
+
+Removes removable features when built +

Applicable to: Improvement

+
+
+Gives a defensive bonus of [relativeAmount]% +

Does not accept unit-based conditionals +Example: "Gives a defensive bonus of [+20]%"

+

Applicable to: Improvement

+
+
+Costs [amount] [stat] per turn when in your territory +

Example: "Costs [3] [Culture] per turn when in your territory"

+

Applicable to: Improvement

+
+
+Costs [amount] [stat] per turn +

Example: "Costs [3] [Culture] per turn"

+

Applicable to: Improvement

+
+
+Adjacent enemy units ending their turn take [amount] damage +

Example: "Adjacent enemy units ending their turn take [3] damage"

+

Applicable to: Improvement

+
+
+Great Improvement +

Applicable to: Improvement

+
+
+Provides a random bonus when entered +

Applicable to: Improvement

+
+
+Unpillagable +

Applicable to: Improvement

+
+
+Pillaging this improvement yields approximately [stats] +

Example: "Pillaging this improvement yields approximately [+1 Gold, +2 Production]"

+

Applicable to: Improvement

+
+
+Pillaging this improvement yields [stats] +

Example: "Pillaging this improvement yields [+1 Gold, +2 Production]"

+

Applicable to: Improvement

+
+
+Irremovable +

Applicable to: Improvement

+
+
+Will not be replaced by automated units +

Applicable to: Improvement

+
+
+Improves [resourceFilter] resource in this tile +

This is offered as an alternative to the improvedBy field of a resource. The result will be cached within the resource definition when loading a game, without knowledge about terrain, cities, civs, units or time. Therefore, most conditionals will not work, only those not dependent on game state. +Example: "Improves [Strategic] resource in this tile"

+

Applicable to: Improvement

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

Resource uniques

+
+Obsolete with [tech] +

Example: "Obsolete with [Agriculture]"

+

Applicable to: Building, Improvement, Resource

+
+
+Doesn't generate naturally +

Applicable to: Terrain, Resource

+
+
+Occurs at temperature between [fraction] and [fraction] and humidity between [fraction] and [fraction] +

Example: "Occurs at temperature between [0.5] and [0.5] and humidity between [0.5] and [0.5]"

+

Applicable to: Terrain, Resource

+
+
+Excluded from map editor +

Applicable to: Nation, Terrain, Improvement, Resource

+
+
+Deposits in [tileFilter] tiles always provide [amount] resources +

Example: "Deposits in [Farm] tiles always provide [3] resources"

+

Applicable to: Resource

+
+
+Can only be created by Mercantile City-States +

Applicable to: Resource

+
+
+Stockpiled +

This resource is accumulated each turn, rather than having a set of producers and consumers at a given moment.The current stockpiled amount can be affected with trigger uniques. +Applicable to: Resource

+
+
+City-level resource +

This resource is calculated on a per-city level rather than a per-civ level +Applicable to: Resource

+
+
+Cannot be traded +

Applicable to: Resource

+
+
+Not shown on world screen +

Applicable to: Resource

+
+
+Generated with weight [amount] +

The probability for this resource to be chosen is (this resource weight) / (sum weight of all eligible resources). Resources without a unique are given weight 1 +Example: "Generated with weight [3]"

+

Applicable to: Resource

+
+
+Minor deposits generated with weight [amount] +

The probability for this resource to be chosen is (this resource weight) / (sum weight of all eligible resources). Resources without a unique are not generated as minor deposits. +Example: "Minor deposits generated with weight [3]"

+

Applicable to: Resource

+
+
+Generated near City States with weight [amount] +

The probability for this resource to be chosen is (this resource weight) / (sum weight of all eligible resources). Only assignable to luxuries, resources without a unique are given weight 1 +Example: "Generated near City States with weight [3]"

+

Applicable to: Resource

+
+
+Special placement during map generation +

Applicable to: Resource

+
+
+Generated on every [amount] tiles +

Example: "Generated on every [3] tiles"

+

Applicable to: Resource

+
+
+Guaranteed with Strategic Balance resource option +

Applicable to: Resource

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

Ruins uniques

+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Unavailable +

Meant to be used together with conditionals, like "Unavailable ". +Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Free [unit] found in the ruins +

Example: "Free [Musketman] found in the ruins"

+

Applicable to: Ruins

+
+
+From a randomly chosen tile [positiveAmount] tiles away from the ruins, reveal tiles up to [positiveAmount] tiles away with [positiveAmount]% chance +

Example: "From a randomly chosen tile [3] tiles away from the ruins, reveal tiles up to [3] tiles away with [3]% chance"

+

Applicable to: Ruins

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

Speed uniques

+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Suppress warning [validationWarning] +

Allows suppressing specific validation warnings. Errors, deprecation warnings, or warnings about untyped and non-filtering uniques should be heeded, not suppressed, and are therefore not accepted. Note that this can be used in ModOptions, in the uniques a warning is about, or as modifier on the unique triggering a warning - but you still need to be specific. Even in the modifier case you will need to specify a sufficiently selective portion of the warning text as parameter. +Example: "Suppress warning [Tinman is supposed to automatically upgrade at tech Clockwork, and therefore Servos for its upgrade Mecha may not yet be researched! -or- is supposed to automatically upgrade]"

+

Applicable to: Triggerable, Terrain, Speed, ModOptions, MetaModifier

+
+

Tutorial uniques

+

CityState uniques

+
+Provides military units every ≈[amount] turns +

Example: "Provides military units every ≈[3] turns"

+

Applicable to: CityState

+
+
+Provides a unique luxury +

Applicable to: CityState

+
+

ModOptions uniques

+
+Diplomatic relationships cannot change +

Applicable to: ModOptions

+
+
+Can convert gold to science with sliders +

Applicable to: ModOptions

+
+
+Allow City States to spawn with additional units +

Applicable to: ModOptions

+
+
+Can trade civilization introductions for [positiveAmount] Gold +

Example: "Can trade civilization introductions for [3] Gold"

+

Applicable to: ModOptions

+
+
+Disable religion +

Applicable to: ModOptions

+
+
+Can only start games from the starting era +

Applicable to: ModOptions

+
+
+Allow raze capital +

Applicable to: ModOptions

+
+
+Allow raze holy city +

Applicable to: ModOptions

+
+
+Suppress warning [validationWarning] +

Allows suppressing specific validation warnings. Errors, deprecation warnings, or warnings about untyped and non-filtering uniques should be heeded, not suppressed, and are therefore not accepted. Note that this can be used in ModOptions, in the uniques a warning is about, or as modifier on the unique triggering a warning - but you still need to be specific. Even in the modifier case you will need to specify a sufficiently selective portion of the warning text as parameter. +Example: "Suppress warning [Tinman is supposed to automatically upgrade at tech Clockwork, and therefore Servos for its upgrade Mecha may not yet be researched! -or- is supposed to automatically upgrade]"

+

Applicable to: Triggerable, Terrain, Speed, ModOptions, MetaModifier

+
+
+Mod is incompatible with [modFilter] +

Specifies that your Mod is incompatible with another. Always treated symmetrically, and cannot be overridden by the Mod you are declaring as incompatible. +Example: "Mod is incompatible with [DeCiv Redux]"

+

Applicable to: ModOptions

+
+
+Mod requires [modFilter] +

Specifies that your Extension Mod is only available if any other Mod matching the filter is active. +Example: "Mod requires [DeCiv Redux]"

+

Applicable to: ModOptions

+
+
+Should only be used as permanent audiovisual mod +

Applicable to: ModOptions

+
+
+Can be used as permanent audiovisual mod +

Applicable to: ModOptions

+
+
+Cannot be used as permanent audiovisual mod +

Applicable to: ModOptions

+
+
+Mod preselects map [comment] +

Only meaningful for Mods containing several maps. When this mod is selected on the new game screen's custom maps mod dropdown, the named map will be selected on the map dropdown. Also disables selection by recently modified. Case insensitive. +Example: "Mod preselects map [comment]"

+

Applicable to: ModOptions

+
+

Event uniques

+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Unavailable +

Meant to be used together with conditionals, like "Unavailable ". +Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+

EventChoice uniques

+
+Only available +

Meant to be used together with conditionals, like "Only available ". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen +Applicable to: Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Unavailable +

Meant to be used together with conditionals, like "Unavailable ". +Applicable to: Tech, Policy, Building, Unit, Promotion, Improvement, Ruins, Event, EventChoice

+
+
+Will not be displayed in Civilopedia +

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+
+Comment [comment] +

Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent. +Example: "Comment [comment]"

+

Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, EventChoice

+
+

Conditional uniques

+
+

Modifiers that can be added to other uniques to limit when they will be active

+
+
+<every [positiveAmount] turns> +

Example: "<every [3] turns>"

+

Applicable to: Conditional

+
+
+<before turn number [amount]> +

Example: "<before turn number [3]>"

+

Applicable to: Conditional

+
+
+<after turn number [amount]> +

Example: "<after turn number [3]>"

+

Applicable to: Conditional

+
+
+<on [speed] game speed> +

Example: "<on [Quick] game speed>"

+

Applicable to: Conditional

+
+
+<on [difficulty] difficulty> +

Example: "<on [Prince] difficulty>"

+

Applicable to: Conditional

+
+
+<when [victoryType] Victory is enabled> +

Example: "<when [Domination] Victory is enabled>"

+

Applicable to: Conditional

+
+
+<when [victoryType] Victory is disabled> +

Example: "<when [Domination] Victory is disabled>"

+

Applicable to: Conditional

+
+
+<when religion is enabled> +

Applicable to: Conditional

+
+
+<when religion is disabled> +

Applicable to: Conditional

+
+
+<when espionage is enabled> +

Applicable to: Conditional

+
+
+<when espionage is disabled> +

Applicable to: Conditional

+
+
+<with [amount]% chance> +

Example: "<with [3]% chance>"

+

Applicable to: Conditional

+
+
+<if tutorials are enabled> +

Applicable to: Conditional

+
+
+<if tutorial [comment] is completed> +

Example: "<if tutorial [comment] is completed>"

+

Applicable to: Conditional

+
+
+<for [civFilter] Civilizations> +

Example: "<for [City-States] Civilizations>"

+

Applicable to: Conditional

+
+
+<when at war> +

Applicable to: Conditional

+
+
+<when not at war> +

Applicable to: Conditional

+
+
+<during a Golden Age> +

Applicable to: Conditional

+
+
+<during We Love The King Day> +

Applicable to: Conditional

+
+
+<while the empire is happy> +

Applicable to: Conditional

+
+
+<when between [amount] and [amount] Happiness> +

Example: "<when between [3] and [3] Happiness>"

+

Applicable to: Conditional

+
+
+<when above [amount] Happiness> +

Example: "<when above [3] Happiness>"

+

Applicable to: Conditional

+
+
+<when below [amount] Happiness> +

Example: "<when below [3] Happiness>"

+

Applicable to: Conditional

+
+
+<during the [era]> +

Example: "<during the [Ancient era]>"

+

Applicable to: Conditional

+
+
+<before the [era]> +

Example: "<before the [Ancient era]>"

+

Applicable to: Conditional

+
+
+<starting from the [era]> +

Example: "<starting from the [Ancient era]>"

+

Applicable to: Conditional

+
+
+<if starting in the [era]> +

Example: "<if starting in the [Ancient era]>"

+

Applicable to: Conditional

+
+
+<if no other Civilization has researched this> +

Applicable to: Conditional

+
+
+<after discovering [tech]> +

Example: "<after discovering [Agriculture]>"

+

Applicable to: Conditional

+
+
+<before discovering [tech]> +

Example: "<before discovering [Agriculture]>"

+

Applicable to: Conditional

+
+
+<while researching [tech]> +

This condition is fulfilled while the technology is actively being researched (it is the one research points are added to) +Example: "<while researching [Agriculture]>"

+

Applicable to: Conditional

+
+
+<if no other Civilization has adopted this> +

Applicable to: Conditional

+
+
+<after adopting [policy/belief]> +

Example: "<after adopting [Oligarchy]>"

+

Applicable to: Conditional

+
+
+<before adopting [policy/belief]> +

Example: "<before adopting [Oligarchy]>"

+

Applicable to: Conditional

+
+
+<before founding a Pantheon> +

Applicable to: Conditional

+
+
+<after founding a Pantheon> +

Applicable to: Conditional

+
+
+<before founding a religion> +

Applicable to: Conditional

+
+
+<after founding a religion> +

Applicable to: Conditional

+
+
+<before enhancing a religion> +

Applicable to: Conditional

+
+
+<after enhancing a religion> +

Applicable to: Conditional

+
+
+<after generating a Great Prophet> +

Applicable to: Conditional

+
+
+<if [buildingFilter] is constructed> +

Example: "<if [Culture] is constructed>"

+

Applicable to: Conditional

+
+
+<if [buildingFilter] is not constructed> +

Example: "<if [Culture] is not constructed>"

+

Applicable to: Conditional

+
+
+<if [buildingFilter] is constructed in all [cityFilter] cities> +

Example: "<if [Culture] is constructed in all [in all cities] cities>"

+

Applicable to: Conditional

+
+
+<if [buildingFilter] is constructed in at least [positiveAmount] of [cityFilter] cities> +

Example: "<if [Culture] is constructed in at least [3] of [in all cities] cities>"

+

Applicable to: Conditional

+
+
+<if [buildingFilter] is constructed by anybody> +

Example: "<if [Culture] is constructed by anybody>"

+

Applicable to: Conditional

+
+
+<with [resource]> +

Example: "<with [Iron]>"

+

Applicable to: Conditional

+
+
+<without [resource]> +

Example: "<without [Iron]>"

+

Applicable to: Conditional

+
+
+<when above [amount] [stat/resource]> +

Stats refers to the accumulated stat, not stat-per-turn +Example: "<when above [3] [Culture]>"

+

This unique's effect can be modified with <(modified by game speed)> +Applicable to: Conditional

+
+
+<when below [amount] [stat/resource]> +

Stats refers to the accumulated stat, not stat-per-turn +Example: "<when below [3] [Culture]>"

+

This unique's effect can be modified with <(modified by game speed)> +Applicable to: Conditional

+
+
+<when between [amount] and [amount] [stat/resource]> +

Stats refers to the accumulated stat, not stat-per-turn +Example: "<when between [3] and [3] [Culture]>"

+

This unique's effect can be modified with <(modified by game speed)> +Applicable to: Conditional

+
+
+<in this city> +

Applicable to: Conditional

+
+
+<in [cityFilter] cities> +

Example: "<in [in all cities] cities>"

+

Applicable to: Conditional

+
+
+<in cities connected to the capital> +

Applicable to: Conditional

+
+
+<in cities with a major religion> +

Applicable to: Conditional

+
+
+<in cities with an enhanced religion> +

Applicable to: Conditional

+
+
+<in cities following our religion> +

Applicable to: Conditional

+
+
+<in cities with a [buildingFilter]> +

Example: "<in cities with a [Culture]>"

+

Applicable to: Conditional

+
+
+<in cities without a [buildingFilter]> +

Example: "<in cities without a [Culture]>"

+

Applicable to: Conditional

+
+
+<in cities with at least [amount] [populationFilter]> +

Example: "<in cities with at least [3] [Followers of this Religion]>"

+

Applicable to: Conditional

+
+
+<in cities with [amount] [populationFilter]> +

Example: "<in cities with [3] [Followers of this Religion]>"

+

Applicable to: Conditional

+
+
+<in cities with between [amount] and [amount] [populationFilter]> +

Example: "<in cities with between [3] and [3] [Followers of this Religion]>"

+

Applicable to: Conditional

+
+
+<in cities with less than [amount] [populationFilter]> +

Example: "<in cities with less than [3] [Followers of this Religion]>"

+

Applicable to: Conditional

+
+
+<with a garrison> +

Applicable to: Conditional

+
+
+<for [mapUnitFilter] units> +

Example: "<for [Wounded] units>"

+

Applicable to: Conditional

+
+
+<when [mapUnitFilter]> +

Example: "<when [Wounded]>"

+

Applicable to: Conditional

+
+
+<for units with [promotion]> +

Also applies to units with temporary status +Example: "<for units with [Shock I]>"

+

Applicable to: Conditional

+
+
+<for units without [promotion]> +

Also applies to units with temporary status +Example: "<for units without [Shock I]>"

+

Applicable to: Conditional

+
+
+<vs cities> +

Applicable to: Conditional

+
+
+<vs [mapUnitFilter] units> +

Example: "<vs [Wounded] units>"

+

Applicable to: Conditional

+
+
+<vs [combatantFilter]> +

Example: "<vs [City]>"

+

Applicable to: Conditional

+
+
+<when fighting units from a Civilization with more Cities than you> +

Applicable to: Conditional

+
+
+<when attacking> +

Applicable to: Conditional

+
+
+<when defending> +

Applicable to: Conditional

+
+
+<when fighting in [tileFilter] tiles> +

Example: "<when fighting in [Farm] tiles>"

+

Applicable to: Conditional

+
+
+<on foreign continents> +

Applicable to: Conditional

+
+
+<when adjacent to a [mapUnitFilter] unit> +

Example: "<when adjacent to a [Wounded] unit>"

+

Applicable to: Conditional

+
+
+<when above [amount] HP> +

Example: "<when above [3] HP>"

+

Applicable to: Conditional

+
+
+<when below [amount] HP> +

Example: "<when below [3] HP>"

+

Applicable to: Conditional

+
+
+<if it hasn't used other actions yet> +

Applicable to: Conditional

+
+
+<with [amount] to [amount] neighboring [tileFilter] tiles> +

Example: "<with [3] to [3] neighboring [Farm] tiles>"

+

Applicable to: Conditional

+
+
+<in [tileFilter] tiles> +

Example: "<in [Farm] tiles>"

+

Applicable to: Conditional

+
+
+<in tiles without [tileFilter]> +

Example: "<in tiles without [Farm]>"

+

Applicable to: Conditional

+
+
+<within [amount] tiles of a [tileFilter]> +

Example: "<within [3] tiles of a [Farm]>"

+

Applicable to: Conditional

+
+
+<in tiles adjacent to [tileFilter] tiles> +

Example: "<in tiles adjacent to [Farm] tiles>"

+

Applicable to: Conditional

+
+
+<in tiles not adjacent to [tileFilter] tiles> +

Example: "<in tiles not adjacent to [Farm] tiles>"

+

Applicable to: Conditional

+
+
+<on water maps> +

Applicable to: Conditional

+
+
+<in [regionType] Regions> +

Example: "<in [Hybrid] Regions>"

+

Applicable to: Conditional

+
+
+<in all except [regionType] Regions> +

Example: "<in all except [Hybrid] Regions>"

+

Applicable to: Conditional

+
+
+<when number of [countable] is equal to [countable]> +

Example: "<when number of [1000] is equal to [1000]>"

+

Applicable to: Conditional

+
+
+<when number of [countable] is different than [countable]> +

Example: "<when number of [1000] is different than [1000]>"

+

Applicable to: Conditional

+
+
+<when number of [countable] is more than [countable]> +

Example: "<when number of [1000] is more than [1000]>"

+

Applicable to: Conditional

+
+
+<when number of [countable] is less than [countable]> +

Example: "<when number of [1000] is less than [1000]>"

+

Applicable to: Conditional

+
+
+<when number of [countable] is between [countable] and [countable]> +

Example: "<when number of [1000] is between [1000] and [1000]>"

+

Applicable to: Conditional

+
+
+<if [modFilter] is enabled> +

Example: "<if [DeCiv Redux] is enabled>"

+

Applicable to: Conditional

+
+
+<if [modFilter] is not enabled> +

Example: "<if [DeCiv Redux] is not enabled>"

+

Applicable to: Conditional

+
+

TriggerCondition uniques

+
+

Special conditionals that can be added to Triggerable uniques, to make them activate upon specific actions.

+
+
+<upon discovering [techFilter] technology> +

Example: "<upon discovering [Agriculture] technology>"

+

Applicable to: TriggerCondition

+
+
+<upon entering the [era]> +

Example: "<upon entering the [Ancient era]>"

+

Applicable to: TriggerCondition

+
+
+<upon entering a new era> +

Applicable to: TriggerCondition

+
+
+<upon adopting [policy/belief]> +

Example: "<upon adopting [Oligarchy]>"

+

Applicable to: TriggerCondition

+
+
+<upon declaring war with a major Civilization> +

Applicable to: TriggerCondition

+
+
+<upon declaring friendship> +

Applicable to: TriggerCondition

+
+
+<upon declaring a defensive pact> +

Applicable to: TriggerCondition

+
+
+<upon entering a Golden Age> +

Applicable to: TriggerCondition

+
+
+<upon conquering a city> +

Applicable to: TriggerCondition, UnitTriggerCondition

+
+
+<upon founding a city> +

Applicable to: TriggerCondition

+
+
+<upon building a [improvementFilter] improvement> +

Example: "<upon building a [All Road] improvement>"

+

Applicable to: TriggerCondition, UnitTriggerCondition

+
+
+<upon discovering a Natural Wonder> +

Applicable to: TriggerCondition

+
+
+<upon constructing [buildingFilter]> +

Example: "<upon constructing [Culture]>"

+

Applicable to: TriggerCondition

+
+
+<upon constructing [buildingFilter] [cityFilter]> +

Example: "<upon constructing [Culture] [in all cities]>"

+

Applicable to: TriggerCondition

+
+
+<upon gaining a [baseUnitFilter] unit> +

Example: "<upon gaining a [Melee] unit>"

+

Applicable to: TriggerCondition

+
+
+<upon turn end> +

Applicable to: TriggerCondition

+
+
+<upon founding a Pantheon> +

Applicable to: TriggerCondition

+
+
+<upon founding a Religion> +

Applicable to: TriggerCondition

+
+
+<upon enhancing a Religion> +

Applicable to: TriggerCondition

+
+

UnitTriggerCondition uniques

+
+

Special conditionals that can be added to UnitTriggerable uniques, to make them activate upon specific actions.

+
+
+<upon conquering a city> +

Applicable to: TriggerCondition, UnitTriggerCondition

+
+
+<upon building a [improvementFilter] improvement> +

Example: "<upon building a [All Road] improvement>"

+

Applicable to: TriggerCondition, UnitTriggerCondition

+
+
+<upon damaging a [mapUnitFilter] unit> +

Can apply triggers to to damaged unit by setting the first parameter to 'Target Unit' +Example: "<upon damaging a [Wounded] unit>"

+

Applicable to: UnitTriggerCondition

+
+
+<upon defeating a [mapUnitFilter] unit> +

Example: "<upon defeating a [Wounded] unit>"

+

Applicable to: UnitTriggerCondition

+
+
+<upon expending a [mapUnitFilter] unit> +

Example: "<upon expending a [Wounded] unit>"

+

Applicable to: UnitTriggerCondition

+
+
+<upon being defeated> +

Applicable to: UnitTriggerCondition

+
+
+<upon being promoted> +

Applicable to: UnitTriggerCondition

+
+
+<upon gaining the [promotion] promotion> +

Example: "<upon gaining the [Shock I] promotion>"

+

Applicable to: UnitTriggerCondition

+
+
+<upon losing the [promotion] promotion> +

Example: "<upon losing the [Shock I] promotion>"

+

Applicable to: UnitTriggerCondition

+
+
+<upon gaining the [promotion] status> +

Example: "<upon gaining the [Shock I] status>"

+

Applicable to: UnitTriggerCondition

+
+
+<upon losing the [promotion] status> +

Example: "<upon losing the [Shock I] status>"

+

Applicable to: UnitTriggerCondition

+
+
+<upon losing at least [amount] HP in a single attack> +

Example: "<upon losing at least [3] HP in a single attack>"

+

Applicable to: UnitTriggerCondition

+
+
+<upon ending a turn in a [tileFilter] tile> +

Example: "<upon ending a turn in a [Farm] tile>"

+

Applicable to: UnitTriggerCondition

+
+
+<upon discovering a [tileFilter] tile> +

Example: "<upon discovering a [Farm] tile>"

+

Applicable to: UnitTriggerCondition

+
+

UnitActionModifier uniques

+
+

Modifiers that can be added to UnitAction uniques as conditionals

+
+
+<by consuming this unit> +

Applicable to: UnitActionModifier

+
+
+<for [amount] movement> +

Will consume up to [amount] of Movement to execute +Example: "<for [3] movement>"

+

Applicable to: UnitActionModifier

+
+
+<for all movement> +

Will consume all Movement to execute +Applicable to: UnitActionModifier

+
+
+<requires [amount] movement> +

Requires [amount] of Movement to execute. Unit's Movement is rounded up +Example: "<requires [3] movement>"

+

Applicable to: UnitActionModifier

+
+
+<costs [stats] stats> +

A positive Integer value will be subtracted from your stock. Food and Production will be removed from Closest City's current stock +Example: "<costs [+1 Gold, +2 Production] stats>"

+

Applicable to: UnitActionModifier

+
+
+<costs [amount] [stockpiledResource]> +

A positive Integer value will be subtracted from your stock. Do not confuse with "Costs [amount] [stockpiledResource]" (uppercase 'C') for Improvements, Buildings, and Units. +Example: "<costs [3] [Mana]>"

+

Applicable to: UnitActionModifier

+
+
+<removing the [promotion] promotion/status> +

Removes the promotion/status from the unit - this is not a cost, units will be able to activate the action even without the promotion/status. To limit, use conditional +Example: "<removing the [Shock I] promotion/status>"

+

Applicable to: UnitActionModifier

+
+
+<once> +

Applicable to: UnitActionModifier

+
+
+<[amount] times> +

Example: "<[3] times>"

+

Applicable to: UnitActionModifier

+
+
+<[amount] additional time(s)> +

Example: "<[3] additional time(s)>"

+

Applicable to: UnitActionModifier

+
+
+<after which this unit is consumed> +

Applicable to: UnitActionModifier

+
+

MetaModifier uniques

+
+

Modifiers that can be added to other uniques changing user experience, not their behavior

+
+
+<for [amount] turns> +

Turns this unique into a trigger, activating this unique as a global unique for a number of turns +Example: "<for [3] turns>"

+

Applicable to: MetaModifier

+
+
+<hidden from users> +

Applicable to: MetaModifier

+
+
+<for every [countable]> +

Example: "<for every [1000]>"

+

Applicable to: MetaModifier

+
+
+<for every adjacent [tileFilter]> +

Example: "<for every adjacent [Farm]>"

+

Applicable to: MetaModifier

+
+
+<for every [amount] [countable]> +

Example: "<for every [3] [1000]>"

+

Applicable to: MetaModifier

+
+
+<(modified by game speed)> +

Can only be applied to certain uniques, see details of each unique for specifics +Applicable to: MetaModifier

+
+
+<Suppress warning [validationWarning]> +

Allows suppressing specific validation warnings. Errors, deprecation warnings, or warnings about untyped and non-filtering uniques should be heeded, not suppressed, and are therefore not accepted. Note that this can be used in ModOptions, in the uniques a warning is about, or as modifier on the unique triggering a warning - but you still need to be specific. Even in the modifier case you will need to specify a sufficiently selective portion of the warning text as parameter. +Example: "<Suppress warning [Tinman is supposed to automatically upgrade at tech Clockwork, and therefore Servos for its upgrade Mecha may not yet be researched! -or- is supposed to automatically upgrade]>"

+

Applicable to: Triggerable, Terrain, Speed, ModOptions, MetaModifier

+
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Other/Force-rating-calculation/index.html b/Other/Force-rating-calculation/index.html new file mode 100644 index 0000000000..33df962b01 --- /dev/null +++ b/Other/Force-rating-calculation/index.html @@ -0,0 +1,1557 @@ + + + + + + + + + + + + + + + + + + + + + + + Force rating calculation - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Force rating calculation

+

Since the question has come up several times, here is a summary of how Force ratings are calculated.

+

Base Unit Force Evaluation

+

First the base unit gets a force evaluation. +If the unit has a ranged attack, the starting force is the ranged strength ^ 1.45. Otherwise the starting force is strength ^ 1.5. +This is multiplied by the unit's movement ^ 0.3. Nukes get +4000.

+

Then this is multiplied by a bunch of modifiers:

+
    +
  • 0.5 if ranged naval
  • +
  • 0.5 if self-destructs when attacking
  • +
  • Half the city attack bonus (So +25% if the unit has +50% when attacking cities)
  • +
  • A Quarter of attack bonuses vs things other than cities
  • +
  • Half the bonus "when attacking"
  • +
  • Half the bonus "when defending"
  • +
  • +25% if paradrop able
  • +
  • -20% if needs to set up to attack
  • +
  • Half the bonus from certain terrain
  • +
  • +20% bonus per extra attack per turn
  • +
+

Individual Unit Force Evaluation

+

Each individual unit has a Force equal to the Base Unit Force,

+
    +
  • multiplied by (number of times promoted +1) ^ 0.3.
  • +
  • multiplied by current health as a percentage.
  • +
+

Civ Force Ranking

+

The civs Force Ranking is based on the sum of all their units' Force Evaluation (cities are not counted). +Only half the Force of naval units is counted. +This is multiplied by a gold modifier equal to the square root of current gold, as a percentage. +The gold multiplier is constrained to be between 1 and 2, so the max multiplier is 2 which is reached at 10000 gold.

+

Show Me Some Numbers

+
    +
  • Scout 13
  • +
  • Archer 19
  • +
  • Slinger 19
  • +
  • Dromon 23
  • +
  • Warrior 27
  • +
  • Maori Warrior 27
  • +
  • Brute 27
  • +
  • Bowman 29
  • +
  • Jaguar 36
  • +
  • Catapult 39
  • +
  • Composite Bowman 39
  • +
  • Galleass 41
  • +
  • Chariot Archer 42
  • +
  • War Elephant 44
  • +
  • War Chariot 45
  • +
  • Horse Archer 45
  • +
  • Trireme 46
  • +
  • Spearman 49
  • +
  • Ballista 55
  • +
  • Persian Immortal 56
  • +
  • Horseman 62
  • +
  • Hoplite 63
  • +
  • Swordsman 64
  • +
  • Chu-Ko-Nu 66
  • +
  • Quinquereme 69
  • +
  • African Forest Elephant 72
  • +
  • Battering Ram 80
  • +
  • Cataphract 80
  • +
  • Crossbowman 81
  • +
  • Longbowman 81
  • +
  • Companion Cavalry 84
  • +
  • Legion 86
  • +
  • Mohawk Warrior 86
  • +
  • Pikeman 87
  • +
  • Landsknecht 87
  • +
  • Trebuchet 88
  • +
  • Keshik 89
  • +
  • Frigate 100
  • +
  • Hwach'a 110
  • +
  • Longswordsman 118
  • +
  • Camel Archer 124
  • +
  • Samurai 126
  • +
  • Berserker 133
  • +
  • Knight 134
  • +
  • Conquistador 134
  • +
  • Mandekalu Cavalry 134
  • +
  • Caravel 134
  • +
  • Ship of the Line 139
  • +
  • Musketman 144
  • +
  • Cannon 151
  • +
  • Minuteman 154
  • +
  • Janissary 162
  • +
  • Gatling Gun 169
  • +
  • Musketeer 182
  • +
  • Tercio 182
  • +
  • Naresuan's Elephant 194
  • +
  • Lancer 204
  • +
  • Hakkapeliitta 204
  • +
  • Sipahi 218
  • +
  • Privateer 222
  • +
  • Rifleman 243
  • +
  • Carolean 243
  • +
  • Sea Beggar 244
  • +
  • Artillery 245
  • +
  • Battleship 269
  • +
  • Great War Bomber 290
  • +
  • Cavalry 300
  • +
  • Hussar 320
  • +
  • Triplane 325
  • +
  • Turtle Ship 327
  • +
  • Cossack 337
  • +
  • Norwegian Ski Infantry 345
  • +
  • Guided Missile 378
  • +
  • Carrier 408
  • +
  • Submarine 420
  • +
  • Bomber 425
  • +
  • Great War Infantry 434
  • +
  • Machine Gun 465
  • +
  • Fighter 470
  • +
  • Foreign Legion 477
  • +
  • Ironclad 486
  • +
  • Zero 508
  • +
  • Anti-Tank Gun 542
  • +
  • B17 551
  • +
  • Marine 645
  • +
  • Landship 703
  • +
  • Infantry 720
  • +
  • Nuclear Submarine 735
  • +
  • Stealth Bomber 771
  • +
  • Paratrooper 806
  • +
  • Anti-Aircraft Gun 819
  • +
  • Destroyer 870
  • +
  • Missile Cruiser 888
  • +
  • Rocket Artillery 930
  • +
  • Tank 948
  • +
  • Jet Fighter 988
  • +
  • Helicopter Gunship 992
  • +
  • Mechanized Infantry 1186
  • +
  • Panzer 1223
  • +
  • Mobile SAM 1376
  • +
  • Modern Armor 1620
  • +
  • Giant Death Robot 2977
  • +
  • Atomic Bomb 4714
  • +
  • Nuclear Missile 7906
  • +
+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Other/Installing-on-macOS/index.html b/Other/Installing-on-macOS/index.html new file mode 100644 index 0000000000..6a3b98f69e --- /dev/null +++ b/Other/Installing-on-macOS/index.html @@ -0,0 +1,1329 @@ + + + + + + + + + + + + + + + + + + + + + + + Installing on MacOS - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Installing on MacOS

+

There are currently several ways to install Unciv on MacOS.

+

It is recommended that you do not install from source, since the end result will be the same.

+

Installing using MacPorts

+

Details here - simply run sudo port install unciv from command line and you're good to go!

+

Does not require a JDK to be preinstalled.

+

Installing using JAR

+
    +
  1. If you don't already have Java 8 or OpenJDK (versions 11 and 18 do work) installed on your Mac, either
  2. +
  3. Download it from the official website. Once you have downloaded the file, open it and follow the instructions on screen.
  4. +
  5. If you use Homebrew, just run brew install java
  6. +
  7. Now that you have Java installed, it's time to download the latest Unciv JAR. This can be done from the releases screen here on Github. Download the file called Unciv.jar.
  8. +
  9. To run the game, you'll need to run java -jar Unciv.jar from a Terminal.
  10. +
  11. Alternatively, you could create an 'Unciv.sh' file containing that line, and then run the new file to allow you to create shortcuts, etc.
  12. +
+

(Sadly Unciv does not auto update when installing it using this method on MacOS so you will need to download the latest Unciv.jar from Github every time you want to update the game.)

+

Installing from source

+

For instructions on how to install Unciv from source see Building locally without Android Studio. It is not recommended to use this method as it achieves the same result as the first method whilst being much more complicated and prone to errors along the way.

+

(Sadly Unciv does not auto update when installing it using this method on MacOS so you will need to follow these steps every time you want to update the game.)

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Other/Intentional-departures-from-Civ-V/index.html b/Other/Intentional-departures-from-Civ-V/index.html new file mode 100644 index 0000000000..42c764bd3f --- /dev/null +++ b/Other/Intentional-departures-from-Civ-V/index.html @@ -0,0 +1,1429 @@ + + + + + + + + + + + + + + + + + + + + + + + Intentional departures from Civ V - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Intentional departures from Civ V

+ +

Water melee units can attack land units on shore

+

A result of a Discord poll. Water melee units in Civ V are considerably underpowered, so much so that even on maps with water they weren't considered competitive.

+

This change brings back an edge to water melee units that makes them worth building.

+

Workers do not complete an improvement on the same turn - they get orders to work on it

+

This allows players to assign workers to improve tiles, and then to reconsider and change improvement or move them elsewhere.

+

Building stuff takes time!

+

Forests and Jungle are visible 1 tile outside visibility range

+

In Civ V, this is the behavior of Hills and Mountains, but not of Jungle and Forest.

+

And yet, Jungle and Forest can block Hills, and Hills+Forest can block Mountains from view - so they must be the same height!

+

This part of Civ V visibility makes no sense, considering the otherwise well-structured visibility logic, and we consider this to be a bug.

+

Great Warpath does not provide railroad bonuses for connected cities

+

This is a discrepancy in Civ V rules - if all jungles are like roads, then they shouldn't be considered railroads for production purposes.

+

The idea is that if you can quickly transport goods with railroads that yields a production bonus - moving fast through jungles may be possible, but not with industrial amounts of goods.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Other/Multiplayer/index.html b/Other/Multiplayer/index.html new file mode 100644 index 0000000000..23ad404601 --- /dev/null +++ b/Other/Multiplayer/index.html @@ -0,0 +1,1522 @@ + + + + + + + + + + + + + + + + + + + + + + + Multiplayer - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Multiplayer

+

Multiplayer in Unciv is based on simple save file up/download, which is why it is based on a free Dropbox account by default. However, a lot of people use this default, so it is uncertain if you'll actually be able to access it consistently. See Hosting a Multiplayer server for hosting your own server.

+

How to play

+
    +
  1. Make sure you are all using the same multiplayer server (Main Menu -> Options -> Multiplayer)
  2. +
  3. Let all players send you their user ID (Main Menu -> Multiplayer -> Copy user ID).
  4. +
  5. (Optional) Add those user IDs to your friend list (Main Menu -> Multiplayer -> Friends list).
  6. +
  7. In Main Menu -> Start new game, check Online multiplayer on the left. On the right, add more human players and input the user IDs of the players you want to play with. Press Start game!.
  8. +
  9. The game ID will be automatically put in your clipboard. (If you lost it, you can get it again from Main Menu -> Multiplayer -> Copy game ID). Send this game ID to the other players.
  10. +
  11. The other players need to go to Main Menu -> Multiplayer -> Add multiplayer game and enter the game ID you just sent them. They can then join the game from this multiplayer screen.
  12. +
+

Hosting a Multiplayer server

+

Due to certain limitations on Dropbox's API, with the current influx of players, we've many times reached the point that Dropbox has become unavailable.

+

Therefore, you can now host your own Unciv server on any computer that can run Java programs.

+

This guide is written for people with a moderate amount of technical knowledge about computer software and who are able to search the web to learn stuff they might not know. If you're completely new to this, you'll likely not be able to follow without some larger time investment to learn.

+

If you're proficient in server hosting, there's another how-to for you at the end.

+

How To

+

Before starting, you must have a Java JDK installed. You'll also have to download the latest UncivServer.jar.

+

From the directory where the UncivServer.jar file is located, create a folder named "MultiplayerFiles", open a terminal (in Windows, Shift+RightClick in the folder) and run the following command in the directory: +java -jar UncivServer.jar

+

Your server has now started!

+

To check if everything works, you can start Unciv on the same computer, go to "Options > Multiplayer", then enter http://localhost:8080 as the "Server address" and click "Check connection to server". You should now get a "Success!" result, which means it's working!

+

To connect with other devices outside your local network or to make your server accessible from the web, you'll need a real IP. If your ISP provides you with a real IP already, forward your server's port (default 8080) with your router, and your server would be exposed to the internet! In this case you can also use http://<your-real-ip-adress>:<your-forwarded-port>. For example, if you have the IP 203.0.113.1 and forwarded the port of your server to port 1234, your server can be accessed from the internet from the url http://203.0.113.1:1234. Additionally, since the HTTP protocol defaults to port 80, if you have forwarded your server to port 80, you wouldn’t need to specify any port. For example, if you forward the server's port to port 80 of your real IP, your server would be exposed to http://<your-real-ip> or in this case http://203.0.113.1.

+

On the other device, enter the URL to your server (http://<your IP address>:<your chosen port>), click 'check connection' from the new device, and if you get the same "Success!" result - congratulations, you're connected to the same server and can start a multiplayer game!

+

Please note: +* Devices not connected to the same server will not be able to participate in multiplayer games together +* In many places, your external IP address changes periodically. If that is the case, you either have to update the IP all the time or use something like a dynamic DNS service. +* To start your server from some special ports like 80 or 443, you would need admin privileges. If you want to use those ports, run PowerShell as admin. However, if you use port forwarding from a router, you really don't need to do this. You can start the server from port 8080 and forward it to 80.

+

How To for people with hosting experience

+
    +
  • Have a Java JDK installed
  • +
  • Download the latest UncivServer.jar (can also use that link to automatically update probably)
  • +
  • See options with java -jar UncivServer.jar --help
      +
    • The server will run on a specified port (-p, default 8080), writing files in a folder (-f, default ./MultiplayerFiles/), so it needs appropriate permissions.
    • +
    +
  • +
  • Run it: java -jar UncivServer.jar -p 8080 -f /some/folder/
      +
    • It basically just does simple file storage over HTTP.
    • +
    • Files are not cleaned automatically if a game ends or is deleted on the client-side
    • +
    +
  • +
+

Third-party (unofficial) software for hosting your own Unciv server

+ +

Third-party (unofficial) publicly hosted Unciv servers

+

These servers are run by the community and not official servers. These servers may become (temporarily or permanently) unavailable and lose your game saves. They might also collect data like your IP, how often you play, or other data. Use these only if you accept these risks and trust the server owners.

+ + + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Other/Regions/index.html b/Other/Regions/index.html new file mode 100644 index 0000000000..6e01443552 --- /dev/null +++ b/Other/Regions/index.html @@ -0,0 +1,1434 @@ + + + + + + + + + + + + + + + + + + + + + + + Regions - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Regions

+

The Concept

+

During the generation of a random map (only; not pre-made maps) the map is split into a number of regions equal to the number of major civs. Each region gets classified according to its prevalent terrain, or if unable to be classified is called a "hybrid" region. +The region type corresponds to the start bias of the civs as they are distributed. +The region type also determines start placement and what luxuries will appear in the region.

+
+ Example + Region Example +
+ +

How to define region behavior in your mod

+

The game will work without any extra json definitions, but if you want the region system to work well when generating maps for your mod, these are the relevant uniques to define.

+

Terrains.json

+

"Always Fertility [amount] for Map Generation", "[amount] to Fertility for Map Generation" - these determine how good a terrain is for purposes of dividing land up fairly. The numbers are arbitrary but should reflect the relative value of the terrains.

+

"A Region is formed with at least [amount]% [simpleTerrain] tiles, with priority [amount]", +"A Region is formed with at least [amount]% [simpleTerrain] tiles and [simpleTerrain] tiles, with priority [amount]" - these determine the rules for when a region is classified as eg a "desert" region. Terrains are evaluated in ascending priority order, so in the base ruleset tundra regions are checked first. +"A Region can not contain more [simpleTerrain] tiles than [simpleTerrain] tiles" - a useful compliment to the sum-of-two-terrains criterium above, if both terrains are in and of themselves terrain types. So in the base ruleset a large enough sum of jungle and forest allows a region to be classified as jungle, but only if there is more jungle than forest. +"Base Terrain on this tile is not counted for Region determination" - for terrain features that are unremovable or otherwise dominate the tile. Used for Hills in the base ruleset. +A region not fulfilling any criteria is classified as "Hybrid"

+

"Considered [terrainQuality] when determining start locations" - where "terrainQuality" is one of "Food", "Production", "Desirable", "Undesirable". Usually used together with the "" or "" to determine what terrain is attractive when determining start locations. Note: if there are none of these for a terrain, the game will use the base stats of the terrain to guess a quality, but if there are any, the game will assume that they are complete.

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Other/Translating/index.html b/Other/Translating/index.html new file mode 100644 index 0000000000..d5244eb977 --- /dev/null +++ b/Other/Translating/index.html @@ -0,0 +1,1670 @@ + + + + + + + + + + + + + + + + + + + + + Translating - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Translating

+

Starting out

+

The translation files are at /android/assets/jsons/translations

+

If you're adding a new language, see Adding a new language.

+

If you're adding stuff to an existing language, simply start editing the file!

+

You don't need to download anything, all translation work can be done on the Github website :)

+

When you feel that you're ready to add your translation to the game, you'll need to create a merge request, which takes your changes and puts them into the main version of the game - it's pretty straightforward once you do it

+

Please note that Right-to-Left languages such as Arabic and Hebrew are not supported by the framework :/

+

App store text

+

There are two special entries that won't show in the game but are automatically used to provide short and long descriptions for F-Droid (and possibly other stores soon). They're near the beginning of each language file and marked "Fastlane". See the comments just above each for help, and where to find the actual english original to translate. Do not overlook the note on line breaks in Other notes for the full description!

+

Pitfalls

+
    +
  • If a translation template (the stuff to the left of "=") contains square brackets, you will have to include each of them verbatim in your translation, but you can move them. Upper/lower case is relevant! e.g. All [personFilter] are cool can be translated as Tous les [personFilter] sont cool, but not as Tous les [personnages] sont cool, and neither as Nous sommes vraiment cool. Failing this is the main cause of your PR's showing up with red "x"es and "checks failed".
  • +
  • Blanks: Watch out for blanks at the start of a line or two of them before the equals sign. If you got such a line - those blanks are part of the translation key and must not be deleted on the left side, and you should probably also include them in your translation (unless your language doesn't need spaces to separate things).
  • +
  • Changes in the templates: When we find a typo in the english texts and fix it, or marginally change a wording, the template changes. Often the old template will not be automatically fixed in the existing translations, because it's a lot of work and in most cases the developers cannot be sure the translation is still correct. For you, that might look like your translations are simply disappearing with an update. In such a case, you have the option to use github's history to look up old versions, copy the old translation, place it where the new template now says "requires translation" - and proofread and adapt it to the new english version. The history link for each file is in the top right area and has a nice round clock icon.
  • +
+

Wait, what just happened?

+

Like most open-source projects, Unciv is developed at Github, so if you don't have a user you'll first have to create one. The way Github works is the following:

+
    +
  1. You create a 'fork' repo, i.e. copy, of Unciv that belongs to your user (myUser/Unciv)
  2. +
  3. You make changes to your copy. These changes are called a 'commit'.
  4. +
  5. You make a pull request, which is basically asking for the changes you made on myUser/Unciv to be merged into the main repo (yairm210/Unciv)
  6. +
+

When you ask to 'edit' a file in yairm210/Unciv, these stages happen automatically - but it's important to understand what's happening behind the scenes do you understand where the changes actually are!

+

Other notes

+

Each untranslated phrase will have a ` # Requires translation!" line before it, so you can quickly find them. +You don't need to remove them yourself - they will be automatically removed the next time we rebuild the file.

+

Do as much as you're comfortable with - it's a big game with a lot of named objects, so don't feel pressured into doing everything =)

+

If you're making changes to your own repo, make sure that you make the branch you're changing is based on Unciv's master branch

+

Some entries have line breaks expressed as \n: Your translation can and in most cases should use them as well, but you do not need to distribute them exactly as in the original. Try to find a translation that reads nicely, then place the line break codes at roughly the same intervals as the original uses (less if your language's glyphs are wider than latin ones). Important: You cannot use normal line breaks, you must use the \n codes, normal line breaks are not part of a translation.

+

Chinese tutorial: 如果你是中国人,那么恭喜你运气不错!这里有Unciv中文开发者们专门为中文翻译工作者准备的(十分详尽)教程视频。:(Video On Bilibili)

+

Adding a new language

+

If any of the following steps are beyond your skillset, ask for help. All but the first two steps can be postponed.

+
    +
  • You'll need to create a new file ('Create a new file' to the right of the folder name in the UI), and copy into it the contents of template.properties
  • +
  • For automatic language processing for a release (e.g. adding new templates) there needs to exist a line in completionPercentages.properties. Location and number do not matter, what matters is that the language name left of the = corresponds exactly with your new language file name, case-sensitive, without extension.
  • +
  • For a nice display in language picker and options, we need a flag. It should be a circle surrounded by transparency within a 128x128px square. Add such a png to FlagIcons. For potential sources, look in the credits, and when done, add your source unless it is already covered.
  • +
  • A new graphic needs to be converted into the texture atlases (see Images and the texture atlas) - for a new flag, this usually means running the desktop version once from source, then uploading the updated atlas files via push or manually.
  • +
  • Lastly, your new language should be represented in the LocaleCode enum - reasons see below. You can add this even if you cannot compile - just make sure you follow the existing pattern, and read the inline documentation.
  • +
  • The first function of this entry is alphabetical sorting. Unfortunately, it is not easy to tell whether a specific combination is supported by Java. The simplest way to deal with this is trial and error - once your language is established and playable, see if Civilopedia entries seem properly sorted, if not, open an issue and tell us what other, more common language may have better sorting rules.
  • +
  • This entry is also required to enable fastlane description upload to a correct folder - however, whether F-Droid supports your language is not guaranteed (this page should help - but doesn't).
  • +
+

Diacritics support

+

When displaying text, the underlying libraries (libGdx and possibly lwjgl3/GWT) that Unciv uses assume one codepoint in the UTF-16 representation corresponds to one rendered glyph, +which causes incorrect display of languages making heavy use of diacritics or of characters outside the basic multilinguial plane like most emoji. +A language file can activate a "trick", where combinations of codepoints that should render as one single glyph are mapped into a "fake alphabet", +which is created on the fly in the Private Use Area defined by Unicode.

+

To activate this feature, set diacritics_support = true in your translation. There are a few additional "settings" - translation lines where the "translation" is some control instruction instead. +All of these are optional, though your language may show glitches unless you define some. For example, Bangla needs a definition for U+09CD, where the Unicode category does not fully define the required behaviour.

+

Each of the following definitions represents zero or more characters, and can simply list them as one string. +For readability, they can also be quoted (" surrounding the entire definition), characters can be separated by spaces, or you can use standard "U+xxxx" representations (these need space separators). +These entries, unlike the rest of a translation file, also support entry-specific comments: After the code(s), from a '#' to the end of the line. +Search for the information about the Unicode support in your language, e.g. on https://www.unicode.org/charts/ for information on which codes you might need to specify. +If your language does not need these, feel free to ignore, or use "" to avoid the "requires translation" mark.

+

Limitations

+
    +
  • Consider this feature as being in an experimental stage.
  • +
  • Can only work if the language's script still consists of individual glyphs rendered left to right.
  • +
  • The underlying libraries (Java AWT or Android) must be able to render the combinations you need - sometimes you will need to select and possibly install a specific font to see the intended results.
  • +
  • Using diacritics support incurs a performance penalty, but mostly when loading languages starting Unciv.
  • +
  • The feature also has a "quantity" limitation from the size of the Unicode "Private Use Area" (only the one in the BMP can be used), and this must be shared by the feature whereby Unciv automatically displays ruleset object icons: + The total number of distinct diacritic "combinations" (or glyphs) your translation actually uses plus the number of objects in the loaded mods (or vanilla ruleset) must not exceed 6400.
  • +
  • When enabled and the range is the default (or spans the unicode range for surrogates), then the engine will treat Unicode surrogate pairs correctly, assigning a fake alphabet codepoint for them and allowing diacritics to include them in a combo. + However, the parser is strict and throws an Exception on violations of the UTF-16 standard. If your translation crashes Unciv, check your editor for incorrect Unicode handling + (translation files are UTF-8 not UTF-16, but unfortunately most encoding converters allow transferring mismatched surrogate pairs). + Also, this possibility could so far not be successfully tested for emoji - no supporting font found, see "experimental".
  • +
+

Settings (as translation entries in the language file)

+
    +
  • diacritics_support: This entry must be set to "true" for the diacritics support to work at all. Any other value will cause text to be passed through unchanged.
  • +
  • unicode_block_start_character and unicode_block_end_character: These define the range of characters that should be considered. One character or code each. Defaults to the entire BMP range. + All characters in this range will be categorized, those undefined by Unicode, controls or punctuation, or those outside the range will pass through and reset the diacritics engine for the rest of the line, that is, pending potential combinations will be flushed. + Limiting this range - e.g. to the Unicode page dedicated to your language - is a performance optimization, but ultimately not required.
  • +
  • left_joining_diacritics: Optionally define additional codes meant to join with the character to the left of them, by default the unicode categories "Mn" and "Mc" within the range described above are used.
  • +
  • right_joining_diacritics: Optionally define additional codes meant to join with the character to the right of them, by default none.
  • +
  • left_and_right_joiners: Optionally define additional codes meant to join with the character to the left AND with the character to the right, by default none
  • +
+

These are processed in listed order and can override previous categorizations per character codepoint. +Thus a code specified in left_and_right_joiners can be in the "Mn" unicode category, which would put it into the left_joining_diacritics, but will still work, because the later definition overrides the earlier one.

+

Why not use a crowdsourcing translation website like <...>?

+
    +
  1. Testing. Currently, translations undergo a number of tests for verification. This allows some language changes to be accepted and others not, and it's all in the same platform with the same tests. External translation tools don't allow for this.
  2. +
  3. History and revisions. This is what Git was made for, and nothing like it exists in the world. I'm not exaggerating.
  4. +
  5. Release cycle. We release versions weekly. If we need to take information from an external website every time, and for many that I've checked - you need to download the info as a csv or something and convert it. Every extra step hurts.
  6. +
  7. Discussions. Most crowdsourcing translation websites don't allow for discussions and corrections on translations. Github does.
  8. +
  9. Mass changes. If we're changing the source of the translation but want to keep the various destinations (say, we change "Gold from trade routes +[amount]%" to "+[amount]% Gold from trade routes"), if all the translation files are in Git we can do that in 1 minute. If it's external, this varies greatly.
  10. +
+

Translation generation - for developers

+

The automatic template generation

+

Before releasing every version, we regenerate the translation files.

+

Sometimes, new strings (names, uniques, etc) are added in the json files. In order to not have to add every single one to the translation files manually, we have a class - TranslationFileWriter - that, for every language:

+
    +
  • Goes over the template.properties and copies translation lines
  • +
  • For every json file in the jsons folder
      +
    • Selects all string values - both in objects, and in arrays in objects, to any inheritance or nesting level. + (Collections that can be parsed must be derived from List or AbstractCollection)
    • +
    • Generates a 'key = value' line
    • +
    +
  • +
  • Scans knowledge from UniqueType and UniqueParameterType instances and generates 'key = value' lines for them
  • +
+

This means that every text that ISN'T in the jsons or the UniqueType system needs to be added manually to the template.properties in order to be translated! +That also means if you've been adding new json structures you (or someone) should check TranslationFileWriter and see if it is able to cope with them.

+

Rules for templates added manually

+

Building a new UI and doing something like popup.add("Hello world".toLabel()) is a typical case: This is not contained in json data, so you'll have to add the template to template.properties yourself. For this example, adding Hello world = somewhere in a line of its own could suffice.

+

Note the space at the end - it's absolutely required, and see to it your editor does not destroy your work. If you want to make sure, use Android Studio for git integration, but edit the file in an external editor, then run the unit tests locally before pushing. (to do: add link for instructions how to do that)

+

Leading spaces on a translation line or more than one space between the text and the = would mean these spaces are a key part of the string to be translated. That can work, but be warned: translators often overlook that those spaces are a required part of both template and translation, so if you can do without, then doing without is safer.

+

Translation templates can use placeholders, and there's two varieties: [] and {}. Square ones take precedence over curly ones, and nesting works only with a single level of curly nested inside one level of square. I both cases the symbols themselves ([]{}) are removed by the translation engine.

+

Square brackets [] mean the outer and inner components are both translated individually. The outer template will use alias names inside the brackets - example: Your code outputs "Everyone gains [5000] gold!", then the translation template should be "Everyone gains [amount] gold! = ". The translation engine would translate the "Everyone gains [] gold!" and "5000" individually and reassemble them - of course, the number is simply passed through. But in other cases that could be e.g. a Unit name that would be translated, and you could trust that translations for units are already handled just fine. Note that uniques often use the feature, but it is in no way limited to them. It it makes life easier for translators, use it.

+

Curly brackets {} are simpler - the contents within the brackets are translated individually, while the outer parts are passed through verbatim. Example: "+$amount${Fonts.gold} {Gold}".toLabel() - note the first ${} is a kotlin template while the second pair becomes part of the string. It tells the translation engine to ignore the numbers and the symbol but to translate the single word "Gold".

+

Rules for all sources

+

The [], {} and <> bracket types are used internally and cannot be part of a translatable text. Use () instead.

+

Translation generation - for modders

+

If you can run desktop with the mod installed, then provide at least one valid translation of something that is present in your mod or the base game in that file. The file can be empty otherwise. Now run Unciv and use options-advanced-"Generate translation files". Reload your translation file and it will have added all the necessary "requires translation" entries specific to your mod (I repeat, works only if there's at least one valid entry already there). AFAIK you can also override base game translations, but those won't be output by the "Generate translation files" tool.

+

Here's an example: +Say you have a new nation in your mod named "The Borg". You create the translations folder, create an empty file named, say, "Hungarian.properties", add "The Borg = The Borg" to that, run Unciv and run the translation generator from options. Reload the new file, bingo all what Unciv would like to see is there.

+

If you're modding on Android only - don't. That said, it's not impossible, just make do without the described tool and add everything yourself, test, rinse, repeat. Be aware that the game does not read changed files from disk if it doesn't need to, so on Droid you could either edit locally and force-stop to ensure changes are read, or edit on a github repo and re-install from there, or...

+

Adding new languages in a mod is not supported (because the completionPercentages.properties file determines which languages Unciv deems as known, and I'm not saying impossible as one could manipulate their GameSettings.json).

+

Remember, exact case is important both in translations left of the "=" and file names.

+

More about translating

+

Sometimes you'll see a English.properties in the translation folder. For example, if you see gold = credits in English.properties, It means the word 'gold' will be displayed as 'credits' in the English version. +So in your translation file, though 'gold' is already translated in vanilla unciv, you should sill translate the line. +

gold = credits ( <- in your language)
+NOT:gold = gold ( <- in your language)
+
+(The example comes from the mod Alpha-Frontier. Thanks @carriontrooper.)

+

Most Base Ruleset mods contain this feature, so you'd better be careful translating those, or you'll make the translation work really 'amuzing':D

+

Another thing about translation is 'extra translating'. The aim of 'extra translating' is to make your mod translation closer to the gaming content and give the players a better gaming experience. +A great example is from @SpacedOutChicken's mod Deciv. @The Bucketeer made some 'extra translations' which are excellent.(link is here)I've got a few lines here so you can take it as a reference. +

Your warmongering ways are unacceptable to us. = 即使在野蠻的荒土世界,窮兵黷武的行徑還是無法容忍的!
+(English meaning: These warmongering ways are still unaccepable enen in this world of savage)
+

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/Privacy-Policy/index.html b/Privacy-Policy/index.html new file mode 100644 index 0000000000..36f856a268 --- /dev/null +++ b/Privacy-Policy/index.html @@ -0,0 +1,1387 @@ + + + + + + + + + + + + + + + + + + + + + + + Privacy Policy - Unciv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Privacy Policy

+

We don't store personal information

+

At all, in any way.

+

We do store your Multiplayer games

+

If you want to use an alternative server backend, you can set your server URL in the Options menu

+ + + + + + + + + + + + + +
+
+ + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/assets/Android_SDK_Platforms.png b/assets/Android_SDK_Platforms.png new file mode 100644 index 0000000000000000000000000000000000000000..b35692eefc790c669ef2e1551171fdee0cd9cf2c GIT binary patch literal 38563 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 literal 0 HcmV?d00001 diff --git a/assets/Android_SDK_Tools.png b/assets/Android_SDK_Tools.png new file mode 100644 index 0000000000000000000000000000000000000000..0d3250f34fa7906e74176b219e9d5241cd833d71 GIT binary patch literal 51636 zcmbSy1#lcqljX>gES6<4S%Y7YP)I{f(uSvSIU0RV=?#02>iT(nN#pgfQj zyJ=sQ9aWHi9HAilQXP#`)JQl@%A)&6oRk&Ufq(}NisUn&B1p5yhS|JGHHHGbCD>M_Sm zBU9m9QH92+2utoA(`{E%#J@e&r8Q9a|GpY0$(%*#u)E@cS*^>_*dNn4xA7_okZ1~- z`LemD+>}%ON*JJQ|`S!pQPywhz?>AC` zXqu4F{Un^mxiObFIv=}leHq9n zmo>w1x#?YjJ+JJnFrQaDBy|iN-I5aNWM4Tg~=blxpgNwh!0(7cS}#Ng-O3GvFK_-5&CdD&wv&H@0-q(QYT87Mt@FK5+JSzOw)Gs@maM>>`0A zYJ*IbV%}yXf`?s9;nTuHVa|TeK2}HN>-A&tMe2@lB+hX4y>$M&&NmmO&?nLne_0lr zF)g`(>_)*@E7k`lxR0|0ZjT${#DAXfaN;pbhcB`2hGN%_ z+jpNUa(`Z5T^0N|;@k-NXG8Lu7V*E@BeL+it@vGbcI+O{(hMzV=GLMEhnps8*}sUy z;ndLBiTDbz`uovVUaZ~Mqo%N+@5`)G%GH90P#J>gXLusV^hFR*&pWWf z>X`M8>{QHE<-o;4DJjOfdTa7Oo5=@jQ>)VMM8kU>zr)7Na>OpKED?E6Kc5HOoEXWuLc!{iJ1`#9J|JkQCnNk zeEgV-r&hu&+{)HzHKxDsy^p9fzHr^05q~^P18kcsr+G6`D3(`Prnyb4DO*Q{?(g6g z#PCn;gi{2t*t+em*{qTwGE8;Q=x|XDN|L~ zaQ&4-Pmb_5YRk!+GQxzH!%+%d9}B-d)$I^22|H_{sShFp_`)#_L{zIDmBkQJd9H2D zhiEcGNsYDH?k3C1(N=)ufp9V0y=WcNC>?S<8cA5ld%hK9d9~Pd{KREvwvBI+dq0(Z z_^ta!GgZ_CawR6FFAV#ul*5}dJBaM>vaeH3m+qAbG&OKJ>=zr{~?ElD~a`W(LWB$`yu>VBIjg~0_fL(e-CSv-J#pRvw4V3)b z?|)}`|38QPuO|HQl;a}dGs^RA#L)t&FeV5fAQ5$Icn-H9W>U?|EI#nh4BQfbQwV0E zuhHE|fkNEI^@HFI%J96sYI^o0;CjXO&;4`TT#7m0x`3{Gxy1)xKs^Z{Q1_|x$4qf4Rr?rP*kqay3=Z!Y_!gLe|r{< z`#W$PaNuxh5pmIJNI;Qk2Y)9aJ^f+C-%EsZ3<&2uf0_Uvb`9Y_&KB?=o`8u2ir(ZV z)~J9r2D3;0y2FymL)X^4vW6VI*WTlyDgZMEL*wxD6nfo+v@#_^v)U@zfREW4I9`QuQRd`$~H;+ zRI1$yc;&Ccn%CDQcIUUJ8)F%5QwwZ%&dky4rt~5q-ix1qUF)sd&&tHwNKCa0C++5E zKgRQ!O6|_s5c!ELwl~eS#%Us421?Ua!O9gy=WBmPsO(=@t|Mc{55T+ZH}9mfGIk0u+5_?OsWluc6MkV;5F_<-Rq1K@Y6H4%p5{N^yeC5F zi(-|x7VEDvDpwMEEWD4{tbd)CE&c>eOHC{I3J0cpxYqe@eH1u`SZl~3@)wj1^)6c%0_OH1rP^Soa699f%<_E;UwpiKw?9NN<45kqIVa(e_$tU3zz54ziM8;$zHY(?0?f8@=9{Gk*oNkdz

skiB<|hL< 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 literal 0 HcmV?d00001 diff --git a/assets/Desktop_Build.png b/assets/Desktop_Build.png new file mode 100644 index 0000000000000000000000000000000000000000..26500e7c8f96ca0f7b22ae5467cc22d0be2a394c GIT binary patch literal 20675 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 Wwf};bMIT7E20_}KJ^}VZul@%W3XSOi literal 0 HcmV?d00001 diff --git a/assets/FasterUIDevelopment.png b/assets/FasterUIDevelopment.png new file mode 100644 index 0000000000000000000000000000000000000000..68f00d1d865830667cd0516c7267631ceeebe572 GIT binary patch literal 17369 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�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 literal 0 HcmV?d00001 diff --git a/assets/Icon.png b/assets/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..eb5af4b221eed34d192960d2215044cf00b90043 GIT binary patch literal 69531 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 H)b;-#Nw_0M literal 0 HcmV?d00001 diff --git a/assets/favicon.png b/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..7b95752b019540251cd7f5e50c45e7aa6026a8ef GIT binary patch literal 6626 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 QZho?nZ8wuHZ}8jqAN#DDBLDyZ literal 0 HcmV?d00001 diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/javascripts/bundle.88dd0f4e.min.js b/assets/javascripts/bundle.88dd0f4e.min.js new file mode 100644 index 0000000000..fb8f31090f --- /dev/null +++ b/assets/javascripts/bundle.88dd0f4e.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Di=Object.getOwnPropertyDescriptor;var Vi=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,Ni=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var zi=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Vi(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Di(t,n))||o.enumerable});return e};var Mt=(e,t,r)=>(r=e!=null?Wi(Ni(e)):{},zi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function te(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,te())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var qr=xr((hy,On)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof It=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof It=="object"?It.ClipboardJS=r():t.ClipboardJS=r()})(It,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(V){try{return document.execCommand(V)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(V){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=V,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var D=f()(F);return u("copy"),F.remove(),D},te=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=te;function k(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(V)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,D=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if($e)return J($e,{container:D});if(Y)return F==="cut"?y(Y):J(Y,{container:D})},qe=ft;function Fe(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(V)}function ki(V,A){if(!(V instanceof A))throw new TypeError("Cannot call a class as a function")}function no(V,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof D.action=="function"?D.action:this.defaultAction,this.target=typeof D.target=="function"?D.target:this.defaultTarget,this.text=typeof D.text=="function"?D.text:this.defaultText,this.container=Fe(D.container)==="object"?D.container:document.body}},{key:"listenClick",value:function(D){var Y=this;this.listener=c()(D,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(D){var Y=D.delegateTarget||D.currentTarget,$e=this.action(Y)||"copy",Dt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Dt?"success":"error",{action:$e,text:Dt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(D){return vr("action",D)}},{key:"defaultTarget",value:function(D){var Y=vr("target",D);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(D){return vr("text",D)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(D){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(D,Y)}},{key:"cut",value:function(D){return y(D)}},{key:"isSupported",value:function(){var D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof D=="string"?[D]:D,$e=!!document.queryCommandSupported;return Y.forEach(function(Dt){$e=$e&&!!document.queryCommandSupported(Dt)}),$e}}]),M}(s()),Ui=Fi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function N(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof he=="function"?he(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ut(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],N(i)),N(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Tr=Ue.EMPTY;function qt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t}(j);var To=function(e){oe(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t}(g);var _r=function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(g);var At={now:function(){return(At.delegate||Date).now()},delegate:void 0};var Ct=function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=At);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(gt);var Lo=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(yt);var kr=new Lo(Oo);var Mo=function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(gt);var _o=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Yt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Yt(Hr(e))?e.pop():void 0}function Bt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Gt(e){return H(e==null?void 0:e.then)}function Jt(e){return H(e[bt])}function Xt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Zt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var er=Zi();function tr(e){return H(e==null?void 0:e[er])}function rr(e){return fo(this,arguments,function(){var r,o,n,i;return Nt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function or(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Jt(e))return ea(e);if(xt(e))return ta(e);if(Gt(e))return ra(e);if(Xt(e))return Ao(e);if(tr(e))return oa(e);if(or(e))return na(e)}throw Zt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?De(t):Qo(function(){return new ir}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},te=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;te(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(te,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(te,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function $t(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?Ht(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Jo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function Tt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?Tt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function St(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Ve(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>Ve(e)),Q(Ve(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ne(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return Ne(e).pipe(m(({y:r})=>{let o=ce(e),n=St(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function ze(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function Pt(e){let t=matchMedia(e);return ar(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function Nr(e,t){return e.pipe(v(r=>r?t():S))}function zr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return zr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return zr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return z([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(ee("size")),n=z([o,r]).pipe(m(()=>Ve(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),Ot=JSON.parse(Ca.textContent);Ot.base=`${new URL(Ot.base,ye())}`;function xe(){return Ot}function B(e){return Ot.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?Ot.translations[e].replace("#",t.toString()):Ot.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Rt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Rt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Mt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=z([et(e),$t(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(Ne),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>z([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(Ht(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());z([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>$t(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return z([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),z([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>z([tn(e),Ne(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Da(e){let t=[];for(let r of Wa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Da(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Mt(Br());var Va=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function Na(e){return ge(e).pipe(m(({width:t})=>({scrollable:St(e).width>t})),ee("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Va++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),Na(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function za(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),za(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.entityTitleText{fill:var(--md-mermaid-label-fg-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?Tt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Dn=x("table");function Vn(e){return e.replaceWith(Dn),Dn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function Nn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));z([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=Ve(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([Ne(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=St(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function zn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Vn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>Nn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?Ne(o):I({x:0,y:0}),i=O(et(t),$t(t)).pipe(K());return z([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=Ve(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Rt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=ze("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>z([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(ee("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:o>=n}}),ee("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),ee("bottom"))));return z([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(ne(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=Pt("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Mt(Br());function ts(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function ns(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return We(P("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(ee("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),ee("pathname"),v(()=>e),ee("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(ee("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Mt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function jt(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),ze("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),z([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let s=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(s)||(s=[s]);e:for(let p of s)for(let c of n.aliases.concat(n.version))if(new RegExp(p,"i").test(c)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ae("outdated"))s.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),ze("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(jt)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return z([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));z([t.pipe(Ae(jt)),r],(i,a)=>a).pipe(ee("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(ee("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);ze("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(re(r),Wr(t.pipe(Ae(jt)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Vr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return z([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=Ve(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),De({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),De({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),De({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),De({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),ee("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(ee("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(ee("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),ee("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return z([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),ee("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Dr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Oi({viewport$:e,tablet$:t}){z([ze("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ys(){return location.protocol==="file:"?Tt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ut=sn(),Lt=ln(Ut),to=an(),Oe=gn(),hr=Pt("(min-width: 960px)"),Mi=Pt("(min-width: 1220px)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ut,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ut,Lt).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),Ft=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Lt})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>zn(e,{viewport$:Oe,target$:Lt,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ut}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:Ft})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Nr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft})):Nr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:Ft}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Lt})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ut;window.target$=Lt;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); +//# sourceMappingURL=bundle.88dd0f4e.min.js.map + diff --git a/assets/javascripts/bundle.88dd0f4e.min.js.map b/assets/javascripts/bundle.88dd0f4e.min.js.map new file mode 100644 index 0000000000..dab2a8754b --- /dev/null +++ b/assets/javascripts/bundle.88dd0f4e.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n *\n * @class BehaviorSubject\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:

\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an