diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1104b06f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +run_php_server.bat +.vscode \ No newline at end of file diff --git a/Fantasy Map Generator.lnk b/Fantasy Map Generator.lnk deleted file mode 100644 index 1e1f2012..00000000 Binary files a/Fantasy Map Generator.lnk and /dev/null differ diff --git a/LICENSE b/LICENSE index 19e4e777..24402ab6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright 2018-2019 Max Ganiev (Azgaar), azgaar.fmg@yandex.by +Copyright 2018-2020 Max Ganiev (Azgaar), azgaar.fmg@yandex.by Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -12,7 +12,7 @@ furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -You can produce, without restrictions, any derivative works from the original +You can produce, without restrictions, any derivative works from the original software and even reap commercial benefits from the sale of the secondary product. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR diff --git a/README.md b/README.md index d5fe4479..3161d67c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Fantasy Map Generator -Azgaar's _Fantasy Map Generator_. Online tool generating interactive and highly customizable svg maps based on voronoi diagram. +Azgaar's _Fantasy Map Generator_ is a free client-side web application generating interactive and highly customizable svg maps based on voronoi diagram. -Project is under development, check out the current version [here](https://azgaar.github.io/Fantasy-Map-Generator). You can also try an Electron desktop application - download [an archive](https://github.com/Azgaar/Fantasy-Map-Generator/releases) for your architecture, unzip and run the _Azgaar's Fantasy Map Generator.exe_. +Project is under development, the current version is available on [Github Pages](https://azgaar.github.io/Fantasy-Map-Generator). -Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for a guidance. Some details are covered in my blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com), you may also keep an eye on my [Trello devboard](https://trello.com/b/7x832DG4/fantasy-map-generator). +Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for a guidance. Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com), you may also keep an eye on my [Trello devboard](https://trello.com/b/7x832DG4/fantasy-map-generator). [](https://i.redd.it/8bf81ir2cy631.png) @@ -14,7 +14,11 @@ Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki Join our [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) and [Discord server](https://discordapp.com/invite/X7E84HU) to share the created maps, discuss the Generator, suggest ideas and get a most recent updates. You may also contact me directly via [email](mailto:azgaar.fmg@yandex.by). For bug reports please use the project [issues page](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or Discord "Bugs" channel. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips). -You can support the project [on Patreon](https://www.patreon.com/azgaar). +Electron desktop application is available in [releases](https://github.com/Azgaar/Fantasy-Map-Generator/releases). Download archive for your architecture, unzip and run. + +Pull requests are welcomed. The Tool codebase is messy and requires re-design, but I will appreciate if you start with minor changes. + +You can support the project on [Patreon](https://www.patreon.com/azgaar). _Inspiration:_ diff --git a/_config.yml b/_config.yml deleted file mode 100644 index c7418817..00000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-slate \ No newline at end of file diff --git a/charges/anchor.svg b/charges/anchor.svg new file mode 100644 index 00000000..c05b085f --- /dev/null +++ b/charges/anchor.svg @@ -0,0 +1,9 @@ + diff --git a/charges/annulet.svg b/charges/annulet.svg new file mode 100644 index 00000000..976c2474 --- /dev/null +++ b/charges/annulet.svg @@ -0,0 +1,6 @@ + diff --git a/charges/armillarySphere.svg b/charges/armillarySphere.svg new file mode 100644 index 00000000..60a1cc65 --- /dev/null +++ b/charges/armillarySphere.svg @@ -0,0 +1,18 @@ + diff --git a/charges/arrow.svg b/charges/arrow.svg new file mode 100644 index 00000000..c9e8f852 --- /dev/null +++ b/charges/arrow.svg @@ -0,0 +1,9 @@ + diff --git a/charges/arrowsSheaf.svg b/charges/arrowsSheaf.svg new file mode 100644 index 00000000..41880647 --- /dev/null +++ b/charges/arrowsSheaf.svg @@ -0,0 +1,24 @@ + diff --git a/charges/bearPassant.svg b/charges/bearPassant.svg new file mode 100644 index 00000000..9fe44e31 --- /dev/null +++ b/charges/bearPassant.svg @@ -0,0 +1,141 @@ + diff --git a/charges/bearRampant.svg b/charges/bearRampant.svg new file mode 100644 index 00000000..f320d194 --- /dev/null +++ b/charges/bearRampant.svg @@ -0,0 +1,129 @@ + diff --git a/charges/bell.svg b/charges/bell.svg new file mode 100644 index 00000000..73124adb --- /dev/null +++ b/charges/bell.svg @@ -0,0 +1,18 @@ + diff --git a/charges/billet.svg b/charges/billet.svg new file mode 100644 index 00000000..6398df51 --- /dev/null +++ b/charges/billet.svg @@ -0,0 +1,6 @@ + diff --git a/charges/boarRampant.svg b/charges/boarRampant.svg new file mode 100644 index 00000000..fced611a --- /dev/null +++ b/charges/boarRampant.svg @@ -0,0 +1,23 @@ + diff --git a/charges/boat.svg b/charges/boat.svg new file mode 100644 index 00000000..02070e6e --- /dev/null +++ b/charges/boat.svg @@ -0,0 +1,19 @@ + diff --git a/charges/bow.svg b/charges/bow.svg new file mode 100644 index 00000000..c0033517 --- /dev/null +++ b/charges/bow.svg @@ -0,0 +1,8 @@ + diff --git a/charges/bowWithArrow.svg b/charges/bowWithArrow.svg new file mode 100644 index 00000000..ad95d94d --- /dev/null +++ b/charges/bowWithArrow.svg @@ -0,0 +1,14 @@ + diff --git a/charges/buckle.svg b/charges/buckle.svg new file mode 100644 index 00000000..e558bfaa --- /dev/null +++ b/charges/buckle.svg @@ -0,0 +1,8 @@ + diff --git a/charges/bugleHorn.svg b/charges/bugleHorn.svg new file mode 100644 index 00000000..b006a69f --- /dev/null +++ b/charges/bugleHorn.svg @@ -0,0 +1,17 @@ + diff --git a/charges/bullHeadCaboshed.svg b/charges/bullHeadCaboshed.svg new file mode 100644 index 00000000..65112450 --- /dev/null +++ b/charges/bullHeadCaboshed.svg @@ -0,0 +1,17 @@ + diff --git a/charges/bullPassant.svg b/charges/bullPassant.svg new file mode 100644 index 00000000..48b9c9ca --- /dev/null +++ b/charges/bullPassant.svg @@ -0,0 +1,19 @@ + diff --git a/charges/cancer.svg b/charges/cancer.svg new file mode 100644 index 00000000..937ea3eb --- /dev/null +++ b/charges/cancer.svg @@ -0,0 +1,20 @@ + diff --git a/charges/castle.svg b/charges/castle.svg new file mode 100644 index 00000000..0a2168e6 --- /dev/null +++ b/charges/castle.svg @@ -0,0 +1,24 @@ + diff --git a/charges/cavalier.svg b/charges/cavalier.svg new file mode 100644 index 00000000..9e7207a7 --- /dev/null +++ b/charges/cavalier.svg @@ -0,0 +1,8 @@ + diff --git a/charges/chalice.svg b/charges/chalice.svg new file mode 100644 index 00000000..daf10466 --- /dev/null +++ b/charges/chalice.svg @@ -0,0 +1,10 @@ + diff --git a/charges/cinquefoil.svg b/charges/cinquefoil.svg new file mode 100644 index 00000000..1dfea574 --- /dev/null +++ b/charges/cinquefoil.svg @@ -0,0 +1,14 @@ + diff --git a/charges/cock.svg b/charges/cock.svg new file mode 100644 index 00000000..d8f92c52 --- /dev/null +++ b/charges/cock.svg @@ -0,0 +1,32 @@ + diff --git a/charges/compassRose.svg b/charges/compassRose.svg new file mode 100644 index 00000000..16e3ba6b --- /dev/null +++ b/charges/compassRose.svg @@ -0,0 +1,7 @@ + diff --git a/charges/cowHorns.svg b/charges/cowHorns.svg new file mode 100644 index 00000000..6564982a --- /dev/null +++ b/charges/cowHorns.svg @@ -0,0 +1,10 @@ + diff --git a/charges/crescent.svg b/charges/crescent.svg new file mode 100644 index 00000000..f6f1f921 --- /dev/null +++ b/charges/crescent.svg @@ -0,0 +1,7 @@ + diff --git a/charges/crosier.svg b/charges/crosier.svg new file mode 100644 index 00000000..1149b3ef --- /dev/null +++ b/charges/crosier.svg @@ -0,0 +1,16 @@ + diff --git a/charges/crossArrowed.svg b/charges/crossArrowed.svg new file mode 100644 index 00000000..a3844430 --- /dev/null +++ b/charges/crossArrowed.svg @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/charges/crossAvellane.svg b/charges/crossAvellane.svg new file mode 100644 index 00000000..387a7072 --- /dev/null +++ b/charges/crossAvellane.svg @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/charges/crossBottony.svg b/charges/crossBottony.svg new file mode 100644 index 00000000..345d7d82 --- /dev/null +++ b/charges/crossBottony.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossCeltic.svg b/charges/crossCeltic.svg new file mode 100644 index 00000000..e47c2834 --- /dev/null +++ b/charges/crossCeltic.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossCercelee.svg b/charges/crossCercelee.svg new file mode 100644 index 00000000..bc5771da --- /dev/null +++ b/charges/crossCercelee.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossClechy.svg b/charges/crossClechy.svg new file mode 100644 index 00000000..51a143e4 --- /dev/null +++ b/charges/crossClechy.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossErminee.svg b/charges/crossErminee.svg new file mode 100644 index 00000000..c9190575 --- /dev/null +++ b/charges/crossErminee.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossFitchy.svg b/charges/crossFitchy.svg new file mode 100644 index 00000000..87e9265c --- /dev/null +++ b/charges/crossFitchy.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossFleury.svg b/charges/crossFleury.svg new file mode 100644 index 00000000..86d6b9ed --- /dev/null +++ b/charges/crossFleury.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossFourchy.svg b/charges/crossFourchy.svg new file mode 100644 index 00000000..6a19f8e5 --- /dev/null +++ b/charges/crossFourchy.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossGamma.svg b/charges/crossGamma.svg new file mode 100644 index 00000000..7610c995 --- /dev/null +++ b/charges/crossGamma.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossHummetty.svg b/charges/crossHummetty.svg new file mode 100644 index 00000000..5a7388fc --- /dev/null +++ b/charges/crossHummetty.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossMaltese.svg b/charges/crossMaltese.svg new file mode 100644 index 00000000..f704ccb4 --- /dev/null +++ b/charges/crossMaltese.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossMoline.svg b/charges/crossMoline.svg new file mode 100644 index 00000000..4df553ef --- /dev/null +++ b/charges/crossMoline.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossOccitan.svg b/charges/crossOccitan.svg new file mode 100644 index 00000000..60f102d7 --- /dev/null +++ b/charges/crossOccitan.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossPatonce.svg b/charges/crossPatonce.svg new file mode 100644 index 00000000..3fda2f9c --- /dev/null +++ b/charges/crossPatonce.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossPattee.svg b/charges/crossPattee.svg new file mode 100644 index 00000000..d4c3da87 --- /dev/null +++ b/charges/crossPattee.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossPommy.svg b/charges/crossPommy.svg new file mode 100644 index 00000000..dbf85e7b --- /dev/null +++ b/charges/crossPommy.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossPotent.svg b/charges/crossPotent.svg new file mode 100644 index 00000000..c8aa500f --- /dev/null +++ b/charges/crossPotent.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossSaltire.svg b/charges/crossSaltire.svg new file mode 100644 index 00000000..dd5d8f4b --- /dev/null +++ b/charges/crossSaltire.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossTau.svg b/charges/crossTau.svg new file mode 100644 index 00000000..4927e0a7 --- /dev/null +++ b/charges/crossTau.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crossVoided.svg b/charges/crossVoided.svg new file mode 100644 index 00000000..71287ee4 --- /dev/null +++ b/charges/crossVoided.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crosslet.svg b/charges/crosslet.svg new file mode 100644 index 00000000..e359ce28 --- /dev/null +++ b/charges/crosslet.svg @@ -0,0 +1,6 @@ + diff --git a/charges/crown.svg b/charges/crown.svg new file mode 100644 index 00000000..1b06f811 --- /dev/null +++ b/charges/crown.svg @@ -0,0 +1,14 @@ + diff --git a/charges/deerHeadCaboshed.svg b/charges/deerHeadCaboshed.svg new file mode 100644 index 00000000..a4f702f5 --- /dev/null +++ b/charges/deerHeadCaboshed.svg @@ -0,0 +1,25 @@ + diff --git a/charges/delf.svg b/charges/delf.svg new file mode 100644 index 00000000..fc4c57c9 --- /dev/null +++ b/charges/delf.svg @@ -0,0 +1,6 @@ + diff --git a/charges/dolphin.svg b/charges/dolphin.svg new file mode 100644 index 00000000..afb48d23 --- /dev/null +++ b/charges/dolphin.svg @@ -0,0 +1,60 @@ + diff --git a/charges/dragonPassant.svg b/charges/dragonPassant.svg new file mode 100644 index 00000000..36986715 --- /dev/null +++ b/charges/dragonPassant.svg @@ -0,0 +1,36 @@ + diff --git a/charges/dragonRampant.svg b/charges/dragonRampant.svg new file mode 100644 index 00000000..d2b2ee68 --- /dev/null +++ b/charges/dragonRampant.svg @@ -0,0 +1,54 @@ + diff --git a/charges/eagle.svg b/charges/eagle.svg new file mode 100644 index 00000000..963a289c --- /dev/null +++ b/charges/eagle.svg @@ -0,0 +1,87 @@ + diff --git a/charges/eagleTwoHeards.svg b/charges/eagleTwoHeards.svg new file mode 100644 index 00000000..079f539c --- /dev/null +++ b/charges/eagleTwoHeards.svg @@ -0,0 +1,102 @@ + diff --git a/charges/elephant.svg b/charges/elephant.svg new file mode 100644 index 00000000..d5af22ed --- /dev/null +++ b/charges/elephant.svg @@ -0,0 +1,17 @@ + diff --git a/charges/escallop.svg b/charges/escallop.svg new file mode 100644 index 00000000..14652210 --- /dev/null +++ b/charges/escallop.svg @@ -0,0 +1,18 @@ + diff --git a/charges/estoile.svg b/charges/estoile.svg new file mode 100644 index 00000000..af35f897 --- /dev/null +++ b/charges/estoile.svg @@ -0,0 +1,20 @@ + diff --git a/charges/fleurDeLis.svg b/charges/fleurDeLis.svg new file mode 100644 index 00000000..2902d3ac --- /dev/null +++ b/charges/fleurDeLis.svg @@ -0,0 +1,10 @@ + diff --git a/charges/fountain.svg b/charges/fountain.svg new file mode 100644 index 00000000..2ef97e3b --- /dev/null +++ b/charges/fountain.svg @@ -0,0 +1,11 @@ + diff --git a/charges/fusil.svg b/charges/fusil.svg new file mode 100644 index 00000000..8da86e7c --- /dev/null +++ b/charges/fusil.svg @@ -0,0 +1,6 @@ + diff --git a/charges/garb.svg b/charges/garb.svg new file mode 100644 index 00000000..07fb901e --- /dev/null +++ b/charges/garb.svg @@ -0,0 +1,149 @@ + diff --git a/charges/goat.svg b/charges/goat.svg new file mode 100644 index 00000000..c323cb0f --- /dev/null +++ b/charges/goat.svg @@ -0,0 +1,20 @@ + diff --git a/charges/goutte.svg b/charges/goutte.svg new file mode 100644 index 00000000..a836f7b4 --- /dev/null +++ b/charges/goutte.svg @@ -0,0 +1,6 @@ + diff --git a/charges/greyhoundCourant.svg b/charges/greyhoundCourant.svg new file mode 100644 index 00000000..3dc41b55 --- /dev/null +++ b/charges/greyhoundCourant.svg @@ -0,0 +1,27 @@ + diff --git a/charges/griffinPassant.svg b/charges/griffinPassant.svg new file mode 100644 index 00000000..025c2654 --- /dev/null +++ b/charges/griffinPassant.svg @@ -0,0 +1,59 @@ + diff --git a/charges/griffinRampant.svg b/charges/griffinRampant.svg new file mode 100644 index 00000000..40d8c9f0 --- /dev/null +++ b/charges/griffinRampant.svg @@ -0,0 +1,115 @@ + diff --git a/charges/hand.svg b/charges/hand.svg new file mode 100644 index 00000000..f23ca361 --- /dev/null +++ b/charges/hand.svg @@ -0,0 +1,14 @@ + diff --git a/charges/harp.svg b/charges/harp.svg new file mode 100644 index 00000000..7832707b --- /dev/null +++ b/charges/harp.svg @@ -0,0 +1,28 @@ + diff --git a/charges/hatchet.svg b/charges/hatchet.svg new file mode 100644 index 00000000..a543a5f7 --- /dev/null +++ b/charges/hatchet.svg @@ -0,0 +1,8 @@ + diff --git a/charges/head.svg b/charges/head.svg new file mode 100644 index 00000000..5c49b6f8 --- /dev/null +++ b/charges/head.svg @@ -0,0 +1,16 @@ + diff --git a/charges/headWreathed.svg b/charges/headWreathed.svg new file mode 100644 index 00000000..d3dab379 --- /dev/null +++ b/charges/headWreathed.svg @@ -0,0 +1,28 @@ + diff --git a/charges/heart.svg b/charges/heart.svg new file mode 100644 index 00000000..01ce307b --- /dev/null +++ b/charges/heart.svg @@ -0,0 +1,6 @@ + diff --git a/charges/heron.svg b/charges/heron.svg new file mode 100644 index 00000000..8a4ecca3 --- /dev/null +++ b/charges/heron.svg @@ -0,0 +1,25 @@ + diff --git a/charges/horseRampant.svg b/charges/horseRampant.svg new file mode 100644 index 00000000..55b46119 --- /dev/null +++ b/charges/horseRampant.svg @@ -0,0 +1,25 @@ + diff --git a/charges/horseSalient.svg b/charges/horseSalient.svg new file mode 100644 index 00000000..2b487240 --- /dev/null +++ b/charges/horseSalient.svg @@ -0,0 +1,23 @@ + diff --git a/charges/horseshoe.svg b/charges/horseshoe.svg new file mode 100644 index 00000000..542acae1 --- /dev/null +++ b/charges/horseshoe.svg @@ -0,0 +1,9 @@ + diff --git a/charges/key.svg b/charges/key.svg new file mode 100644 index 00000000..90f135e0 --- /dev/null +++ b/charges/key.svg @@ -0,0 +1,16 @@ + diff --git a/charges/lamb.svg b/charges/lamb.svg new file mode 100644 index 00000000..8854a232 --- /dev/null +++ b/charges/lamb.svg @@ -0,0 +1,18 @@ + diff --git a/charges/lionPassant.svg b/charges/lionPassant.svg new file mode 100644 index 00000000..6ff03aea --- /dev/null +++ b/charges/lionPassant.svg @@ -0,0 +1,183 @@ + diff --git a/charges/lionRampant.svg b/charges/lionRampant.svg new file mode 100644 index 00000000..552d2647 --- /dev/null +++ b/charges/lionRampant.svg @@ -0,0 +1,21 @@ + diff --git a/charges/lochaberAxe.svg b/charges/lochaberAxe.svg new file mode 100644 index 00000000..4f724811 --- /dev/null +++ b/charges/lochaberAxe.svg @@ -0,0 +1,9 @@ + diff --git a/charges/log.svg b/charges/log.svg new file mode 100644 index 00000000..9f8a01bc --- /dev/null +++ b/charges/log.svg @@ -0,0 +1,8 @@ + diff --git a/charges/lozenge.svg b/charges/lozenge.svg new file mode 100644 index 00000000..cfa6f506 --- /dev/null +++ b/charges/lozenge.svg @@ -0,0 +1,6 @@ + diff --git a/charges/lozengeFaceted.svg b/charges/lozengeFaceted.svg new file mode 100644 index 00000000..81bec3dc --- /dev/null +++ b/charges/lozengeFaceted.svg @@ -0,0 +1,11 @@ + diff --git a/charges/lozengePloye.svg b/charges/lozengePloye.svg new file mode 100644 index 00000000..9ff50ea2 --- /dev/null +++ b/charges/lozengePloye.svg @@ -0,0 +1,6 @@ + diff --git a/charges/lute.svg b/charges/lute.svg new file mode 100644 index 00000000..3e6c70d5 --- /dev/null +++ b/charges/lute.svg @@ -0,0 +1,15 @@ + diff --git a/charges/lymphad.svg b/charges/lymphad.svg new file mode 100644 index 00000000..86186299 --- /dev/null +++ b/charges/lymphad.svg @@ -0,0 +1,75 @@ + diff --git a/charges/mallet.svg b/charges/mallet.svg new file mode 100644 index 00000000..b6d9fb24 --- /dev/null +++ b/charges/mallet.svg @@ -0,0 +1,8 @@ + diff --git a/charges/mascle.svg b/charges/mascle.svg new file mode 100644 index 00000000..c274a6ad --- /dev/null +++ b/charges/mascle.svg @@ -0,0 +1,6 @@ + diff --git a/charges/mullet.svg b/charges/mullet.svg new file mode 100644 index 00000000..8aa647fd --- /dev/null +++ b/charges/mullet.svg @@ -0,0 +1,6 @@ + diff --git a/charges/mullet10.svg b/charges/mullet10.svg new file mode 100644 index 00000000..b34684f5 --- /dev/null +++ b/charges/mullet10.svg @@ -0,0 +1,6 @@ + diff --git a/charges/mullet4.svg b/charges/mullet4.svg new file mode 100644 index 00000000..ea581ebf --- /dev/null +++ b/charges/mullet4.svg @@ -0,0 +1,6 @@ + diff --git a/charges/mullet6.svg b/charges/mullet6.svg new file mode 100644 index 00000000..8821b639 --- /dev/null +++ b/charges/mullet6.svg @@ -0,0 +1,6 @@ + diff --git a/charges/mullet6Faceted.svg b/charges/mullet6Faceted.svg new file mode 100644 index 00000000..bf5f4a08 --- /dev/null +++ b/charges/mullet6Faceted.svg @@ -0,0 +1,7 @@ + diff --git a/charges/mullet6Pierced.svg b/charges/mullet6Pierced.svg new file mode 100644 index 00000000..03be315a --- /dev/null +++ b/charges/mullet6Pierced.svg @@ -0,0 +1,6 @@ + diff --git a/charges/mullet7.svg b/charges/mullet7.svg new file mode 100644 index 00000000..000038ab --- /dev/null +++ b/charges/mullet7.svg @@ -0,0 +1,6 @@ + diff --git a/charges/mullet8.svg b/charges/mullet8.svg new file mode 100644 index 00000000..7e1a40af --- /dev/null +++ b/charges/mullet8.svg @@ -0,0 +1,6 @@ + diff --git a/charges/mulletFaceted.svg b/charges/mulletFaceted.svg new file mode 100644 index 00000000..83f7a993 --- /dev/null +++ b/charges/mulletFaceted.svg @@ -0,0 +1,7 @@ + diff --git a/charges/mulletPierced.svg b/charges/mulletPierced.svg new file mode 100644 index 00000000..f1f67375 --- /dev/null +++ b/charges/mulletPierced.svg @@ -0,0 +1,6 @@ + diff --git a/charges/orb.svg b/charges/orb.svg new file mode 100644 index 00000000..4bf6e9c3 --- /dev/null +++ b/charges/orb.svg @@ -0,0 +1,22 @@ + diff --git a/charges/parrot.svg b/charges/parrot.svg new file mode 100644 index 00000000..1e65ce33 --- /dev/null +++ b/charges/parrot.svg @@ -0,0 +1,26 @@ + diff --git a/charges/pegasus.svg b/charges/pegasus.svg new file mode 100644 index 00000000..543b1a1e --- /dev/null +++ b/charges/pegasus.svg @@ -0,0 +1,42 @@ + diff --git a/charges/pike.svg b/charges/pike.svg new file mode 100644 index 00000000..3bd9ed53 --- /dev/null +++ b/charges/pike.svg @@ -0,0 +1,19 @@ + diff --git a/charges/pique.svg b/charges/pique.svg new file mode 100644 index 00000000..c1e2e4d6 --- /dev/null +++ b/charges/pique.svg @@ -0,0 +1,6 @@ + diff --git a/charges/pot.svg b/charges/pot.svg new file mode 100644 index 00000000..96e39d04 --- /dev/null +++ b/charges/pot.svg @@ -0,0 +1,10 @@ + diff --git a/charges/rake.svg b/charges/rake.svg new file mode 100644 index 00000000..03220f06 --- /dev/null +++ b/charges/rake.svg @@ -0,0 +1,26 @@ + diff --git a/charges/raven.svg b/charges/raven.svg new file mode 100644 index 00000000..3d1ae0a5 --- /dev/null +++ b/charges/raven.svg @@ -0,0 +1,23 @@ + diff --git a/charges/rose.svg b/charges/rose.svg new file mode 100644 index 00000000..b80c3d51 --- /dev/null +++ b/charges/rose.svg @@ -0,0 +1,12 @@ + diff --git a/charges/roundel.svg b/charges/roundel.svg new file mode 100644 index 00000000..c02cc392 --- /dev/null +++ b/charges/roundel.svg @@ -0,0 +1,6 @@ + diff --git a/charges/roundel2.svg b/charges/roundel2.svg new file mode 100644 index 00000000..c3d8c036 --- /dev/null +++ b/charges/roundel2.svg @@ -0,0 +1,7 @@ + diff --git a/charges/rustre.svg b/charges/rustre.svg new file mode 100644 index 00000000..dbfe8848 --- /dev/null +++ b/charges/rustre.svg @@ -0,0 +1,6 @@ + diff --git a/charges/sabre.svg b/charges/sabre.svg new file mode 100644 index 00000000..306ff201 --- /dev/null +++ b/charges/sabre.svg @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/charges/sabresCrossed.svg b/charges/sabresCrossed.svg new file mode 100644 index 00000000..ad87ee2e --- /dev/null +++ b/charges/sabresCrossed.svg @@ -0,0 +1,40 @@ + \ No newline at end of file diff --git a/charges/serpent.svg b/charges/serpent.svg new file mode 100644 index 00000000..fb2089d0 --- /dev/null +++ b/charges/serpent.svg @@ -0,0 +1,23 @@ + diff --git a/charges/stagsAttires.svg b/charges/stagsAttires.svg new file mode 100644 index 00000000..0fd887c4 --- /dev/null +++ b/charges/stagsAttires.svg @@ -0,0 +1,14 @@ + diff --git a/charges/sun.svg b/charges/sun.svg new file mode 100644 index 00000000..247515eb --- /dev/null +++ b/charges/sun.svg @@ -0,0 +1,6 @@ + diff --git a/charges/sunInSplendour.svg b/charges/sunInSplendour.svg new file mode 100644 index 00000000..a11ab0dc --- /dev/null +++ b/charges/sunInSplendour.svg @@ -0,0 +1,15 @@ + diff --git a/charges/swan.svg b/charges/swan.svg new file mode 100644 index 00000000..491b23fd --- /dev/null +++ b/charges/swan.svg @@ -0,0 +1,26 @@ + diff --git a/charges/swanErased.svg b/charges/swanErased.svg new file mode 100644 index 00000000..40e1e588 --- /dev/null +++ b/charges/swanErased.svg @@ -0,0 +1,10 @@ + diff --git a/charges/sword.svg b/charges/sword.svg new file mode 100644 index 00000000..65f0ac35 --- /dev/null +++ b/charges/sword.svg @@ -0,0 +1,14 @@ + diff --git a/charges/template.svg b/charges/template.svg new file mode 100644 index 00000000..a7510939 --- /dev/null +++ b/charges/template.svg @@ -0,0 +1,21 @@ + + + + diff --git a/charges/tower.svg b/charges/tower.svg new file mode 100644 index 00000000..40df5b9e --- /dev/null +++ b/charges/tower.svg @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/charges/tree.svg b/charges/tree.svg new file mode 100644 index 00000000..41a4b032 --- /dev/null +++ b/charges/tree.svg @@ -0,0 +1,7 @@ + diff --git a/charges/trefle.svg b/charges/trefle.svg new file mode 100644 index 00000000..9916c5c2 --- /dev/null +++ b/charges/trefle.svg @@ -0,0 +1,6 @@ + diff --git a/charges/triangle.svg b/charges/triangle.svg new file mode 100644 index 00000000..7109e498 --- /dev/null +++ b/charges/triangle.svg @@ -0,0 +1,6 @@ + diff --git a/charges/trianglePierced.svg b/charges/trianglePierced.svg new file mode 100644 index 00000000..9017aead --- /dev/null +++ b/charges/trianglePierced.svg @@ -0,0 +1,6 @@ + diff --git a/charges/unicornRampant.svg b/charges/unicornRampant.svg new file mode 100644 index 00000000..18040236 --- /dev/null +++ b/charges/unicornRampant.svg @@ -0,0 +1,27 @@ + diff --git a/charges/wheel.svg b/charges/wheel.svg new file mode 100644 index 00000000..aadbde83 --- /dev/null +++ b/charges/wheel.svg @@ -0,0 +1,23 @@ + diff --git a/charges/wing.svg b/charges/wing.svg new file mode 100644 index 00000000..04c108f2 --- /dev/null +++ b/charges/wing.svg @@ -0,0 +1,34 @@ + diff --git a/charges/wingSword.svg b/charges/wingSword.svg new file mode 100644 index 00000000..4fe4b70e --- /dev/null +++ b/charges/wingSword.svg @@ -0,0 +1,23 @@ + diff --git a/charges/wolfHeadErased.svg b/charges/wolfHeadErased.svg new file mode 100644 index 00000000..4f821ba8 --- /dev/null +++ b/charges/wolfHeadErased.svg @@ -0,0 +1,20 @@ + diff --git a/charges/wolfPassant.svg b/charges/wolfPassant.svg new file mode 100644 index 00000000..35e68ef2 --- /dev/null +++ b/charges/wolfPassant.svg @@ -0,0 +1,30 @@ + diff --git a/charges/wolfRampant.svg b/charges/wolfRampant.svg new file mode 100644 index 00000000..3c9344b0 --- /dev/null +++ b/charges/wolfRampant.svg @@ -0,0 +1,27 @@ + diff --git a/charges/wolfStatant.svg b/charges/wolfStatant.svg new file mode 100644 index 00000000..ff624fb7 --- /dev/null +++ b/charges/wolfStatant.svg @@ -0,0 +1,43 @@ + diff --git a/charges/wyvern.svg b/charges/wyvern.svg new file mode 100644 index 00000000..2767237f --- /dev/null +++ b/charges/wyvern.svg @@ -0,0 +1,66 @@ + diff --git a/charges/wyvernWithWingsDisplayed.svg b/charges/wyvernWithWingsDisplayed.svg new file mode 100644 index 00000000..ae71c5fe --- /dev/null +++ b/charges/wyvernWithWingsDisplayed.svg @@ -0,0 +1,42 @@ + diff --git a/charges/сarreau.svg b/charges/сarreau.svg new file mode 100644 index 00000000..24f687c0 --- /dev/null +++ b/charges/сarreau.svg @@ -0,0 +1,6 @@ + diff --git a/icons.css b/icons.css index eab23067..008faebc 100644 --- a/icons.css +++ b/icons.css @@ -252,17 +252,8 @@ .icon-if:before {font-style: italic; font-weight: bold;content:'if';} .icon-coa:before {content:'\f3ed'; font-size: .9em; color: #999;} /* '' */ .icon-half:before {font-weight: bold;content:'½';} -.icon-curve:before {content: 'C';} -.icon-area:before {content: 'O';} -.icon-curve:before, -.icon-area:before { - font-size: 1.5em; - padding: 0; - writing-mode: tb-rl; - margin-left: 1px; - width: .6em; - font-family: monospace; -} +.icon-voice:before {content:'🔊';} + .icon-die:before {content:'🎲';} .icon-button-die:before {content:'🎲'; padding-right: .4em;} .icon-button-power:before {content:'💪'; padding-right: .6em;} diff --git a/index.css b/index.css index 57d0df7f..2fff4e78 100644 --- a/index.css +++ b/index.css @@ -5,11 +5,6 @@ src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); } -body { - border: 0; - height: 100%; -} - t { pointer-events: none; } @@ -76,10 +71,14 @@ input, button, select, a, textarea { outline: none; } -button, select, a, .pointer { +button, select, a { cursor: pointer; } +.pointer { + cursor: pointer !important; +} + #prec text { font-size: 32px; stroke: none; @@ -101,7 +100,7 @@ button, select, a, .pointer { fill-rule: evenodd; } -#lakes, #coastline, #armies, #ice { +#lakes, #coastline, #armies, #ice, #emblems { cursor: pointer; } @@ -130,9 +129,26 @@ button, select, a, .pointer { #armies text { pointer-events: none; user-select: none; + stroke: none; + fill: #fff; + text-shadow: 0 0 4px #000; + dominant-baseline: central; + text-anchor: middle; + font-family: Helvetica; + fill-opacity: 1; } -#statesBody, #provincesBody, #relig, #biomes, #cults { +#armies text.regimentIcon { + font-size: .8em; +} + +#statesBody, #provincesBody { + stroke-width: 2; + fill-rule: evenodd; + mask: url(#land); +} + +#relig, #biomes, #cults { stroke-width: .6; fill-rule: evenodd; mask: url(#land); @@ -141,7 +157,6 @@ button, select, a, .pointer { #statesHalo { fill: none; filter: url(#blur5); - /*animation: hideshow 3s infinite;*/ } #borders { @@ -156,11 +171,6 @@ button, select, a, .pointer { font-size: 9px; } -@keyframes hideshow { - 0% {stroke-width: 1;} - 50% {stroke-width: 10;} -} - #rivers { stroke: none; mask: url(#land); @@ -707,6 +717,15 @@ fieldset { width: 6%; } +.emblemShapePreview { + width: 1.5em; + height: 1.5em; + margin: -.4em .1em; + fill: #fff; + stroke: #000; + stroke-width: 5px; +} + #styleContent table { border-spacing: 0; margin-left: .2em; @@ -833,7 +852,7 @@ body button.noicon { } #brushesButtons > button { - padding: 0; + padding: .3em; } #brushesButtons svg { @@ -1088,6 +1107,7 @@ i.resetButton:active { box-shadow: inset 1px 1px 0 0 #ccc; border-color: #a6a6da; background-color: #ecd8d8; + border-radius: 10%; } .ui-dialog input[type="range"] { @@ -1187,7 +1207,7 @@ div.slider .ui-slider-handle { #brushPower, #brushRadius { - width: 8em; + width: 12em; } #rescaleHigher, @@ -1261,7 +1281,7 @@ div.states { line-height: 1.5em; } -div.states:hover { +div.states:hover, div.states.hovered { border: 1px solid #c4c4c4; background-image: linear-gradient(to right, #dedede 100%, #f2f2f2 50%, #fcfcfc 0%); } @@ -1333,7 +1353,7 @@ div.states>.small { } div.states>.cultureName { - width: 5em; + width: 7em; } div.states>.culturePopulation { @@ -1390,6 +1410,18 @@ div.states>input.riverType { width: 5em; } +div.states > .coaIcon { + stroke-width: 3; + width: 1.4em; + height: 1.4em; + margin: -.3em 0; + cursor: pointer; +} + +div.states > .coaIcon > use { + pointer-events: none; +} + #diplomacyBodySection > div { cursor: pointer; } @@ -1421,9 +1453,13 @@ div.states>input.riverType { line-height: 1.4em; } +#burgBody div.label { + display: inline-block; + width: 6em; +} + #stateNameEditor div.label, #provinceNameEditor div.label, -#burgBody div.label, #regimentBody div.label { display: inline-block; width: 5.5em; @@ -1438,9 +1474,7 @@ div.states>input.riverType { } .burgFeature { - font-size: 1.2em; - padding: 1px 2px; - color: #555; + padding: 1px; cursor: pointer; } @@ -1491,12 +1525,10 @@ div.states.active { } div.states.Self { - border-color: #858b8e; - background-image: linear-gradient(to right, #f2f2f2 0%, #b0c6d9 100%); - font-style: italic; - font-weight: bold; margin-bottom: .2em; cursor: default !important; + padding: .2em 0 0 .5em; + font-weight: bold; } div.states button.selectCapital { @@ -1532,6 +1564,44 @@ rect.fillRect { width: 5em; } +#emblemBody > div { + padding: 1px 3px; + transition: all .3s ease-out; +} + +#emblemBody > div.active { + background-color: #54ca7728; +} + +#emblemArmiger { + text-align: center; + display: block; +} + +#emblemBody .label { + width: 6em; + display: inline-block; +} + +#emblemBody select { + width: 9em; +} + +#emblemsBottom { + margin-top: 4px; + text-align: center; +} + +#emblemUploadControl, +#emblemDownloadControl { + margin-top: .3em; + text-align: center; +} + +#emblemDownloadControl > input { + width: 4.1em; +} + #picker text { cursor: default; } @@ -1854,7 +1924,6 @@ div#notes { background: rgba(255, 250, 228, 0.7); box-shadow: 2px 2px 5px -3px #3a2804; white-space: pre-line; - pointer-events: none; } div#notesHeader { @@ -2079,6 +2148,11 @@ svg.button { border: dashed 1px #5d4651; } +.speaker { + font-size: .9em; + cursor: pointer; +} + #prompt { position: absolute; left: 50%; @@ -2089,9 +2163,10 @@ svg.button { padding: 1.2em; border: solid 1px #000; font-size: 1.2em; + z-index: 1000; } -#promptTest { +#promptText { padding: 0 0 .6em 0; font-weight: bold; font-family: sans-serif; @@ -2125,6 +2200,44 @@ svg.button { stroke-width: 0; } +.pell { + border: 1px solid hsla(0,0%,4%,.1) +} + +.pell,.pell-content { + box-sizing: border-box +} + +.pell-content { + height: 14em; + outline: 0; + overflow-y: auto; + padding: .6em; + font-family: Copperplate, monospace; + background-color: #fff; + border: 1px solid #dedede; +} + +.pell-actionbar { + background-color: #fff; + border: 1px solid #dedede; + border-bottom: 0; +} + +.pell-button { + background-color: transparent; + border: none; + cursor: pointer; + height: 30px; + outline: 0; + width: 30px; + vertical-align: bottom +} + +.pell-button-selected { + background-color: #f0f0f0 +} + #debug { font-size: 1px; opacity: .8; diff --git a/index.html b/index.html index eaf4bf58..e4e31e0a 100644 --- a/index.html +++ b/index.html @@ -7,8 +7,6 @@ - - @@ -34,13 +32,9 @@ #loading-text span:nth-child(3), #mapOverlay > span:nth-child(3) {animation-delay: 2s;} @keyframes blink {0% {opacity: 0;} 20% {opacity: 1;} 100% {opacity: .1;}} - - - - - + + -
+ +")},quote:{icon:"“ ”",title:"Quote",result:()=>o("formatBlock","
")},olist:{icon:"#",title:"Ordered List",result:()=>o("insertOrderedList")},ulist:{icon:"•",title:"Unordered List",result:()=>o("insertUnorderedList")},code:{icon:"</>",title:"Code",result:()=>o("formatBlock","")},line:{icon:"―",title:"Horizontal Line",result:()=>o("insertHorizontalRule")},link:{icon:"🔗",title:"Link",result:()=>navigator.clipboard.readText().then(e=>o("createLink",e))},image:{icon:"📷",title:"Image",result:()=>{navigator.clipboard.readText().then(e=>o("insertImage",e)),o("enableObjectResizing")}}},r={actionbar:"pell-actionbar",button:"pell-button",content:"pell-content",selected:"pell-button-selected"};return{exec:o,init:i=>{const a=i.actions?i.actions.map(e=>"string"==typeof e?l[e]:l[e.name]?{...l[e.name],...e}:e):Object.keys(l).map(e=>l[e]),s={...r,...i.classes},c=i.defaultParagraphSeparator||"div",u=n("div");u.className=s.actionbar,t(i.element,u);const d=i.element.content=n("div");return d.contentEditable=!0,d.className=s.content,d.oninput=(({target:{firstChild:e}})=>{e&&3===e.nodeType?o("formatBlock",`<${c}>`):"
"===d.innerHTML&&(d.innerHTML=""),i.onChange(d.innerHTML)}),d.onkeydown=(e=>{"Enter"===e.key&&"blockquote"===(e=>document.queryCommandValue(e))("formatBlock")&&setTimeout(()=>o("formatBlock",`<${c}>`),0)}),t(i.element,d),a.forEach(i=>{const o=n("button");if(o.className=s.button,o.innerHTML=i.icon,o.title=i.title,o.setAttribute("type","button"),o.onclick=(()=>i.result()&&d.focus()),i.state){const t=()=>o.classList[i.state()?"add":"remove"](s.selected);e(d,"keyup",t),e(d,"mouseup",t),e(o,"click",t)}t(u,o)}),i.styleWithCSS&&o("styleWithCSS"),o("defaultParagraphSeparator",c),i.element}}});
\ No newline at end of file
diff --git a/libs/publicstorage.js b/libs/publicstorage.js
deleted file mode 100644
index 9d8ebe54..00000000
--- a/libs/publicstorage.js
+++ /dev/null
@@ -1,188 +0,0 @@
-// https://github.com/Highbrainer/jeevaneo-js-publicstorage. MIT
-const IFRAME_ROOT_URL = "https://publicstorage.neocities.org/shared-iframe.html";
-
-class PublicStorageAccess {
-
- constructor ({debug=false}={}) {
- this.uid = this.uniqueId();
- this.debug=debug;
- }
-
- uniqueId() {
- function chr4(){
- return Math.random().toString(16).slice(-4);
- }
- return chr4() + chr4() +
- '-' + chr4() +
- '-' + chr4() +
- '-' + chr4() +
- '-' + chr4() + chr4() + chr4();
- }
-
- _debug(msg) {
- if(this.debug) {
- if(console && console.debug) {
- console.debug(msg);
- }
- }
- }
-
- prepareIFrame() {
- const that = this;
- const iframe = document.createElement("iframe");
- iframe.id=that.uid;
- iframe.src=IFRAME_ROOT_URL + "?uid=init-"+that.uid;
- iframe.style.cssText="display:none;";
- return new Promise(function(resolve, reject) {
- window.addEventListener('message', function mafunc(tkn) {
-
- if (IFRAME_ROOT_URL.indexOf(tkn.origin)<0) {
- return;
- }
-
- try {
- const packet = JSON.parse(tkn.data);
-
- if(!(packet.frameId === "init-" + that.uid)) {
- // ignore
- return;
- }
-
- if(packet.ready) {
- resolve(iframe);
- }
- } catch (e) {
- reject(tkn.data);
- }
- window.removeEventListener('message', mafunc);
- });
- onLoadThen().then(() => {
- document.getElementsByTagName("body")[0].appendChild(iframe);
- });
-
- setTimeout(()=>reject(`Request ${that.uid} TIMEOUTED!`), 20000);
- });
- }
-
- access(access, prop, value = null, level = "local") {
-
- if(!(access === "get" || access === "set" || access === "delete")) {
- throw new Error("access can only be 'set', 'get' or 'delete' - not '" + access + "'");
- }
-
- if (!prop) {
- throw new Error("Prop name is mandatory");
- }
-
- if(!(level === "local" || level === "session")) {
- throw new Error("level can only be 'session' or 'local' - not '" + access + "'");
- }
-
- const that = this;
-
- const promise = new Promise(function(resolve, reject) {
- that.prepareIFrame().then(iframe => {
- window.addEventListener('message', function mafunc(tkn) {
- if (IFRAME_ROOT_URL.indexOf(tkn.origin)<0) {
- return;
- }
- try {
- var packet = JSON.parse(tkn.data);
-
- if(!(packet.uid === that.uid)) {
- // ignore
- return;
- }
- resolve(packet.body);
- } catch (e) {
- reject(tkn.data);
- }
- iframe.parentNode.removeChild(iframe);
- window.removeEventListener('message', mafunc);
- });
-
- const request = {uid:that.uid, access:access, prop:prop, value:value, level:level};
- iframe.contentWindow.postMessage(JSON.stringify(request), '*');
- setTimeout(()=>reject("TIMEOUTED!"), 20000);
- });
- });
- return promise;
- }
-
-}
-
-function __createDebugIFrame() {
- onLoadThen().then(function(){
- const iframe = document.createElement("iframe");
- iframe.src=IFRAME_ROOT_URL + "?for-debug-only";
- iframe.style.cssText="display:none;";
- document.getElementsByTagName("body")[0].appendChild(iframe);
- });
-}
-
-class PublicStorage {
-
- constructor({debug=false}={}) {
- if(debug) {
- __createDebugIFrame();
- }
- }
-
- sessionGet(prop) {
- return new PublicStorageAccess().access("get", prop, null, "session");
- }
- sessionSet(prop, value) {
- return new PublicStorageAccess().access("set", prop, value, "session");
- }
- sessionUnset(prop) {
- return new PublicStorageAccess().access("delete", prop, null, "session");
- }
- localGet(prop) {
- return new PublicStorageAccess().access("get", prop, null, "local");
- }
- localSet(prop, value) {
- return new PublicStorageAccess().access("set", prop, value, "local");
- }
- localUnset(prop) {
- return new PublicStorageAccess().access("delete", prop, null, "local");
- }
- get(prop) {
- return this.localGet(prop);
- }
- set(prop, value) {
- return this.localSet(prop, value);
- }
- unset(prop) {
- return this.localUnset(prop);
- }
-}
-
-const publicstorage = new PublicStorage();
-
-function onLoadThen() {
- return new Promise(function(resolve, reject) {
- if (window) {
- if(document.getElementsByTagName('BODY')[0]) {
- resolve();
- } else {
- registerOnLoad(function unregisterme() {
- resolve();
- window.removeEventListener('load', unregisterme);
- });
- }
- }
- setTimeout(function() {reject(new Error("Timeout waiting for onLoad!"));}, 10000);
- });
-}
-
-function registerOnLoad(lambda) {
- if (window.addEventListener) {
- window.addEventListener('load', lambda);
- } else if (window.attachEvent) {
- window.attachEvent('onload', lambda);
- }
-}
-
-onLoadThen().then(() => window.publicstorage = publicstorage).catch(e=> console.error(e));
-//export {onLoadThen, PublicStorage, publicstorage as default}
-// module.exports = onLoadThen();
diff --git a/libs/rgbquant.js b/libs/rgbquant.js
deleted file mode 100644
index bc0bd1b9..00000000
--- a/libs/rgbquant.js
+++ /dev/null
@@ -1,935 +0,0 @@
-/*
-* Copyright (c) 2015, Leon Sorokin
-* All rights reserved. (MIT Licensed)
-*
-* RgbQuant.js - an image quantization lib
-*/
-
-(function(){
- function RgbQuant(opts) {
- opts = opts || {};
-
- // 1 = by global population, 2 = subregion population threshold
- this.method = opts.method || 2;
- // desired final palette size
- this.colors = opts.colors || 256;
- // # of highest-frequency colors to start with for palette reduction
- this.initColors = opts.initColors || 4096;
- // color-distance threshold for initial reduction pass
- this.initDist = opts.initDist || 0.01;
- // subsequent passes threshold
- this.distIncr = opts.distIncr || 0.005;
- // palette grouping
- this.hueGroups = opts.hueGroups || 10;
- this.satGroups = opts.satGroups || 10;
- this.lumGroups = opts.lumGroups || 10;
- // if > 0, enables hues stats and min-color retention per group
- this.minHueCols = opts.minHueCols || 0;
- // HueStats instance
- this.hueStats = this.minHueCols ? new HueStats(this.hueGroups, this.minHueCols) : null;
-
- // subregion partitioning box size
- this.boxSize = opts.boxSize || [64,64];
- // number of same pixels required within box for histogram inclusion
- this.boxPxls = opts.boxPxls || 2;
- // palette locked indicator
- this.palLocked = false;
- // palette sort order
-// this.sortPal = ['hue-','lum-','sat-'];
-
- // dithering/error diffusion kernel name
- this.dithKern = opts.dithKern || null;
- // dither serpentine pattern
- this.dithSerp = opts.dithSerp || false;
- // minimum color difference (0-1) needed to dither
- this.dithDelta = opts.dithDelta || 0;
-
- // accumulated histogram
- this.histogram = {};
- // palette - rgb triplets
- this.idxrgb = opts.palette ? opts.palette.slice(0) : [];
- // palette - int32 vals
- this.idxi32 = [];
- // reverse lookup {i32:idx}
- this.i32idx = {};
- // {i32:rgb}
- this.i32rgb = {};
- // enable color caching (also incurs overhead of cache misses and cache building)
- this.useCache = opts.useCache !== false;
- // min color occurance count needed to qualify for caching
- this.cacheFreq = opts.cacheFreq || 10;
- // allows pre-defined palettes to be re-indexed (enabling palette compacting and sorting)
- this.reIndex = opts.reIndex || this.idxrgb.length == 0;
- // selection of color-distance equation
- this.colorDist = opts.colorDist == "manhattan" ? distManhattan : distEuclidean;
-
- // if pre-defined palette, build lookups
- if (this.idxrgb.length > 0) {
- var self = this;
- this.idxrgb.forEach(function(rgb, i) {
- var i32 = (
- (255 << 24) | // alpha
- (rgb[2] << 16) | // blue
- (rgb[1] << 8) | // green
- rgb[0] // red
- ) >>> 0;
-
- self.idxi32[i] = i32;
- self.i32idx[i32] = i;
- self.i32rgb[i32] = rgb;
- });
- }
- }
-
- // gathers histogram info
- RgbQuant.prototype.sample = function sample(img, width) {
- if (this.palLocked)
- throw "Cannot sample additional images, palette already assembled.";
-
- var data = getImageData(img, width);
-
- switch (this.method) {
- case 1: this.colorStats1D(data.buf32); break;
- case 2: this.colorStats2D(data.buf32, data.width); break;
- }
- };
-
- // image quantizer
- // todo: memoize colors here also
- // @retType: 1 - Uint8Array (default), 2 - Indexed array, 3 - Match @img type (unimplemented, todo)
- RgbQuant.prototype.reduce = function reduce(img, retType, dithKern, dithSerp) {
- if (!this.palLocked)
- this.buildPal();
-
- dithKern = dithKern || this.dithKern;
- dithSerp = typeof dithSerp != "undefined" ? dithSerp : this.dithSerp;
-
- retType = retType || 1;
-
- // reduce w/dither
- if (dithKern)
- var out32 = this.dither(img, dithKern, dithSerp);
- else {
- var data = getImageData(img),
- buf32 = data.buf32,
- len = buf32.length,
- out32 = new Uint32Array(len);
-
- for (var i = 0; i < len; i++) {
- var i32 = buf32[i];
- out32[i] = this.nearestColor(i32);
- }
- }
-
- if (retType == 1)
- return new Uint8Array(out32.buffer);
-
- if (retType == 2) {
- var out = [],
- len = out32.length;
-
- for (var i = 0; i < len; i++) {
- var i32 = out32[i];
- out[i] = this.i32idx[i32];
- }
-
- return out;
- }
- };
-
- // adapted from http://jsbin.com/iXofIji/2/edit by PAEz
- RgbQuant.prototype.dither = function(img, kernel, serpentine) {
- // http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/
- var kernels = {
- FloydSteinberg: [
- [7 / 16, 1, 0],
- [3 / 16, -1, 1],
- [5 / 16, 0, 1],
- [1 / 16, 1, 1]
- ],
- FalseFloydSteinberg: [
- [3 / 8, 1, 0],
- [3 / 8, 0, 1],
- [2 / 8, 1, 1]
- ],
- Stucki: [
- [8 / 42, 1, 0],
- [4 / 42, 2, 0],
- [2 / 42, -2, 1],
- [4 / 42, -1, 1],
- [8 / 42, 0, 1],
- [4 / 42, 1, 1],
- [2 / 42, 2, 1],
- [1 / 42, -2, 2],
- [2 / 42, -1, 2],
- [4 / 42, 0, 2],
- [2 / 42, 1, 2],
- [1 / 42, 2, 2]
- ],
- Atkinson: [
- [1 / 8, 1, 0],
- [1 / 8, 2, 0],
- [1 / 8, -1, 1],
- [1 / 8, 0, 1],
- [1 / 8, 1, 1],
- [1 / 8, 0, 2]
- ],
- Jarvis: [ // Jarvis, Judice, and Ninke / JJN?
- [7 / 48, 1, 0],
- [5 / 48, 2, 0],
- [3 / 48, -2, 1],
- [5 / 48, -1, 1],
- [7 / 48, 0, 1],
- [5 / 48, 1, 1],
- [3 / 48, 2, 1],
- [1 / 48, -2, 2],
- [3 / 48, -1, 2],
- [5 / 48, 0, 2],
- [3 / 48, 1, 2],
- [1 / 48, 2, 2]
- ],
- Burkes: [
- [8 / 32, 1, 0],
- [4 / 32, 2, 0],
- [2 / 32, -2, 1],
- [4 / 32, -1, 1],
- [8 / 32, 0, 1],
- [4 / 32, 1, 1],
- [2 / 32, 2, 1],
- ],
- Sierra: [
- [5 / 32, 1, 0],
- [3 / 32, 2, 0],
- [2 / 32, -2, 1],
- [4 / 32, -1, 1],
- [5 / 32, 0, 1],
- [4 / 32, 1, 1],
- [2 / 32, 2, 1],
- [2 / 32, -1, 2],
- [3 / 32, 0, 2],
- [2 / 32, 1, 2],
- ],
- TwoSierra: [
- [4 / 16, 1, 0],
- [3 / 16, 2, 0],
- [1 / 16, -2, 1],
- [2 / 16, -1, 1],
- [3 / 16, 0, 1],
- [2 / 16, 1, 1],
- [1 / 16, 2, 1],
- ],
- SierraLite: [
- [2 / 4, 1, 0],
- [1 / 4, -1, 1],
- [1 / 4, 0, 1],
- ],
- };
-
- if (!kernel || !kernels[kernel]) {
- throw 'Unknown dithering kernel: ' + kernel;
- }
-
- var ds = kernels[kernel];
-
- var data = getImageData(img),
-// buf8 = data.buf8,
- buf32 = data.buf32,
- width = data.width,
- height = data.height,
- len = buf32.length;
-
- var dir = serpentine ? -1 : 1;
-
- for (var y = 0; y < height; y++) {
- if (serpentine)
- dir = dir * -1;
-
- var lni = y * width;
-
- for (var x = (dir == 1 ? 0 : width - 1), xend = (dir == 1 ? width : 0); x !== xend; x += dir) {
- // Image pixel
- var idx = lni + x,
- i32 = buf32[idx],
- r1 = (i32 & 0xff),
- g1 = (i32 & 0xff00) >> 8,
- b1 = (i32 & 0xff0000) >> 16;
-
- // Reduced pixel
- var i32x = this.nearestColor(i32),
- r2 = (i32x & 0xff),
- g2 = (i32x & 0xff00) >> 8,
- b2 = (i32x & 0xff0000) >> 16;
-
- buf32[idx] =
- (255 << 24) | // alpha
- (b2 << 16) | // blue
- (g2 << 8) | // green
- r2;
-
- // dithering strength
- if (this.dithDelta) {
- var dist = this.colorDist([r1, g1, b1], [r2, g2, b2]);
- if (dist < this.dithDelta)
- continue;
- }
-
- // Component distance
- var er = r1 - r2,
- eg = g1 - g2,
- eb = b1 - b2;
-
- for (var i = (dir == 1 ? 0 : ds.length - 1), end = (dir == 1 ? ds.length : 0); i !== end; i += dir) {
- var x1 = ds[i][1] * dir,
- y1 = ds[i][2];
-
- var lni2 = y1 * width;
-
- if (x1 + x >= 0 && x1 + x < width && y1 + y >= 0 && y1 + y < height) {
- var d = ds[i][0];
- var idx2 = idx + (lni2 + x1);
-
- var r3 = (buf32[idx2] & 0xff),
- g3 = (buf32[idx2] & 0xff00) >> 8,
- b3 = (buf32[idx2] & 0xff0000) >> 16;
-
- var r4 = Math.max(0, Math.min(255, r3 + er * d)),
- g4 = Math.max(0, Math.min(255, g3 + eg * d)),
- b4 = Math.max(0, Math.min(255, b3 + eb * d));
-
- buf32[idx2] =
- (255 << 24) | // alpha
- (b4 << 16) | // blue
- (g4 << 8) | // green
- r4; // red
- }
- }
- }
- }
-
- return buf32;
- };
-
- // reduces histogram to palette, remaps & memoizes reduced colors
- RgbQuant.prototype.buildPal = function buildPal(noSort) {
- if (this.palLocked || this.idxrgb.length > 0 && this.idxrgb.length <= this.colors) return;
-
- var histG = this.histogram,
- sorted = sortedHashKeys(histG, true);
-
- if (sorted.length == 0)
- throw "Nothing has been sampled, palette cannot be built.";
-
- switch (this.method) {
- case 1:
- var cols = this.initColors,
- last = sorted[cols - 1],
- freq = histG[last];
-
- var idxi32 = sorted.slice(0, cols);
-
- // add any cut off colors with same freq as last
- var pos = cols, len = sorted.length;
- while (pos < len && histG[sorted[pos]] == freq)
- idxi32.push(sorted[pos++]);
-
- // inject min huegroup colors
- if (this.hueStats)
- this.hueStats.inject(idxi32);
-
- break;
- case 2:
- var idxi32 = sorted;
- break;
- }
-
- // int32-ify values
- idxi32 = idxi32.map(function(v){return +v;});
-
- this.reducePal(idxi32);
-
- if (!noSort && this.reIndex)
- this.sortPal();
-
- // build cache of top histogram colors
- if (this.useCache)
- this.cacheHistogram(idxi32);
-
- this.palLocked = true;
- };
-
- RgbQuant.prototype.palette = function palette(tuples, noSort) {
- this.buildPal(noSort);
- return tuples ? this.idxrgb : new Uint8Array((new Uint32Array(this.idxi32)).buffer);
- };
-
- RgbQuant.prototype.prunePal = function prunePal(keep) {
- var i32;
-
- for (var j = 0; j < this.idxrgb.length; j++) {
- if (!keep[j]) {
- i32 = this.idxi32[j];
- this.idxrgb[j] = null;
- this.idxi32[j] = null;
- delete this.i32idx[i32];
- }
- }
-
- // compact
- if (this.reIndex) {
- var idxrgb = [],
- idxi32 = [],
- i32idx = {};
-
- for (var j = 0, i = 0; j < this.idxrgb.length; j++) {
- if (this.idxrgb[j]) {
- i32 = this.idxi32[j];
- idxrgb[i] = this.idxrgb[j];
- i32idx[i32] = i;
- idxi32[i] = i32;
- i++;
- }
- }
-
- this.idxrgb = idxrgb;
- this.idxi32 = idxi32;
- this.i32idx = i32idx;
- }
- };
-
- // reduces similar colors from an importance-sorted Uint32 rgba array
- RgbQuant.prototype.reducePal = function reducePal(idxi32) {
- // if pre-defined palette's length exceeds target
- if (this.idxrgb.length > this.colors) {
- // quantize histogram to existing palette
- var len = idxi32.length, keep = {}, uniques = 0, idx, pruned = false;
-
- for (var i = 0; i < len; i++) {
- // palette length reached, unset all remaining colors (sparse palette)
- if (uniques == this.colors && !pruned) {
- this.prunePal(keep);
- pruned = true;
- }
-
- idx = this.nearestIndex(idxi32[i]);
-
- if (uniques < this.colors && !keep[idx]) {
- keep[idx] = true;
- uniques++;
- }
- }
-
- if (!pruned) {
- this.prunePal(keep);
- pruned = true;
- }
- }
- // reduce histogram to create initial palette
- else {
- // build full rgb palette
- var idxrgb = idxi32.map(function(i32) {
- return [
- (i32 & 0xff),
- (i32 & 0xff00) >> 8,
- (i32 & 0xff0000) >> 16,
- ];
- });
-
- var len = idxrgb.length,
- palLen = len,
- thold = this.initDist;
-
- // palette already at or below desired length
- if (palLen > this.colors) {
- while (palLen > this.colors) {
- var memDist = [];
-
- // iterate palette
- for (var i = 0; i < len; i++) {
- var pxi = idxrgb[i], i32i = idxi32[i];
- if (!pxi) continue;
-
- for (var j = i + 1; j < len; j++) {
- var pxj = idxrgb[j], i32j = idxi32[j];
- if (!pxj) continue;
-
- var dist = this.colorDist(pxi, pxj);
-
- if (dist < thold) {
- // store index,rgb,dist
- memDist.push([j, pxj, i32j, dist]);
-
- // kill squashed value
- delete(idxrgb[j]);
- palLen--;
- }
- }
- }
-
- // palette reduction pass
- // console.log("palette length: " + palLen);
-
- // if palette is still much larger than target, increment by larger initDist
- thold += (palLen > this.colors * 3) ? this.initDist : this.distIncr;
- }
-
- // if palette is over-reduced, re-add removed colors with largest distances from last round
- if (palLen < this.colors) {
- // sort descending
- sort.call(memDist, function(a,b) {
- return b[3] - a[3];
- });
-
- var k = 0;
- while (palLen < this.colors) {
- // re-inject rgb into final palette
- idxrgb[memDist[k][0]] = memDist[k][1];
-
- palLen++;
- k++;
- }
- }
- }
-
- var len = idxrgb.length;
- for (var i = 0; i < len; i++) {
- if (!idxrgb[i]) continue;
-
- this.idxrgb.push(idxrgb[i]);
- this.idxi32.push(idxi32[i]);
-
- this.i32idx[idxi32[i]] = this.idxi32.length - 1;
- this.i32rgb[idxi32[i]] = idxrgb[i];
- }
- }
- };
-
- // global top-population
- RgbQuant.prototype.colorStats1D = function colorStats1D(buf32) {
- var histG = this.histogram,
- num = 0, col,
- len = buf32.length;
-
- for (var i = 0; i < len; i++) {
- col = buf32[i];
-
- // skip transparent
- if ((col & 0xff000000) >> 24 == 0) continue;
-
- // collect hue stats
- if (this.hueStats)
- this.hueStats.check(col);
-
- if (col in histG)
- histG[col]++;
- else
- histG[col] = 1;
- }
- };
-
- // population threshold within subregions
- // FIXME: this can over-reduce (few/no colors same?), need a way to keep
- // important colors that dont ever reach local thresholds (gradients?)
- RgbQuant.prototype.colorStats2D = function colorStats2D(buf32, width) {
- var boxW = this.boxSize[0],
- boxH = this.boxSize[1],
- area = boxW * boxH,
- boxes = makeBoxes(width, buf32.length / width, boxW, boxH),
- histG = this.histogram,
- self = this;
-
- boxes.forEach(function(box) {
- var effc = Math.max(Math.round((box.w * box.h) / area) * self.boxPxls, 2),
- histL = {}, col;
-
- iterBox(box, width, function(i) {
- col = buf32[i];
-
- // skip transparent
- if ((col & 0xff000000) >> 24 == 0) return;
-
- // collect hue stats
- if (self.hueStats)
- self.hueStats.check(col);
-
- if (col in histG)
- histG[col]++;
- else if (col in histL) {
- if (++histL[col] >= effc)
- histG[col] = histL[col];
- }
- else
- histL[col] = 1;
- });
- });
-
- if (this.hueStats)
- this.hueStats.inject(histG);
- };
-
- // TODO: group very low lum and very high lum colors
- // TODO: pass custom sort order
- RgbQuant.prototype.sortPal = function sortPal() {
- var self = this;
-
- this.idxi32.sort(function(a,b) {
- var idxA = self.i32idx[a],
- idxB = self.i32idx[b],
- rgbA = self.idxrgb[idxA],
- rgbB = self.idxrgb[idxB];
-
- var hslA = rgb2hsl(rgbA[0],rgbA[1],rgbA[2]),
- hslB = rgb2hsl(rgbB[0],rgbB[1],rgbB[2]);
-
- // sort all grays + whites together
- var hueA = (rgbA[0] == rgbA[1] && rgbA[1] == rgbA[2]) ? -1 : hueGroup(hslA.h, self.hueGroups);
- var hueB = (rgbB[0] == rgbB[1] && rgbB[1] == rgbB[2]) ? -1 : hueGroup(hslB.h, self.hueGroups);
-
- var hueDiff = hueB - hueA;
- if (hueDiff) return -hueDiff;
-
- var lumDiff = lumGroup(+hslB.l.toFixed(2)) - lumGroup(+hslA.l.toFixed(2));
- if (lumDiff) return -lumDiff;
-
- var satDiff = satGroup(+hslB.s.toFixed(2)) - satGroup(+hslA.s.toFixed(2));
- if (satDiff) return -satDiff;
- });
-
- // sync idxrgb & i32idx
- this.idxi32.forEach(function(i32, i) {
- self.idxrgb[i] = self.i32rgb[i32];
- self.i32idx[i32] = i;
- });
- };
-
- // TOTRY: use HUSL - http://boronine.com/husl/
- RgbQuant.prototype.nearestColor = function nearestColor(i32) {
- var idx = this.nearestIndex(i32);
- return idx === null ? 0 : this.idxi32[idx];
- };
-
- // TOTRY: use HUSL - http://boronine.com/husl/
- RgbQuant.prototype.nearestIndex = function nearestIndex(i32) {
- // alpha 0 returns null index
- if ((i32 & 0xff000000) >> 24 == 0)
- return null;
-
- if (this.useCache && (""+i32) in this.i32idx)
- return this.i32idx[i32];
-
- var min = 1000,
- idx,
- rgb = [
- (i32 & 0xff),
- (i32 & 0xff00) >> 8,
- (i32 & 0xff0000) >> 16,
- ],
- len = this.idxrgb.length;
-
- for (var i = 0; i < len; i++) {
- if (!this.idxrgb[i]) continue; // sparse palettes
-
- var dist = this.colorDist(rgb, this.idxrgb[i]);
-
- if (dist < min) {
- min = dist;
- idx = i;
- }
- }
-
- return idx;
- };
-
- RgbQuant.prototype.cacheHistogram = function cacheHistogram(idxi32) {
- for (var i = 0, i32 = idxi32[i]; i < idxi32.length && this.histogram[i32] >= this.cacheFreq; i32 = idxi32[i++])
- this.i32idx[i32] = this.nearestIndex(i32);
- };
-
- function HueStats(numGroups, minCols) {
- this.numGroups = numGroups;
- this.minCols = minCols;
- this.stats = {};
-
- for (var i = -1; i < numGroups; i++)
- this.stats[i] = {num: 0, cols: []};
-
- this.groupsFull = 0;
- }
-
- HueStats.prototype.check = function checkHue(i32) {
- if (this.groupsFull == this.numGroups + 1)
- this.check = function() {return;};
-
- var r = (i32 & 0xff),
- g = (i32 & 0xff00) >> 8,
- b = (i32 & 0xff0000) >> 16,
- hg = (r == g && g == b) ? -1 : hueGroup(rgb2hsl(r,g,b).h, this.numGroups),
- gr = this.stats[hg],
- min = this.minCols;
-
- gr.num++;
-
- if (gr.num > min)
- return;
- if (gr.num == min)
- this.groupsFull++;
-
- if (gr.num <= min)
- this.stats[hg].cols.push(i32);
- };
-
- HueStats.prototype.inject = function injectHues(histG) {
- for (var i = -1; i < this.numGroups; i++) {
- if (this.stats[i].num <= this.minCols) {
- switch (typeOf(histG)) {
- case "Array":
- this.stats[i].cols.forEach(function(col){
- if (histG.indexOf(col) == -1)
- histG.push(col);
- });
- break;
- case "Object":
- this.stats[i].cols.forEach(function(col){
- if (!histG[col])
- histG[col] = 1;
- else
- histG[col]++;
- });
- break;
- }
- }
- }
- };
-
- // Rec. 709 (sRGB) luma coef
- var Pr = .2126,
- Pg = .7152,
- Pb = .0722;
-
- // http://alienryderflex.com/hsp.html
- function rgb2lum(r,g,b) {
- return Math.sqrt(
- Pr * r*r +
- Pg * g*g +
- Pb * b*b
- );
- }
-
- var rd = 255,
- gd = 255,
- bd = 255;
-
- var euclMax = Math.sqrt(Pr*rd*rd + Pg*gd*gd + Pb*bd*bd);
- // perceptual Euclidean color distance
- function distEuclidean(rgb0, rgb1) {
- var rd = rgb1[0]-rgb0[0],
- gd = rgb1[1]-rgb0[1],
- bd = rgb1[2]-rgb0[2];
-
- return Math.sqrt(Pr*rd*rd + Pg*gd*gd + Pb*bd*bd) / euclMax;
- }
-
- var manhMax = Pr*rd + Pg*gd + Pb*bd;
- // perceptual Manhattan color distance
- function distManhattan(rgb0, rgb1) {
- var rd = Math.abs(rgb1[0]-rgb0[0]),
- gd = Math.abs(rgb1[1]-rgb0[1]),
- bd = Math.abs(rgb1[2]-rgb0[2]);
-
- return (Pr*rd + Pg*gd + Pb*bd) / manhMax;
- }
-
- // http://rgb2hsl.nichabi.com/javascript-function.php
- function rgb2hsl(r, g, b) {
- var max, min, h, s, l, d;
- r /= 255;
- g /= 255;
- b /= 255;
- max = Math.max(r, g, b);
- min = Math.min(r, g, b);
- l = (max + min) / 2;
- if (max == min) {
- h = s = 0;
- } else {
- d = max - min;
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
- switch (max) {
- case r: h = (g - b) / d + (g < b ? 6 : 0); break;
- case g: h = (b - r) / d + 2; break;
- case b: h = (r - g) / d + 4; break
- }
- h /= 6;
- }
-// h = Math.floor(h * 360)
-// s = Math.floor(s * 100)
-// l = Math.floor(l * 100)
- return {
- h: h,
- s: s,
- l: rgb2lum(r,g,b),
- };
- }
-
- function hueGroup(hue, segs) {
- var seg = 1/segs,
- haf = seg/2;
-
- if (hue >= 1 - haf || hue <= haf)
- return 0;
-
- for (var i = 1; i < segs; i++) {
- var mid = i*seg;
- if (hue >= mid - haf && hue <= mid + haf)
- return i;
- }
- }
-
- function satGroup(sat) {
- return sat;
- }
-
- function lumGroup(lum) {
- return lum;
- }
-
- function typeOf(val) {
- return Object.prototype.toString.call(val).slice(8,-1);
- }
-
- var sort = isArrSortStable() ? Array.prototype.sort : stableSort;
-
- // must be used via stableSort.call(arr, fn)
- function stableSort(fn) {
- var type = typeOf(this[0]);
-
- if (type == "Number" || type == "String") {
- var ord = {}, len = this.length, val;
-
- for (var i = 0; i < len; i++) {
- val = this[i];
- if (ord[val] || ord[val] === 0) continue;
- ord[val] = i;
- }
-
- return this.sort(function(a,b) {
- return fn(a,b) || ord[a] - ord[b];
- });
- }
- else {
- var ord = this.map(function(v){return v});
-
- return this.sort(function(a,b) {
- return fn(a,b) || ord.indexOf(a) - ord.indexOf(b);
- });
- }
- }
-
- // test if js engine's Array#sort implementation is stable
- function isArrSortStable() {
- var str = "abcdefghijklmnopqrstuvwxyz";
-
- return "xyzvwtursopqmnklhijfgdeabc" == str.split("").sort(function(a,b) {
- return ~~(str.indexOf(b)/2.3) - ~~(str.indexOf(a)/2.3);
- }).join("");
- }
-
- // returns uniform pixel data from various img
- // TODO?: if array is passed, createimagedata, createlement canvas? take a pxlen?
- function getImageData(img, width) {
- var can, ctx, imgd, buf8, buf32, height;
-
- switch (typeOf(img)) {
- case "HTMLImageElement":
- can = document.createElement("canvas");
- can.width = img.naturalWidth;
- can.height = img.naturalHeight;
- ctx = can.getContext("2d");
- ctx.drawImage(img,0,0);
- case "Canvas":
- case "HTMLCanvasElement":
- can = can || img;
- ctx = ctx || can.getContext("2d");
- case "CanvasRenderingContext2D":
- ctx = ctx || img;
- can = can || ctx.canvas;
- imgd = ctx.getImageData(0, 0, can.width, can.height);
- case "ImageData":
- imgd = imgd || img;
- width = imgd.width;
- if (typeOf(imgd.data) == "CanvasPixelArray")
- buf8 = new Uint8Array(imgd.data);
- else
- buf8 = imgd.data;
- case "Array":
- case "CanvasPixelArray":
- buf8 = buf8 || new Uint8Array(img);
- case "Uint8Array":
- case "Uint8ClampedArray":
- buf8 = buf8 || img;
- buf32 = new Uint32Array(buf8.buffer);
- case "Uint32Array":
- buf32 = buf32 || img;
- buf8 = buf8 || new Uint8Array(buf32.buffer);
- width = width || buf32.length;
- height = buf32.length / width;
- }
-
- return {
- can: can,
- ctx: ctx,
- imgd: imgd,
- buf8: buf8,
- buf32: buf32,
- width: width,
- height: height,
- };
- }
-
- // partitions a rect of wid x hgt into
- // array of bboxes of w0 x h0 (or less)
- function makeBoxes(wid, hgt, w0, h0) {
- var wnum = ~~(wid/w0), wrem = wid%w0,
- hnum = ~~(hgt/h0), hrem = hgt%h0,
- xend = wid-wrem, yend = hgt-hrem;
-
- var bxs = [];
- for (var y = 0; y < hgt; y += h0)
- for (var x = 0; x < wid; x += w0)
- bxs.push({x:x, y:y, w:(x==xend?wrem:w0), h:(y==yend?hrem:h0)});
-
- return bxs;
- }
-
- // iterates @bbox within a parent rect of width @wid; calls @fn, passing index within parent
- function iterBox(bbox, wid, fn) {
- var b = bbox,
- i0 = b.y * wid + b.x,
- i1 = (b.y + b.h - 1) * wid + (b.x + b.w - 1),
- cnt = 0, incr = wid - b.w + 1, i = i0;
-
- do {
- fn.call(this, i);
- i += (++cnt % b.w == 0) ? incr : 1;
- } while (i <= i1);
- }
-
- // returns array of hash keys sorted by their values
- function sortedHashKeys(obj, desc) {
- var keys = [];
-
- for (var key in obj)
- keys.push(key);
-
- return sort.call(keys, function(a,b) {
- return desc ? obj[b] - obj[a] : obj[a] - obj[b];
- });
- }
-
- // expose
- this.RgbQuant = RgbQuant;
-
- // expose to commonJS
- if (typeof module !== 'undefined' && module.exports) {
- module.exports = RgbQuant;
- }
-
-}).call(this);
\ No newline at end of file
diff --git a/libs/rgbquant.min.js b/libs/rgbquant.min.js
new file mode 100644
index 00000000..1cc0104e
--- /dev/null
+++ b/libs/rgbquant.min.js
@@ -0,0 +1,2 @@
+// © 2015, Leon Sorokin, MIT
+(function(){function t(t){var s;t=t||{},this.method=t.method||2,this.colors=t.colors||256,this.initColors=t.initColors||4096,this.initDist=t.initDist||.01,this.distIncr=t.distIncr||.005,this.hueGroups=t.hueGroups||10,this.satGroups=t.satGroups||10,this.lumGroups=t.lumGroups||10,this.minHueCols=t.minHueCols||0,this.hueStats=this.minHueCols?new i(this.hueGroups,this.minHueCols):null,this.boxSize=t.boxSize||[64,64],this.boxPxls=t.boxPxls||2,this.palLocked=!1,this.dithKern=t.dithKern||null,this.dithSerp=t.dithSerp||!1,this.dithDelta=t.dithDelta||0,this.histogram={},this.idxrgb=t.palette?t.palette.slice(0):[],this.idxi32=[],this.i32idx={},this.i32rgb={},this.useCache=!1!==t.useCache,this.cacheFreq=t.cacheFreq||10,this.reIndex=t.reIndex||0==this.idxrgb.length,this.colorDist="manhattan"==t.colorDist?n:r,0>>0;s.idxi32[i]=r,s.i32idx[r]=i,s.i32rgb[r]=t})}function i(t,i){this.numGroups=t,this.minCols=i,this.stats={};for(var r=-1;r>8,b=(16711680&x)>>16,m=this.nearestColor(x),v=255&m,x=(65280&m)>>8,m=(16711680&m)>>16;if(h[f]=255<<24|m<<16|x<<8|v,this.dithDelta)if(this.colorDist([p,g,b],[v,x,m])>8,A=(16711680&h[D])>>16,I=Math.max(0,Math.min(255,I+y*M)),P=Math.max(0,Math.min(255,P+w*M)),M=Math.max(0,Math.min(255,A+S*M)),h[D]=255<<24|M<<16|P<<8|I)}}}return h},t.prototype.buildPal=function(t){if(!(this.palLocked||0this.colors){for(var i,r=t.length,s={},e=0,h=!1,n=0;n>8,(16711680&t)>>16]}),o=r=a.length,u=this.initDist;if(o>this.colors){for(;o>this.colors;){for(var l=[],n=0;n3*this.colors?this.initDist:this.distIncr}if(o>24!=0&&(this.hueStats&&this.hueStats.check(i),i in r?r[i]++:r[i]=1)},t.prototype.colorStats2D=function(e,h){var t=this.boxSize[0],i=this.boxSize[1],n=t*i,i=function(t,i,r,s){for(var e=t%r,h=i%s,n=t-e,a=i-h,o=[],u=0;u>24!=0&&(o.hueStats&&o.hueStats.check(i),i in a?a[i]++:i in s?++s[i]>=r&&(a[i]=s[i]):s[i]=1)})}),this.hueStats&&this.hueStats.inject(a)},t.prototype.sortPal=function(){var e=this;this.idxi32.sort(function(t,i){var r=e.i32idx[t],s=e.i32idx[i],t=e.idxrgb[r],i=e.idxrgb[s],r=a(t[0],t[1],t[2]),s=a(i[0],i[1],i[2]),t=t[0]==t[1]&&t[1]==t[2]?-1:c(r.h,e.hueGroups),t=(i[0]==i[1]&&i[1]==i[2]?-1:c(s.h,e.hueGroups))-t;if(t)return-t;t=+s.l.toFixed(2)-+r.l.toFixed(2);if(t)return-t;r=+s.s.toFixed(2)-+r.s.toFixed(2);return r?-r:void 0}),this.idxi32.forEach(function(t,i){e.idxrgb[i]=e.i32rgb[t],e.i32idx[t]=i})},t.prototype.nearestColor=function(t){t=this.nearestIndex(t);return null===t?0:this.idxi32[t]},t.prototype.nearestIndex=function(t){if((4278190080&t)>>24==0)return null;if(this.useCache&&""+t in this.i32idx)return this.i32idx[t];for(var i,r,s=1e3,e=[255&t,(65280&t)>>8,(16711680&t)>>16],h=this.idxrgb.length,n=0;n=this.cacheFreq;r=t[i++])this.i32idx[r]=this.nearestIndex(r)},i.prototype.check=function(t){this.groupsFull==this.numGroups+1&&(this.check=function(){});var i=255&t,r=(65280&t)>>8,s=(16711680&t)>>16,i=i==r&&r==s?-1:c(a(i,r,s).h,this.numGroups),r=this.stats[i],s=this.minCols;r.num++,r.num>s||(r.num==s&&this.groupsFull++,r.num<=s&&this.stats[i].cols.push(t))},i.prototype.inject=function(i){for(var t=-1;t>>=1;return(n+t)/r};return m.int32=function(){return 0|p.g(4)},m.quick=function(){return p.g(4)/4294967296},m.double=m,q(s(p.S),a),(t.pass||e||function(n,r,t,e){return e&&(e.S&&o(e,p),n.state=function(){return o(p,{})}),t?(b[g]=n,r):n})(m,f,"global"in t?t.global:this==b,t.state)}function n(n){var r,t=n.length,u=this,e=0,o=u.i=u.j=0,i=u.S=[];for(t||(n=[t++]);e tip("Click to open Units Editor"));
+scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => editUnits());
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
// main data variables
@@ -117,7 +125,7 @@ let scale = 1, viewX = 0, viewY = 0;
const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", zoomed);
// default options
-let options = {}; // options object
+let options = {pinNotes:false}; // options object
let mapCoordinates = {}; // map coordinates on globe
options.winds = [225, 45, 225, 315, 135, 315]; // default wind directions
@@ -142,7 +150,7 @@ void function checkLoadParameters() {
// of there is a valid maplink, try to load .map file from URL
if (params.get("maplink")) {
- console.warn("Load map from URL");
+ WARN && console.warn("Load map from URL");
const maplink = params.get("maplink");
const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
const valid = pattern.test(maplink);
@@ -152,7 +160,7 @@ void function checkLoadParameters() {
// if there is a seed (user of MFCG provided), generate map for it
if (params.get("seed")) {
- console.warn("Generate map for seed");
+ WARN && console.warn("Generate map for seed");
generateMapOnLoad();
return;
}
@@ -161,24 +169,24 @@ void function checkLoadParameters() {
if (onloadMap.value === "saved") {
ldb.get("lastMap", blob => {
if (blob) {
- console.warn("Load last saved map");
+ WARN && console.warn("Load last saved map");
try {
uploadMap(blob);
}
catch(error) {
- console.error(error);
- console.warn("Cannot load stored map, random map to be generated");
+ ERROR && console.error(error);
+ WARN && console.warn("Cannot load stored map, random map to be generated");
generateMapOnLoad();
}
} else {
- console.error("No map stored, random map to be generated");
+ ERROR && console.error("No map stored, random map to be generated");
generateMapOnLoad();
}
});
return;
}
- console.warn("Generate random map");
+ WARN && console.warn("Generate random map");
generateMapOnLoad();
}()
@@ -197,7 +205,7 @@ function loadMapFromURL(maplink, random) {
}
function showUploadErrorMessage(error, URL, random) {
- console.error(error);
+ ERROR && console.error(error);
alertMessage.innerHTML = `Cannot load map from the ${link(URL, "link provided")}.
${random?`A new random map is generated. `:''}
Please ensure the linked file is reachable and CORS is allowed on server side`;
@@ -249,7 +257,7 @@ function focusOn() {
// find burg for MFCG and focus on it
function findBurgForMFCG(params) {
const cells = pack.cells, burgs = pack.burgs;
- if (pack.burgs.length < 2) {console.error("Cannot select a burg for MFCG"); return;}
+ if (pack.burgs.length < 2) {ERROR && console.error("Cannot select a burg for MFCG"); return;}
// used for selection
const size = +params.get("size");
@@ -274,7 +282,7 @@ function findBurgForMFCG(params) {
// select a burg with closest population from selection
const selected = d3.scan(selection, (a, b) => Math.abs(a.population - size) - Math.abs(b.population - size));
const burgId = selection[selected].i;
- if (!burgId) {console.error("Cannot select a burg for MFCG"); return;}
+ if (!burgId) {ERROR && console.error("Cannot select a burg for MFCG"); return;}
const b = burgs[burgId];
const referrer = new URL(document.referrer);
@@ -330,31 +338,21 @@ function applyDefaultBiomesSystem() {
}
function showWelcomeMessage() {
- const post = link("https://www.reddit.com/r/FantasyMapGenerator/comments/ft5b41/update_new_version_is_published_into_the_battle_v14/", "Main changes:"); // announcement on Reddit
+ const post = link("https://www.reddit.com/r/FantasyMapGenerator/comments/ft5b41/update_v15/", "Main changes:"); // announcement on Reddit
const changelog = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "previous version");
const reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit community");
const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server");
const patreon = link("https://www.patreon.com/azgaar", "Patreon");
- const desktop = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A#is-there-a-desktop-version", "desktop application");
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version ${version}.
This version is compatible with ${changelog}, loaded .map files will be auto-updated.
-
${post}
- - Military forces changes (${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Military-Forces", "detailed description")})
- - Battle simulation (${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Battle-Simulator", "detailed description")})
- - Ice layer and Ice editor
- - Route and River Elevation profile (by EvolvedExperiment)
- - Image Converter enhancement
- - Name generator improvement
- - Improved integration with City Generator
- - Fogging restyle
+ - Emblems generation
+ - Emblem editor integrated with ${link("https://azgaar.github.io/Armoria", "Armoria")}
+ - Burg editor screen update
+ - Speak name functionality
-
- You can can also download a ${desktop}.
-
Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.
-
Thanks for all supporters on ${patreon}! `;
$("#alert").dialog(
@@ -430,7 +428,18 @@ function invokeActiveZooming() {
const relative = Math.max(rn((desired + desired / scale) / 2, 2), 1);
this.getAttribute("font-size", relative);
const hidden = hideLabels.checked && (relative * scale < 6 || relative * scale > 50);
+ if (hidden) this.classList.add("hidden");
+ else this.classList.remove("hidden");
+ });
+ }
+
+ // rescale emblems on zoom
+ if (emblems.style("display") !== "none") {
+ emblems.selectAll("g").each(function() {
+ const size = this.getAttribute("font-size") * scale;
+ const hidden = hideEmblems.checked && (size < 25 || size > 300);
if (hidden) this.classList.add("hidden"); else this.classList.remove("hidden");
+ if (!hidden && window.COArenderer && this.children.length && !this.children[0].getAttribute("href")) renderGroupCOAs(this);
});
}
@@ -462,6 +471,18 @@ function invokeActiveZooming() {
}
}
+async function renderGroupCOAs(g) {
+ const [group, type] = g.id === "burgEmblems" ? [pack.burgs, "burg"] :
+ g.id === "provinceEmblems" ? [pack.provinces, "province"] :
+ [pack.states, "state"];
+ for (let use of g.children) {
+ const i = +use.dataset.i;
+ const id = type+"COA"+i;
+ COArenderer.trigger(id, group[i].coa);
+ use.setAttribute("href", "#"+id);
+ }
+}
+
// add drag to upload logic, pull request from @evyatron
void function addDragToUpload() {
document.addEventListener("dragover", function(e) {
@@ -507,7 +528,7 @@ function generate() {
const timeStart = performance.now();
invokeActiveZooming();
generateSeed();
- console.group("Generated Map " + seed);
+ INFO && console.group("Generated Map " + seed);
applyMapSize();
randomizeOptions();
placePoints();
@@ -548,12 +569,12 @@ function generate() {
addZones();
Names.getMapName();
- console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
- showStatistics();
- console.groupEnd("Generated Map " + seed);
+ WARN && console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
+ INFO && showStatistics();
+ INFO && console.groupEnd("Generated Map " + seed);
}
catch(error) {
- console.error(error);
+ ERROR && console.error(error);
clearMainTip();
alertMessage.innerHTML = `An error is occured on map generation. Please retry.
@@ -581,41 +602,41 @@ function generateSeed() {
else if (optionsSeed.value && optionsSeed.value != seed) seed = optionsSeed.value;
else seed = Math.floor(Math.random() * 1e9).toString();
optionsSeed.value = seed;
- Math.seedrandom(seed);
+ Math.random = aleaPRNG(seed);
}
// Place points to calculate Voronoi diagram
function placePoints() {
- console.time("placePoints");
+ TIME && console.time("placePoints");
const cellsDesired = 10000 * densityInput.value; // generate 10k points for each densityInput point
const spacing = grid.spacing = rn(Math.sqrt(graphWidth * graphHeight / cellsDesired), 2); // spacing between points before jirrering
grid.boundary = getBoundaryPoints(graphWidth, graphHeight, spacing);
grid.points = getJitteredGrid(graphWidth, graphHeight, spacing); // jittered square grid
grid.cellsX = Math.floor((graphWidth + 0.5 * spacing) / spacing);
grid.cellsY = Math.floor((graphHeight + 0.5 * spacing) / spacing);
- console.timeEnd("placePoints");
+ TIME && console.timeEnd("placePoints");
}
// calculate Delaunay and then Voronoi diagram
function calculateVoronoi(graph, points) {
- console.time("calculateDelaunay");
+ TIME && console.time("calculateDelaunay");
const n = points.length;
const allPoints = points.concat(grid.boundary);
const delaunay = Delaunator.from(allPoints);
- console.timeEnd("calculateDelaunay");
+ TIME && console.timeEnd("calculateDelaunay");
- console.time("calculateVoronoi");
+ TIME && console.time("calculateVoronoi");
const voronoi = Voronoi(delaunay, allPoints, n);
graph.cells = voronoi.cells;
graph.cells.i = n < 65535 ? Uint16Array.from(d3.range(n)) : Uint32Array.from(d3.range(n)); // array of indexes
graph.vertices = voronoi.vertices;
- console.timeEnd("calculateVoronoi");
+ TIME && console.timeEnd("calculateVoronoi");
}
// Mark features (ocean, lakes, islands)
function markFeatures() {
- console.time("markFeatures");
- Math.seedrandom(seed); // restart Math.random() to get the same result on heightmap edit in Erase mode
+ TIME && console.time("markFeatures");
+ Math.random = aleaPRNG(seed); // restart Math.random() to get the same result on heightmap edit in Erase mode
const cells = grid.cells, heights = grid.cells.h;
cells.f = new Uint16Array(cells.i.length); // cell feature number
cells.t = new Int8Array(cells.i.length); // cell type: 1 = land coast; -1 = water near coast;
@@ -648,7 +669,7 @@ function markFeatures() {
queue[0] = cells.f.findIndex(f => !f); // find unmarked cell
}
- console.timeEnd("markFeatures");
+ TIME && console.timeEnd("markFeatures");
}
// How to handle lakes generated near seas? They can be both open or closed.
@@ -658,7 +679,7 @@ function openNearSeaLakes() {
if (templateInput.value === "Atoll") return; // no need for Atolls
const cells = grid.cells, features = grid.features;
if (!features.find(f => f.type === "lake")) return; // no lakes
- console.time("openLakes");
+ TIME && console.time("openLakes");
const limit = 50; // max height that can be breached by water
for (let t = 0, removed = true; t < 5 && removed; t++) {
@@ -694,7 +715,7 @@ function openNearSeaLakes() {
return true;
}
- console.timeEnd("openLakes");
+ TIME && console.timeEnd("openLakes");
}
// define map size and position based on template and random factor
@@ -745,7 +766,7 @@ function calculateMapCoordinates() {
// temperature model
function calculateTemperatures() {
- console.time('calculateTemperatures');
+ TIME && console.time('calculateTemperatures');
const cells = grid.cells;
cells.temp = new Int8Array(cells.i.length); // temperature array
@@ -771,12 +792,12 @@ function calculateTemperatures() {
return rn(height / 1000 * 6.5);
}
- console.timeEnd('calculateTemperatures');
+ TIME && console.timeEnd('calculateTemperatures');
}
// simplest precipitation model
function generatePrecipitation() {
- console.time('generatePrecipitation');
+ TIME && console.time('generatePrecipitation');
prec.selectAll("*").remove();
const cells = grid.cells;
cells.prec = new Uint8Array(cells.i.length); // precipitation array
@@ -785,7 +806,7 @@ function generatePrecipitation() {
let westerly = [], easterly = [], southerly = 0, northerly = 0;
{// latitude bands
- // x4 = 0-5 latitude: wet throught the year (rising zone)
+ // x4 = 0-5 latitude: wet through the year (rising zone)
// x2 = 5-20 latitude: wet summer (rising zone), dry winter (sinking zone)
// x1 = 20-30 latitude: dry all year (sinking zone)
// x2 = 30-50 latitude: wet winter (rising zone), dry summer (sinking zone)
@@ -887,12 +908,12 @@ function generatePrecipitation() {
if (southerly) wind.append("text").attr("x", graphWidth / 2).attr("y", graphHeight - 20).text("\u21C8");
}();
- console.timeEnd('generatePrecipitation');
+ TIME && console.timeEnd('generatePrecipitation');
}
// recalculate Voronoi Graph to pack cells
function reGraph() {
- console.time("reGraph");
+ TIME && console.time("reGraph");
let cells = grid.cells, points = grid.points, features = grid.features;
const newCells = {p:[], g:[], h:[], t:[], f:[], r:[], biome:[]}; // to store new data
const spacing2 = grid.spacing ** 2;
@@ -936,12 +957,12 @@ function reGraph() {
cells.area = new Uint16Array(cells.i.length); // cell area
cells.i.forEach(i => cells.area[i] = Math.abs(d3.polygonArea(getPackPolygon(i))));
- console.timeEnd("reGraph");
+ TIME && console.timeEnd("reGraph");
}
// Detect and draw the coasline
function drawCoastline() {
- console.time('drawCoastline');
+ TIME && console.time('drawCoastline');
reMarkFeatures();
const cells = pack.cells, vertices = pack.vertices, n = cells.i.length, features = pack.features;
const used = new Uint8Array(features.length); // store conneted features
@@ -1016,7 +1037,7 @@ function drawCoastline() {
if (v[0] !== prev && c0 !== c1) current = v[0]; else
if (v[1] !== prev && c1 !== c2) current = v[1]; else
if (v[2] !== prev && c0 !== c2) current = v[2];
- if (current === chain[chain.length-1]) {console.error("Next vertex is not found"); break;}
+ if (current === chain[chain.length-1]) {ERROR && console.error("Next vertex is not found"); break;}
}
//chain.push(chain[0]); // push first vertex as the last one
return chain;
@@ -1040,12 +1061,12 @@ function drawCoastline() {
}
}
- console.timeEnd('drawCoastline');
+ TIME && console.timeEnd('drawCoastline');
}
// Re-mark features (ocean, lakes, islands)
function reMarkFeatures() {
- console.time("reMarkFeatures");
+ TIME && console.time("reMarkFeatures");
const cells = pack.cells, features = pack.features = [0], temp = grid.cells.temp;
cells.f = new Uint16Array(cells.i.length); // cell feature number
cells.t = new Int16Array(cells.i.length); // cell type: 1 = land along coast; -1 = water along coast;
@@ -1113,13 +1134,13 @@ function reMarkFeatures() {
return "isle";
}
- console.timeEnd("reMarkFeatures");
+ TIME && console.timeEnd("reMarkFeatures");
}
// temporary elevate some lakes to resolve depressions and flux the water to form an open (exorheic) lake
function elevateLakes() {
if (templateInput.value === "Atoll") return; // no need for Atolls
- console.time('elevateLakes');
+ TIME && console.time('elevateLakes');
const cells = pack.cells, features = pack.features;
const maxCells = cells.i.length / 100; // size limit; let big lakes be closed (endorheic)
cells.i.forEach(i => {
@@ -1129,12 +1150,12 @@ function elevateLakes() {
//debug.append("circle").attr("cx", cells.p[i][0]).attr("cy", cells.p[i][1]).attr("r", .5).attr("fill", "blue");
});
- console.timeEnd('elevateLakes');
+ TIME && console.timeEnd('elevateLakes');
}
// assign biome id for each cell
function defineBiomes() {
- console.time("defineBiomes");
+ TIME && console.time("defineBiomes");
const cells = pack.cells, f = pack.features, temp = grid.cells.temp, prec = grid.cells.prec;
cells.biome = new Uint8Array(cells.i.length); // biomes array
@@ -1153,7 +1174,7 @@ function defineBiomes() {
return rn(4 + d3.mean(n));
}
- console.timeEnd("defineBiomes");
+ TIME && console.timeEnd("defineBiomes");
}
// assign biome id to a cell
@@ -1168,7 +1189,7 @@ function getBiomeId(moisture, temperature, height) {
// assess cells suitability to calculate population and rand cells for culture center and burgs placement
function rankCells() {
- console.time('rankCells');
+ TIME && console.time('rankCells');
const cells = pack.cells, f = pack.features;
cells.s = new Int16Array(cells.i.length); // cell suitability array
cells.pop = new Float32Array(cells.i.length); // cell population array
@@ -1202,13 +1223,13 @@ function rankCells() {
cells.pop[i] = cells.s[i] > 0 ? cells.s[i] * cells.area[i] / areaMean : 0;
}
- console.timeEnd('rankCells');
+ TIME && console.timeEnd('rankCells');
}
// generate some markers
function addMarkers(number = 1) {
if (!number) return;
- console.time("addMarkers");
+ TIME && console.time("addMarkers");
const cells = pack.cells, states = pack.states;
void function addVolcanoes() {
@@ -1374,12 +1395,12 @@ function addMarkers(number = 1) {
return id;
}
- console.timeEnd("addMarkers");
+ TIME && console.timeEnd("addMarkers");
}
// regenerate some zones
function addZones(number = 1) {
- console.time("addZones");
+ TIME && console.time("addZones");
const data = [], cells = pack.cells, states = pack.states, burgs = pack.burgs;
const used = new Uint8Array(cells.i.length); // to store used cells
@@ -1690,14 +1711,18 @@ function addZones(number = 1) {
.attr("points", d => getPackPolygon(d)).attr("id", function(d) {return this.parentNode.id+"_"+d});
}()
- console.timeEnd("addZones");
+ TIME && console.timeEnd("addZones");
}
// show map stats on generation complete
function showStatistics() {
const template = templateInput.value;
const templateRandom = locked("template") ? "" : "(random)";
- const stats = ` Seed: ${seed}
+
+ mapId = Date.now(); // unique map id is it's creation date number
+ mapHistory.push({seed, width:graphWidth, height:graphHeight, template, created:mapId});
+ console.log(`
+ Seed: ${seed}
Canvas size: ${graphWidth}x${graphHeight}
Template: ${template} ${templateRandom}
Points: ${grid.points.length}
@@ -1708,15 +1733,11 @@ function showStatistics() {
Burgs: ${pack.burgs.length-1}
Religions: ${pack.religions.length-1}
Culture set: ${culturesSet.selectedOptions[0].innerText}
- Cultures: ${pack.cultures.length-1}`;
-
- mapId = Date.now(); // unique map id is it's creation date number
- mapHistory.push({seed, width:graphWidth, height:graphHeight, template, created:mapId});
- console.log(stats);
+ Cultures: ${pack.cultures.length-1}`);
}
const regenerateMap = debounce(function() {
- console.warn("Generate new random map");
+ WARN && console.warn("Generate new random map");
closeDialogs("#worldConfigurator, #options3d");
customization = 0;
undraw();
@@ -1730,7 +1751,8 @@ const regenerateMap = debounce(function() {
// clear the map
function undraw() {
viewbox.selectAll("path, circle, polygon, line, text, use, #zones > g, #armies > g, #ruler > g").remove();
- defs.selectAll("path, clipPath").remove();
+ document.getElementById("deftemp").querySelectorAll("path, clipPath, svg").forEach(el => el.remove());
+ document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
notes = [];
unfog();
}
diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js
index b3083ac6..6120c27a 100644
--- a/modules/burgs-and-states.js
+++ b/modules/burgs-and-states.js
@@ -32,7 +32,7 @@
drawBurgs();
function placeCapitals() {
- console.time('placeCapitals');
+ TIME && console.time('placeCapitals');
let count = +regionsInput.value;
let burgs = [0];
@@ -41,8 +41,8 @@
if (sorted.length < count * 10) {
count = Math.floor(sorted.length / 10);
- if (!count) {console.warn(`There is no populated cells. Cannot generate states`); return burgs;}
- else {console.warn(`Not enought populated cells (${sorted.length}). Will generate only ${count} states`);}
+ if (!count) {WARN && console.warn(`There is no populated cells. Cannot generate states`); return burgs;}
+ else {WARN && console.warn(`Not enough populated cells (${sorted.length}). Will generate only ${count} states`);}
}
let burgsTree = d3.quadtree();
@@ -57,20 +57,20 @@
}
if (i === sorted.length - 1) {
- console.warn("Cannot place capitals with current spacing. Trying again with reduced spacing");
+ WARN && console.warn("Cannot place capitals with current spacing. Trying again with reduced spacing");
burgsTree = d3.quadtree();
i = -1, burgs = [0], spacing /= 1.2;
}
}
burgs[0] = burgsTree;
- console.timeEnd('placeCapitals');
+ TIME && console.timeEnd('placeCapitals');
return burgs;
}
// For each capital create a state
function createStates() {
- console.time('createStates');
+ TIME && console.time('createStates');
const states = [{i:0, name: "Neutrals"}];
const colors = getColors(burgs.length-1);
@@ -88,19 +88,21 @@
const expansionism = rn(Math.random() * powerInput.value + 1, 1);
const basename = b.name.length < 9 && b.cell%5 === 0 ? b.name : Names.getCultureShort(b.culture);
const name = Names.getState(basename, b.culture);
- const nomadic = [1, 2, 3, 4].includes(cells.biome[b.cell]);
- const type = nomadic ? "Nomadic" : cultures[b.culture].type === "Nomadic" ? "Generic" : cultures[b.culture].type;
- states.push({i, color: colors[i-1], name, expansionism, capital: i, type, center: b.cell, culture: b.culture});
+ const type = cultures[b.culture].type;
+
+ const coa = COA.generate(null, null, null, type);
+ coa.shield = COA.getShield(b.culture, null);
+ states.push({i, color: colors[i-1], name, expansionism, capital: i, type, center: b.cell, culture: b.culture, coa});
cells.burg[b.cell] = i;
});
- console.timeEnd('createStates');
+ TIME && console.timeEnd('createStates');
return states;
}
// place secondary settlements based on geo and economical evaluation
function placeTowns() {
- console.time('placeTowns');
+ TIME && console.time('placeTowns');
const score = new Int16Array(cells.s.map(s => s * gauss(1,3,0,20,3))); // a bit randomized cell score for towns placement
const sorted = cells.i.filter(i => !cells.burg[i] && score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
@@ -129,17 +131,17 @@
}
if (manorsInput.value != 1000 && burgsAdded < desiredNumber) {
- console.error(`Cannot place all burgs. Requested ${desiredNumber}, placed ${burgsAdded}`);
+ ERROR && console.error(`Cannot place all burgs. Requested ${desiredNumber}, placed ${burgsAdded}`);
}
burgs[0] = {name:undefined}; // do not store burgsTree anymore
- console.timeEnd('placeTowns');
+ TIME && console.timeEnd('placeTowns');
}
}
- // define burg coordinates, port status and define details
+ // define burg coordinates, coa, port status and define details
const specifyBurgs = function() {
- console.time("specifyBurgs");
+ TIME && console.time("specifyBurgs");
const cells = pack.cells, vertices = pack.vertices, features = pack.features, temp = grid.cells.temp;
for (const b of pack.burgs) {
@@ -175,6 +177,18 @@
if (i%2) b.x = rn(b.x + shift, 2); else b.x = rn(b.x - shift, 2);
if (cells.r[i]%2) b.y = rn(b.y + shift, 2); else b.y = rn(b.y - shift, 2);
}
+
+ // define emblem
+ const state = pack.states[b.state];
+ const stateCOA = state.coa;
+ let kinship = .25;
+ if (b.capital) kinship += .1;
+ else if (b.port) kinship -= .1;
+ if (b.culture !== state.culture) kinship -= .25;
+ b.type = getType(i, b.port);
+ const type = b.capital && P(.2) ? "Capital" : b.type === "Generic" ? "City" : b.type;
+ b.coa = COA.generate(stateCOA, kinship, null, type);
+ b.coa.shield = COA.getShield(b.culture, b.state);
}
// de-assign port status if it's the only one on feature
@@ -185,24 +199,36 @@
if (featurePorts.length === 1) featurePorts[0].port = 0;
}
- console.timeEnd("specifyBurgs");
+ TIME && console.timeEnd("specifyBurgs");
+ }
+
+ const getType = function(i, port) {
+ const cells = pack.cells;
+ if (port) return "Naval";
+ if (cells.haven[i] && pack.features[cells.f[cells.haven[i]]].type === "lake") return "Lake";
+ if (cells.h[i] > 60) return "Highland";
+ if (cells.r[i] && cells.r[i].length > 100 && cells.r[i].length >= pack.rivers[0].length) return "River";
+ if ([1, 2, 3, 4].includes(cells.biome[i])) return "Nomadic";
+ if (cells.biome[i] > 4 && cells.biome[i] < 10) return "Hunting";
+ return "Generic";
}
const defineBurgFeatures = function(newburg) {
+ const cells = pack.cells;
pack.burgs.filter(b => newburg ? b.i == newburg.i : (b.i && !b.removed)).forEach(b => {
const pop = b.population;
b.citadel = b.capital || pop > 50 && P(.75) || P(.5) ? 1 : 0;
b.plaza = pop > 50 || pop > 30 && P(.75) || pop > 10 && P(.5) || P(.25) ? 1 : 0;
b.walls = b.capital || pop > 30 || pop > 20 && P(.75) || pop > 10 && P(.5) || P(.2) ? 1 : 0;
b.shanty = pop > 30 || pop > 20 && P(.75) || b.walls && P(.75) ? 1 : 0;
- const religion = pack.cells.religion[b.cell];
+ const religion = cells.religion[b.cell];
const theocracy = pack.states[b.state].form === "Theocracy";
b.temple = religion && theocracy || pop > 50 || pop > 35 && P(.75) || pop > 20 && P(.5) ? 1 : 0;
});
}
const drawBurgs = function() {
- console.time("drawBurgs");
+ TIME && console.time("drawBurgs");
// remove old data
burgIcons.selectAll("circle").remove();
@@ -251,12 +277,12 @@
.attr("x", d => rn(d.x - taSize * .47, 2)).attr("y", d => rn(d.y - taSize * .47, 2))
.attr("width", taSize).attr("height", taSize);
- console.timeEnd("drawBurgs");
+ TIME && console.timeEnd("drawBurgs");
}
// growth algorithm to assign cells to states like we did for cultures
const expandStates = function() {
- console.time("expandStates");
+ TIME && console.time("expandStates");
const cells = pack.cells, states = pack.states, cultures = pack.cultures, burgs = pack.burgs;
cells.state = new Uint16Array(cells.i.length); // cell state
@@ -331,11 +357,11 @@
return 0;
}
- console.timeEnd("expandStates");
+ TIME && console.timeEnd("expandStates");
}
const normalizeStates = function() {
- console.time("normalizeStates");
+ TIME && console.time("normalizeStates");
const cells = pack.cells, burgs = pack.burgs;
for (const i of cells.i) {
@@ -348,14 +374,39 @@
if (buddies.length > 2) continue;
if (adversaries.length <= buddies.length) continue;
cells.state[i] = cells.state[adversaries[0]];
- //debug.append("circle").attr("cx", cells.p[i][0]).attr("cy", cells.p[i][1]).attr("r", .5).attr("fill", "red");
}
- console.timeEnd("normalizeStates");
+ TIME && console.timeEnd("normalizeStates");
+ }
+
+ // Resets the cultures of all burgs and states to their
+ // cell or center cell's (respectively) culture.
+ const updateCultures = function () {
+ TIME && console.time('updateCulturesForBurgsAndStates');
+
+ // Assign the culture associated with the burgs cell.
+ pack.burgs = pack.burgs.map( (burg, index) => {
+ // Ignore metadata burg
+ if(index === 0) {
+ return burg;
+ }
+ return {...burg, culture: pack.cells.culture[burg.cell]};
+ });
+
+ // Assign the culture associated with the states' center cell.
+ pack.states = pack.states.map( (state, index) => {
+ // Ignore neutrals state
+ if(index === 0) {
+ return state;
+ }
+ return {...state, culture: pack.cells.culture[state.center]};
+ });
+
+ TIME && console.timeEnd('updateCulturesForBurgsAndStates');
}
// calculate and draw curved state labels for a list of states
const drawStateLabels = function(list) {
- console.time("drawStateLabels");
+ TIME && console.time("drawStateLabels");
const cells = pack.cells, features = pack.features, states = pack.states;
const paths = []; // text paths
lineGen.curve(d3.curveBundle.beta(1));
@@ -373,11 +424,6 @@
const relaxed = chain.map(i => voronoi.vertices.p[i]).filter((p, i) => i%15 === 0 || i+1 === chain.length);
paths.push([s.i, relaxed]);
- // if (s.i == 13) debug.selectAll(".circle").data(points).enter().append("circle").attr("cx", d => d[0]).attr("cy", d => d[1]).attr("r", .5).attr("fill", "red");
- // if (s.i == 13) d3.select("#cells").selectAll(".polygon").data(d3.range(voronoi.cells.v.length)).enter().append("polygon").attr("points", d => voronoi.cells.v[d] ? voronoi.cells.v[d].map(v => c.p[v]) : "");
- // if (s.i == 13) debug.append("path").attr("d", round(lineGen(relaxed))).attr("fill", "none").attr("stroke", "blue").attr("stroke-width", .5);
- // if (s.i == 13) debug.selectAll(".circle").data(chain).enter().append("circle").attr("cx", d => c.p[d][0]).attr("cy", d => c.p[d][1]).attr("r", 1);
-
function getHull(start, state, maxLake) {
const queue = [start], hull = new Set();
@@ -412,7 +458,6 @@
const h = c.p.length < 200 ? 0 : c.p.length < 600 ? .5 : 1; // power of horyzontality shift
const end = pointsInside[d3.scan(pointsInside, (a, b) => (c.p[a][0] - c.p[b][0]) + (Math.abs(c.p[a][1] - y) - Math.abs(c.p[b][1] - y)) * h)]; // left point
const start = pointsInside[d3.scan(pointsInside, (a, b) => (c.p[b][0] - c.p[a][0]) - (Math.abs(c.p[b][1] - y) - Math.abs(c.p[a][1] - y)) * h)]; // right point
- //debug.append("line").attr("x1", c.p[start][0]).attr("y1", c.p[start][1]).attr("x2", c.p[end][0]).attr("y2", c.p[end][1]).attr("stroke", "#00dd00");
// connect leftmost and rightmost points with shortest path
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
@@ -514,7 +559,7 @@
el.insertAdjacentHTML("afterbegin", spans.join(""));
if (lines.length < 2) return;
- // check whether multilined label is generally inside the strate. If no, replace with short name label
+ // check whether multilined label is generally inside the state. If no, replace with short name label
const cs = pack.cells.state, b = el.parentNode.getBBox();
const c1 = () => +cs[findCell(b.x, b.y)] === id;
const c2 = () => +cs[findCell(b.x + b.width / 2, b.y)] === id;
@@ -537,14 +582,15 @@
if (!displayed) toggleLabels();
}()
- console.timeEnd("drawStateLabels");
+ TIME && console.timeEnd("drawStateLabels");
}
// calculate states data like area, population etc.
const collectStatistics = function() {
- console.time("collectStatistics");
+ TIME && console.time("collectStatistics");
const cells = pack.cells, states = pack.states;
states.forEach(s => {
+ if (s.removed) return;
s.cells = s.area = s.burgs = s.rural = s.urban = 0;
s.neighbors = new Set();
});
@@ -567,13 +613,16 @@
}
// convert neighbors Set object into array
- states.forEach(s => s.neighbors = Array.from(s.neighbors));
+ states.forEach(s => {
+ if (!s.neighbors) return;
+ s.neighbors = Array.from(s.neighbors);
+ });
- console.timeEnd("collectStatistics");
+ TIME && console.timeEnd("collectStatistics");
}
const assignColors = function() {
- console.time("assignColors");
+ TIME && console.time("assignColors");
const colors = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f"]; // d3.schemeSet2;
// assin basic color using greedy coloring algorithm
@@ -594,19 +643,20 @@
});
});
- console.timeEnd("assignColors");
+ TIME && console.timeEnd("assignColors");
}
// generate historical conflicts of each state
const generateCampaigns = function() {
- const wars = {"War":4, "Conflict":2, "Campaign":4, "Invasion":2, "Rebellion":2, "Conquest":2, "Intervention":1, "Expedition":1, "Crusade":1};
+ const wars = {"War":6, "Conflict":2, "Campaign":4, "Invasion":2, "Rebellion":2, "Conquest":2, "Intervention":1, "Expedition":1, "Crusade":1};
pack.states.forEach(s => {
if (!s.i || s.removed) return;
const n = s.neighbors.length ? s.neighbors : [0];
s.campaigns = n.map(i => {
const name = i && P(.8) ? pack.states[i].name : Names.getCultureShort(s.culture);
- const start = gauss(options.year-100, 150, 1, options.year-6), end = start + gauss(4, 5, 1, options.year - start - 1);
+ const start = gauss(options.year-100, 150, 1, options.year-6);
+ const end = start + gauss(4, 5, 1, options.year - start - 1);
return {name:getAdjective(name) + " " + rw(wars), start, end};
}).sort((a, b) => a.start - b.start);
});
@@ -614,7 +664,7 @@
// generate Diplomatic Relationships
const generateDiplomacy = function() {
- console.time("generateDiplomacy");
+ TIME && console.time("generateDiplomacy");
const cells = pack.cells, states = pack.states;
const chronicle = states[0].diplomacy = [];
const valid = states.filter(s => s.i && !states.removed);
@@ -689,9 +739,10 @@
// start a war
const war = [`${an}-${trimVowels(dn)}ian War`,`${an} declared a war on its rival ${dn}`];
- const start = options.year - gauss(2, 2, 0, 5);
- states[attacker].campaigns.push({name: `${trimVowels(dn)}ian War`, start, end:options.year});
- states[defender].campaigns.push({name: `${trimVowels(an)}ian War`, start, end:options.year});
+ const end = options.year;
+ const start = end - gauss(2, 2, 0, 5);
+ states[attacker].campaigns.push({name: `${trimVowels(dn)}ian War`, start, end});
+ states[defender].campaigns.push({name: `${trimVowels(an)}ian War`, start, end});
// attacker vassals join the war
ad.forEach((r, d) => {if (r === "Suzerain") {
@@ -754,21 +805,18 @@
chronicle.push(war); // add a record to diplomatical history
}
- console.timeEnd("generateDiplomacy");
+ TIME && console.timeEnd("generateDiplomacy");
//console.table(states.map(s => s.diplomacy));
}
// select a forms for listed or all valid states
const defineStateForms = function(list) {
- console.time("defineStateForms");
+ TIME && console.time("defineStateForms");
const states = pack.states.filter(s => s.i && !s.removed);
if (states.length < 1) return;
const generic = {Monarchy:25, Republic:2, Union:1};
const naval = {Monarchy:25, Republic:8, Union:3};
- const genericArray = [], navalArray = []; // turn weighted array into simple array
- for (const t in generic) {for (let j=0; j < generic[t]; j++) {genericArray.push(t);}}
- for (const t in naval) {for (let j=0; j < naval[t]; j++) {navalArray.push(t);}}
const median = d3.median(pack.states.map(s => s.area));
const empireMin = states.map(s => s.area).sort((a, b) => b - a)[Math.max(Math.ceil(states.length ** .4) - 2, 0)];
@@ -781,21 +829,19 @@
const monarchy = ["Duchy", "Grand Duchy", "Principality", "Kingdom", "Empire"]; // per expansionism tier
const republic = {Republic:75, Federation:4, Oligarchy:2, Tetrarchy:1, Triumvirate:1, Diarchy:1, "Trade Company":4, Junta:1}; // weighted random
const union = {Union:3, League:4, Confederation:1, "United Kingdom":1, "United Republic":1, "United Provinces":2, Commonwealth:1, Heptarchy:1}; // weighted random
+ const theocracy = {Theocracy: 20, Brotherhood:1, Thearchy:2, See:1};
+ const anarchy = {"Free Territory":2, Council:3, Commune:1, Community:1};
for (const s of states) {
if (list && !list.includes(s.i)) continue;
- // some nomadic states
- if (s.type === "Nomadic" && P(.8)) {
- s.form = "Horde";
- s.formName = expTiers[s.i] > 2 ? "United Hordes" : "Horde";
- s.fullName = getFullName(s);
- continue;
- }
-
const religion = pack.cells.religion[s.center];
- const theocracy = religion && pack.religions[religion].expansion === "state" || (P(.1) && pack.religions[religion].type === "Organized");
- s.form = theocracy ? "Theocracy" : s.type === "Naval" ? ra(navalArray) : ra(genericArray);
+ const isTheocracy = religion && pack.religions[religion].expansion === "state" || (P(.1) && ["Organized", "Cult"].includes(pack.religions[religion].type));
+ const isAnarchy = P(.01 - expTiers[s.i]/500);
+
+ if (isTheocracy) s.form = "Theocracy";
+ else if (isAnarchy) s.form = "Anarchy";
+ else s.form = s.type === "Naval" ? rw(naval) : rw(generic);
s.formName = selectForm(s);
s.fullName = getFullName(s);
}
@@ -813,13 +859,13 @@
if (base === 16 && (form === "Empire" || form === "Kingdom")) return "Sultanate"; // Turkic
if (base === 5 && (form === "Empire" || form === "Kingdom")) return "Tsardom"; // Ruthenian
- if (base === 31 && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Mongolian
+ if ([16, 31].includes(base) && (form === "Empire" || form === "Kingdom")) return "Khaganate"; // Turkic, Mongolian
if (base === 12 && (form === "Kingdom" || form === "Grand Duchy")) return "Shogunate"; // Japanese
if ([18, 17].includes(base) && form === "Empire") return "Caliphate"; // Arabic, Berber
if (base === 18 && (form === "Grand Duchy" || form === "Duchy")) return "Emirate"; // Arabic
if (base === 7 && (form === "Grand Duchy" || form === "Duchy")) return "Despotate"; // Greek
if (base === 31 && (form === "Grand Duchy" || form === "Duchy")) return "Ulus"; // Mongolian
- if (base === 16 && (form === "Grand Duchy" || form === "Duchy")) return "Beylik"; // Turkic
+ if (base === 16 && (form === "Grand Duchy" || form === "Duchy")) return "Horde"; // Turkic
if (base === 24 && (form === "Grand Duchy" || form === "Duchy")) return "Satrapy"; // Iranian
return form;
}
@@ -837,34 +883,34 @@
}
if (s.form === "Union") return rw(union);
+ if (s.form === "Anarchy") return rw(anarchy);
if (s.form === "Theocracy") {
- // default name is "Theocracy"
if (P(.5) && [0, 1, 2, 3, 4, 6, 8, 9, 13, 15, 20].includes(base)) return "Diocese"; // Euporean
if (P(.9) && [7, 5].includes(base)) return "Eparchy"; // Greek, Ruthenian
if (P(.9) && [21, 16].includes(base)) return "Imamah"; // Nigerian, Turkish
if (P(.8) && [18, 17, 28].includes(base)) return "Caliphate"; // Arabic, Berber, Swahili
- if (P(.02)) return "Thearchy"; // "Thearchy" in very rare case
- if (P(.05)) return "See"; // "See" in rare case
- return "Theocracy";
+ return rw(theocracy);
}
}
- console.timeEnd("defineStateForms");
+ TIME && console.timeEnd("defineStateForms");
}
+ // state forms requiring Adjective + Name, all other forms use scheme Form + Of + Name
+ const adjForms = ["Empire", "Sultanate", "Khaganate", "Shogunate", "Caliphate", "Despotate", "Theocracy", "Oligarchy", "Union", "Confederation", "Trade Company", "League", "Tetrarchy", "Triumvirate", "Diarchy", "Horde", "Marches"];
+
const getFullName = function(s) {
if (!s.formName) return s.name;
if (!s.name && s.formName) return "The " + s.formName;
- // state forms requiring Adjective + Name, all other forms use scheme Form + Of + Name
- const adj = ["Empire", "Sultanate", "Khaganate", "Shogunate", "Caliphate", "Despotate", "Theocracy", "Oligarchy", "Union", "Confederation", "Trade Company", "League", "Tetrarchy", "Triumvirate", "Diarchy", "Horde"];
- return adj.includes(s.formName) ? getAdjective(s.name) + " " + s.formName : s.formName + " of " + s.name;
+ const adjName = adjForms.includes(s.formName) && !(/-| /).test(s.name);
+ return adjName ? `${getAdjective(s.name)} ${s.formName}` : `${s.formName} of ${s.name}`;
}
const generateProvinces = function(regenerate) {
- console.time("generateProvinces");
+ TIME && console.time("generateProvinces");
const localSeed = regenerate ? Math.floor(Math.random() * 1e9).toString() : seed;
- Math.seedrandom(localSeed);
+ Math.random = aleaPRNG(localSeed);
const cells = pack.cells, states = pack.states, burgs = pack.burgs;
const provinces = pack.provinces = [0];
@@ -875,15 +921,14 @@
const forms = {
Monarchy:{County:11, Earldom:3, Shire:1, Landgrave:1, Margrave:1, Barony:1},
- Republic:{Province:6, Department:2, Governorate:2, State:1, Canton:1, Prefecture:1},
- Theocracy:{Parish:5, Deanery:3, Province:2, Council:1, District:1},
- Union:{Province:2, State:1, Canton:1, Republic:1, County:1},
- Wild:{Territory:10, Land:5, Province:2, Region:2, Tribe:1, Clan:1},
- Horde:{Horde:1}
+ Republic:{Province:6, Department:2, Governorate:2, District:1, Canton:1, Prefecture:1},
+ Theocracy:{Parish:3, Deanery:1},
+ Union:{Province:1, State:1, Canton:1, Republic:1, County:1, Council:1},
+ Wild:{Territory:10, Land:5, Region:2, Tribe:1, Clan:1, Dependency:1, Area: 1}
}
// generate provinces for a selected burgs
- Math.seedrandom(localSeed);
+ Math.random = aleaPRNG(localSeed);
states.forEach(s => {
s.provinces = [];
if (!s.i || s.removed) return;
@@ -900,12 +945,17 @@
const center = stateBurgs[i].cell;
const burg = stateBurgs[i].i;
const c = stateBurgs[i].culture;
- const name = P(.5) ? Names.getState(Names.getCultureShort(c), c) : stateBurgs[i].name;
+ const nameByBurg = P(.5);
+ const name = nameByBurg ? stateBurgs[i].name : Names.getState(Names.getCultureShort(c), c);
const formName = rw(form);
form[formName] += 5;
const fullName = name + " " + formName;
const color = getMixedColor(s.color);
- provinces.push({i:province, state:s.i, center, burg, name, formName, fullName, color});
+ const kinship = nameByBurg ? .8 : .4;
+ const type = getType(center, burg.port);
+ const coa = COA.generate(stateBurgs[i].coa, kinship, null, type);
+ coa.shield = COA.getShield(c, s.i);
+ provinces.push({i:province, state:s.i, center, burg, name, formName, fullName, color, coa});
}
});
@@ -917,7 +967,6 @@
cells.province[p.center] = p.i;
queue.queue({e:p.center, p:0, province:p.i, state:p.state});
cost[p.center] = 1;
- //debug.append("circle").attr("cx", cells.p[p.center][0]).attr("cy", cells.p[p.center][1]).attr("r", .3).attr("fill", "red");
});
while (queue.length) {
@@ -989,7 +1038,8 @@
// generate "wild" province name
const c = cells.culture[center];
- const name = burgCell && P(.5) ? burgs[burg].name : Names.getState(Names.getCultureShort(c), c);
+ const nameByBurg = burgCell && P(.5);
+ const name = nameByBurg ? burgs[burg].name : Names.getState(Names.getCultureShort(c), c);
const f = pack.features[cells.f[center]];
const provCells = stateNoProvince.filter(i => cells.province[i] === province);
const singleIsle = provCells.length === f.cells && !provCells.find(i => cells.f[i] !== f.i);
@@ -998,7 +1048,12 @@
const formName = singleIsle ? "Island" : isleGroup ? "Islands" : colony ? "Colony" : rw(forms["Wild"]);
const fullName = name + " " + formName;
const color = getMixedColor(s.color);
- provinces.push({i:province, state:s.i, center, burg, name, formName, fullName, color});
+ const dominion = colony ? P(.95) : singleIsle || isleGroup ? P(.7) : P(.3);
+ const kinship = dominion ? 0 : .4;
+ const type = getType(center, burgs[burg]?.port);
+ const coa = COA.generate(s.coa, kinship, dominion, type);
+ coa.shield = COA.getShield(c, s.i);
+ provinces.push({i:province, state:s.i, center, burg, name, formName, fullName, color, coa});
s.provinces.push(province);
// check if there is a land way within the same state between two cells
@@ -1022,11 +1077,11 @@
}
});
- console.timeEnd("generateProvinces");
+ TIME && console.timeEnd("generateProvinces");
}
return {generate, expandStates, normalizeStates, assignColors,
- drawBurgs, specifyBurgs, defineBurgFeatures, drawStateLabels, collectStatistics,
- generateCampaigns, generateDiplomacy, defineStateForms, getFullName, generateProvinces};
+ drawBurgs, specifyBurgs, defineBurgFeatures, getType, drawStateLabels, collectStatistics,
+ generateCampaigns, generateDiplomacy, defineStateForms, getFullName, generateProvinces, updateCultures};
})));
diff --git a/modules/coa-generator.js b/modules/coa-generator.js
new file mode 100644
index 00000000..e2cf6493
--- /dev/null
+++ b/modules/coa-generator.js
@@ -0,0 +1,479 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.COA = factory());
+}(this, (function () {'use strict';
+
+ const tinctures = {
+ field: { metals: 3, colours: 4, stains: +P(.03), patterns: 1 },
+ division: { metals: 5, colours: 8, stains: +P(.03), patterns: 1 },
+ charge: { metals: 2, colours: 3, stains: +P(.05), patterns: 0 },
+ metals: { argent: 3, or: 2 },
+ colours: { gules: 5, azure: 4, sable: 3, purpure: 3, vert: 2 },
+ stains: { murrey: 1, sanguine: 1, tenné: 1 },
+ patterns: { semy: 1, vair: 2, vairInPale: 1, vairEnPointe: 2, ermine: 2, chequy: 5, lozengy: 2, fusily: 1, pally: 4, barry: 4, gemelles: 1, bendy: 3, bendySinister: 2, palyBendy: 1, pappellony: 2, masoned: 3, fretty: 2 }
+ }
+
+ const charges = {
+ // categories selection
+ types: { conventional: 30, crosses: 8, animals: 2, animalHeads: 1, birds: 2, fantastic: 3, plants: 1, agriculture: 1, arms: 3, bodyparts: 1, people: 1, architecture: 1, miscellaneous: 3, inescutcheon: 3 },
+ single: { conventional: 12, crosses: 8, plants: 2, animals: 10, animalHeads: 2, birds: 4, fantastic: 7, agriculture: 1, arms: 6, bodyparts: 1, people: 1, architecture: 1, miscellaneous: 9, inescutcheon: 5 },
+ semy: { conventional: 12, crosses: 3, plants: 1 },
+ // generic categories
+ conventional: {
+ lozenge: 2, fusil: 4, mascle: 4, rustre: 2, lozengeFaceted: 3, lozengePloye: 1, roundel: 4, roundel2: 3, annulet: 4,
+ mullet: 5, mulletPierced: 1, mulletFaceted: 1, mullet4: 3, mullet6: 4, mullet6Pierced: 1, mullet6Faceted: 1, mullet7: 1, mullet8: 1, mullet10: 1,
+ estoile: 1, compassRose: 1, billet: 5, delf: 0, triangle: 3, trianglePierced: 1, goutte: 4, heart: 4, pique: 2, сarreau: 1, trefle: 2,
+ fleurDeLis: 6, sun: 3, sunInSplendour: 1, crescent: 5, fountain: 1
+ },
+ crosses: {
+ crossHummetty: 15, crossVoided: 1, crossPattee: 3, crossPotent: 2, crossClechy: 3, crosslet: 1, crossBottony: 1, crossFleury: 3,
+ crossPatonce: 1, crossPommy: 1, crossGamma: 1, crossArrowed: 1, crossFitchy: 1, crossCercelee: 1, crossMoline: 2, crossFourchy: 1,
+ crossAvellane: 1, crossErminee: 1, crossMaltese: 3, crossCeltic: 1, crossOccitan: 1, crossSaltire: 3, crossTau: 1
+ },
+ animals: {
+ lionRampant: 5, lionPassant: 2, wolfRampant: 1, wolfPassant: 1, wolfStatant: 1, greyhoundCourant: 1, boarRampant: 1,
+ horseRampant: 2, horseSalient: 1, bearRampant: 2, bearPassant: 1, bullPassant: 1, goat: 1, lamb: 1, elephant: 1
+ },
+ animalHeads: { wolfHeadErased: 1, bullHeadCaboshed: 1, deerHeadCaboshed: 1 },
+ fantastic: { dragonPassant: 2, dragonRampant: 2, wyvern: 1, wyvernWithWingsDisplayed: 1, griffinPassant: 1, griffinRampant: 1, eagleTwoHeards: 2, unicornRampant: 1, pegasus: 1, serpent: 1 },
+ birds: { eagle: 9, raven: 1, cock: 3, parrot: 1, swan: 2, swanErased: 1, heron: 1 },
+ plants: { tree: 1, cinquefoil: 1, rose: 1 },
+ agriculture: { garb: 1, rake: 1 },
+ arms: { sword: 5, sabre: 1, sabresCrossed: 1, hatchet: 2, lochaberAxe: 1, mallet: 1, bowWithArrow: 2, bow: 1, arrow: 1, arrowsSheaf: 1 },
+ bodyparts: { hand: 4, head: 1, headWreathed: 1 },
+ people: { cavalier: 1 },
+ architecture: { tower: 1, castle: 1 },
+ miscellaneous: {
+ crown: 3, orb: 1, chalice: 1, key: 1, buckle: 1, bugleHorn: 1, bell: 1, pot: 1, horseshoe: 3, stagsAttires: 1, cowHorns: 2, wing: 1, wingSword: 1,
+ lute: 1, harp: 1, wheel: 2, crosier: 1, log: 1},
+ // selection based on culture type:
+ Naval: { anchor: 3, boat: 1, lymphad: 2, armillarySphere: 1, escallop: 1, dolphin: 1 },
+ Highland: { tower: 1, raven: 1, wolfHeadErased: 1, wolfPassant: 1, goat: 1,},
+ River: { tower: 1, garb: 1, rake: 1, boat: 1, pike: 2, bullHeadCaboshed: 1 },
+ Lake: { cancer: 2, escallop: 1, pike: 2, heron: 1, boat: 1 },
+ Nomadic: { pot: 1, buckle: 1, wheel: 2, sabre: 2, sabresCrossed: 1, bow: 2, arrow: 1, horseRampant: 1, horseSalient: 1, crescent: 1 },
+ Hunting: { bugleHorn: 3, stagsAttires: 2, hatchet: 1, bowWithArrow: 2, arrowsSheaf: 1, deerHeadCaboshed: 1, wolfStatant: 1 },
+ // selection based on type
+ City: { key: 3, bell: 2, lute: 1, tower: 1, castle: 1, mallet: 1 },
+ Capital: { crown: 4, orb: 1, lute: 1, castle: 3, tower: 1 },
+ Сathedra: { chalice: 1, orb: 1, crosier: 3, lamb: 1 },
+ // specific cases
+ natural: { fountain: "azure", garb: "or", raven: "sable" }, // charges to mainly use predefined colours
+ sinister: ["crossGamma", "lionRampant", "lionPassant", "wolfRampant", "wolfPassant", "wolfStatant", "wolfHeadErased", "greyhoundСourant", "boarRampant", "horseRampant", "horseSalient", "bullPassant",
+ "bearRampant", "bearPassant", "goat", "lamb", "elephant",
+ "eagle", "raven", "cock", "parrot", "swan", "swanErased", "heron", "pike", "dragonPassant", "dragonRampant", "wyvern", "wyvernWithWingsDisplayed", "griffinPassant", "griffinRampant", "unicornRampant",
+ "pegasus", "serpent", "hatchet", "lochaberAxe", "hand", "wing", "wingSword", "lute", "harp", "bow", "head", "headWreathed", "knight", "lymphad", "log",
+ "crosier", "dolphin", "sabre"], // charges that can be sinister
+ reversed: ["goutte", "mullet", "mullet7", "crescent", "crossTau", "cancer", "sword", "sabresCrossed", "hand", "horseshoe", "bowWithArrow", "arrow", "arrowsSheaf", "rake"] // charges that can be reversed
+ }
+
+ const positions = {
+ conventional: { e: 20, abcdefgzi: 3, beh: 3, behdf: 2, acegi: 1, kn: 3, bhdf: 1, jeo: 1, abc: 3, jln: 6, jlh: 3, kmo: 2, jleh: 1, def: 3, abcpqh: 4, ABCDEFGHIJKL: 1 },
+ complex: { e: 40, beh: 1, kn: 1, jeo: 1, abc: 2, jln: 7, jlh: 2, def: 1, abcpqh: 1 },
+ divisions: {
+ perPale: { e: 15, pq: 5, jo: 2, jl: 2, ABCDEFGHIJKL: 1 },
+ perFess: { e: 12, kn: 4, jkl: 2, gizgiz: 1, jlh: 3, kmo: 1, ABCDEFGHIJKL: 1 },
+ perBend: { e: 5, lm: 5, bcfdgh: 1 },
+ perBendSinister: { e: 1, jo: 1 },
+ perCross: { e: 4, jlmo: 1, j: 1, jo: 2, jl: 1 },
+ perChevron: { e: 1, jlh: 1, dfk: 1, dfbh: 2, bdefh: 1 },
+ perChevronReversed: { e: 1, mok: 2, dfh: 2, dfbh: 1, bdefh: 1 },
+ perSaltire: { bhdf: 8, e: 3, abcdefgzi: 1, bh: 1, df: 1, ABCDEFGHIJKL: 1 },
+ perPile: { ee: 3, be: 2, abceh: 1, abcabc: 1, jleh: 1 }
+ },
+ ordinariesOn: {
+ pale: { ee: 12, beh: 10, kn: 3, bb: 1 },
+ fess: { ee: 1, def: 3 },
+ bar: { defdefdef: 1 },
+ fessCotissed: { ee: 1, def: 3 },
+ fessDoubleCotissed: { ee: 1, defdef: 3 },
+ bend: { ee: 2, jo: 1, joe: 1 },
+ bendSinister: { ee: 1, lm: 1, lem: 4 },
+ bendlet: { joejoejoe: 1 },
+ bendletSinister: { lemlemlem: 1 },
+ bordure: { ABCDEFGHIJKL: 1 },
+ chief: { abc: 5, bbb: 1 },
+ quarter: { jjj: 1 },
+ canton: { yyyy: 1 },
+ cross: { eeee: 1, behdfbehdf: 3, behbehbeh: 2 },
+ crossParted: { e: 5, ee: 1 },
+ saltire: { ee: 5, jlemo: 1 },
+ saltireParted: { e: 5, ee: 1 },
+ pall: { ee: 1, jleh: 5, jlhh: 3 },
+ pallReversed: { ee: 1, bemo: 5 },
+ pile: { bbb: 1 },
+ pileInBend: { eeee: 1, eeoo: 1 },
+ pileInBendSinister: { eeee: 1, eemm: 1 }
+ },
+ ordinariesOff: {
+ pale: { yyy: 1 },
+ fess: { abc: 3, abcz: 1 },
+ bar: { abc: 2, abcgzi: 1, jlh: 5, bgi: 2, ach: 1 },
+ gemelle: { abc: 1 },
+ bend: { ccg: 2, ccc: 1 },
+ bendSinister: { aai: 2, aaa: 1 },
+ bendlet: { ccg: 2, ccc: 1 },
+ bendletSinister: { aai: 2, aaa: 1 },
+ bordure: { e: 4, jleh:2, kenken: 1, peqpeq: 1 },
+ orle: { e: 4, jleh: 1, kenken: 1, peqpeq: 1 },
+ chief: { emo: 2, emoz: 1, ez: 2 },
+ terrace: { e: 5, def: 1, bdf: 3 },
+ mount: { e: 5, def: 1, bdf: 3 },
+ point: { e: 2, def: 1, bdf: 3, acbdef: 1 },
+ flaunches: { e: 3, kn: 1, beh: 3 },
+ gyron: { bh: 1 },
+ quarter: { e: 1 },
+ canton: { e: 5, beh: 1, def: 1, bdefh: 1, kn: 1 },
+ cross: { acgi: 1 },
+ pall: { BCKFEILGJbdmfo: 1 },
+ pallReversed: { aczac: 1 },
+ chevron: { ach: 3, hhh: 1 },
+ chevronReversed: { bbb: 1 },
+ pile: { acdfgi: 1, acac: 1 },
+ pileInBend: { cg: 1 },
+ pileInBendSinister: { ai: 1 },
+ label: { defgzi: 2, eh: 3, defdefhmo: 1, egiegi: 1, pqn: 5 }
+ },
+ // charges
+ inescutcheon: { e: 4, jln: 1 },
+ mascle: { e: 15, abcdefgzi: 3, beh: 3, bdefh: 4, acegi: 1, kn: 3, joe: 2, abc: 3, jlh: 8, jleh: 1, df: 3, abcpqh: 4, pqe: 3, eknpq: 3 },
+ lionRampant: { e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1 },
+ lionPassant: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
+ wolfPassant: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
+ greyhoundСourant: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
+ griffinRampant: { e: 10, def: 2, abc: 2, bdefh: 1, kn: 1, jlh: 2, abcpqh: 1 },
+ griffinPassant: { e: 10, def: 1, abc: 1, bdefh: 1, jlh: 1, abcpqh: 1 },
+ boarRampant: { e: 12, beh: 1, kn: 1, jln: 2 },
+ eagle: { e: 15, beh: 1, kn: 1, abc: 1, jlh: 2, def: 2, pq: 1 },
+ raven: { e: 15, beh: 1, kn: 1, jeo: 1, abc: 3, jln: 3, def: 1 },
+ wyvern: { e: 10, jln: 1 },
+ garb: { e: 1, def: 3, abc: 2, beh: 1, kn: 1, jln: 3, jleh: 1, abcpqh: 1, joe: 1, lme: 1 },
+ crown: { e: 10, abcdefgzi: 1, beh: 3, behdf: 2, acegi: 1, kn: 1, pq: 2, abc: 1, jln: 4, jleh: 1, def: 2, abcpqh: 3 },
+ hand: { e: 10, jln: 2, kn: 1, jeo: 1, abc: 2, pqe: 1 },
+ armillarySphere: {e: 1},
+ tree: {e: 1},
+ lymphad: {e: 1},
+ head: {e: 1},
+ headWreathed: {e: 1}
+ };
+
+ const lines = {
+ straight: 50, wavy: 8, engrailed: 4, invecked: 3, rayonne: 3, embattled: 1, raguly: 1, urdy: 1, dancetty: 1, indented: 2,
+ dentilly: 1, bevilled: 1, angled: 1, flechy: 1, barby: 1, enclavy: 1, escartely: 1, arched: 2, archedReversed: 1, nowy: 1, nowyReversed: 1,
+ embattledGhibellin: 1, embattledNotched: 1, embattledGrady: 1, dovetailedIndented: 1, dovetailed: 1,
+ potenty: 1, potentyDexter: 1, potentySinister: 1, nebuly: 2, seaWaves: 1, dragonTeeth: 1, firTrees: 1
+ };
+
+ const divisions = {
+ variants: { perPale: 5, perFess: 5, perBend: 2, perBendSinister: 1, perChevron: 1, perChevronReversed: 1, perCross: 5, perPile: 1, perSaltire: 1, gyronny: 1, chevronny: 1 },
+ perPale: lines,
+ perFess: lines,
+ perBend: lines,
+ perBendSinister: lines,
+ perChevron: lines,
+ perChevronReversed: lines,
+ perCross: { straight: 20, wavy: 5, engrailed: 4, invecked: 3, rayonne: 1, embattled: 1, raguly: 1, urdy: 1, indented: 2, dentilly: 1, bevilled: 1, angled: 1, embattledGhibellin: 1, embattledGrady: 1, dovetailedIndented: 1, dovetailed: 1, potenty: 1, potentyDexter: 1, potentySinister: 1, nebuly: 1 },
+ perPile: lines
+ };
+
+ const ordinaries = {
+ lined: {
+ pale: 7, fess: 5, bend: 3, bendSinister: 2, chief: 5, bar: 2, gemelle: 1, fessCotissed: 1, fessDoubleCotissed: 1,
+ bendlet: 2, bendletSinister: 1, terrace: 3, cross: 6, crossParted: 1, saltire: 2, saltireParted: 1
+ },
+ straight: {
+ bordure: 8, orle: 4, mount: 1, point: 2, flaunches: 1, gore: 1,
+ gyron: 1, quarter: 1, canton: 2, pall: 3, pallReversed: 2, chevron: 4, chevronReversed: 3,
+ pile: 2, pileInBend: 2, pileInBendSinister: 1, piles: 1, pilesInPoint: 2, label: 1
+ }
+ };
+
+ const generate = function(parent, kinship, dominion, type) {
+ if (parent === "custom") parent = null;
+ let usedPattern = null, usedTinctures = [];
+
+ const t1 = P(kinship) ? parent.t1 : getTincture("field");
+ if (t1.includes("-")) usedPattern = t1;
+ const coa = {t1};
+
+ let charge = P(usedPattern ? .5 : .93) ? true : false; // 80% for charge
+ const linedOrdinary = charge && P(.3) || P(.5) ? parent?.ordinaries && P(kinship) ? parent.ordinaries[0].ordinary : rw(ordinaries.lined) : null;
+ const ordinary = !charge && P(.65) || P(.3) ? linedOrdinary ? linedOrdinary : rw(ordinaries.straight) : null; // 36% for ordinary
+ const rareDivided = ["chief", "terrace", "chevron", "quarter", "flaunches"].includes(ordinary);
+ const divisioned = rareDivided ? P(.03) : charge && ordinary ? P(.03) : charge ? P(.3) : ordinary ? P(.7) : P(.995); // 33% for division
+ const division = divisioned ? parent?.division && P(kinship - .1) ? parent.division.division : rw(divisions.variants) : null;
+ if (charge) charge =
+ parent?.charges && P(kinship - .1) ? parent.charges[0].charge :
+ type && type !== "Generic" && P(.2) ? rw(charges[type]) :
+ selectCharge();
+
+ if (division) {
+ const t = getTincture("division", usedTinctures, P(.98) ? coa.t1 : null);
+ coa.division = {division, t};
+ if (divisions[division]) coa.division.line = usedPattern || (ordinary && P(.7)) ? "straight" : rw(divisions[division]);
+ }
+
+ if (ordinary) {
+ coa.ordinaries = [{ordinary, t: getTincture("charge", usedTinctures, coa.t1)}];
+ if (linedOrdinary) coa.ordinaries[0].line = usedPattern || (division && P(.7)) ? "straight" : rw(lines);
+ if (division && !charge && !usedPattern && P(.5) && ordinary !== "bordure" && ordinary !== "orle") {
+ if (P(.8)) coa.ordinaries[0].divided = "counter"; // 40%
+ else if (P(.6)) coa.ordinaries[0].divided = "field"; // 6%
+ else coa.ordinaries[0].divided = "division"; // 4%
+ }
+ }
+
+ if (charge) {
+ let p = "e", t = "gules";
+ const ordinaryT = coa.ordinaries ? coa.ordinaries[0].t : null;
+ if (positions.ordinariesOn[ordinary] && P(.8)) {
+ // place charge over ordinary (use tincture of field type)
+ p = rw(positions.ordinariesOn[ordinary]);
+ while (charges.natural[charge] === ordinaryT) charge = selectCharge();
+ t = !usedPattern && P(.3) ? coa.t1 : getTincture("charge", [], ordinaryT);
+ } else if (positions.ordinariesOff[ordinary] && P(.95)) {
+ // place charge out of ordinary (use tincture of ordinary type)
+ p = rw(positions.ordinariesOff[ordinary]);
+ while (charges.natural[charge] === coa.t1) charge = selectCharge();
+ t = !usedPattern && P(.3) ? ordinaryT : getTincture("charge", usedTinctures, coa.t1);
+ } else if (positions.divisions[division]) {
+ // place charge in fields made by division
+ p = rw(positions.divisions[division]);
+ while (charges.natural[charge] === coa.t1) charge = selectCharge();
+ t = getTincture("charge", ordinaryT ? usedTinctures.concat(ordinaryT) : usedTinctures, coa.t1);
+ } else if (positions[charge]) {
+ // place charge-suitable position
+ p = rw(positions[charge]);
+ while (charges.natural[charge] === coa.t1) charge = selectCharge();
+ t = getTincture("charge", usedTinctures, coa.t1);
+ } else {
+ // place in standard position (use new tincture)
+ p = usedPattern ? "e" : charges.conventional[charge] ? rw(positions.conventional) : rw(positions.complex);
+ while (charges.natural[charge] === coa.t1) charge = selectCharge();
+ t = getTincture("charge", usedTinctures.concat(ordinaryT), coa.t1);
+ }
+
+ if (charges.natural[charge]) t = charges.natural[charge]; // natural tincture
+ coa.charges = [{charge, t, p}];
+
+ if (p === "ABCDEFGHIKL" && P(.95)) {
+ // add central charge if charge is in bordure
+ coa.charges[0].charge = rw(charges.conventional);
+ const charge = selectCharge(charges.single);
+ const t = getTincture("charge", usedTinctures, coa.t1);
+ coa.charges.push({charge, t, p: "e"});
+ } else if (P(.8) && charge === "inescutcheon") {
+ // add charge to inescutcheon
+ const charge = selectCharge(charges.types);
+ const t2 = getTincture("charge", [], t);
+ coa.charges.push({charge, t: t2, p, size:.5});
+ } else if (division && !ordinary) {
+ const allowCounter = !usedPattern && (!coa.line || coa.line === "straight");
+
+ // dimidiation: second charge at division basic positons
+ if (P(.3) && ["perPale", "perFess"].includes(division) && coa.line === "straight") {
+ coa.charges[0].divided = "field";
+ if (P(.95)) {
+ const p2 = p === "e" || P(.5) ? "e" : rw(positions.divisions[division]);
+ const charge = selectCharge(charges.single);
+ const t = getTincture("charge", usedTinctures, coa.division.t);
+ coa.charges.push({charge, t, p: p2, divided: "division"});
+ }
+ }
+ else if (allowCounter && P(.4)) coa.charges[0].divided = "counter"; // counterchanged, 40%
+ else if (["perPale", "perFess", "perBend", "perBendSinister"].includes(division) && P(.8)) { // place 2 charges in division standard positions
+ const [p1, p2] = division === "perPale" ? ["p", "q"] :
+ division === "perFess" ? ["k", "n"] :
+ division === "perBend" ? ["l", "m"] :
+ ["j", "o"]; // perBendSinister
+ coa.charges[0].p = p1;
+
+ const charge = selectCharge(charges.single);
+ const t = getTincture("charge", usedTinctures, coa.division.t);
+ coa.charges.push({charge, t, p: p2});
+ }
+ else if (["perCross", "perSaltire"].includes(division) && P(.5)) { // place 4 charges in division standard positions
+ const [p1, p2, p3, p4] = division === "perCross" ? ["j", "l", "m", "o"] : ["b", "d", "f", "h"];
+ coa.charges[0].p = p1;
+
+ const c2 = selectCharge(charges.single);
+ const t2 = getTincture("charge", [], coa.division.t);
+
+ const c3 = selectCharge(charges.single);
+ const t3 = getTincture("charge", [], coa.division.t);
+
+ const c4 = selectCharge(charges.single);
+ const t4 = getTincture("charge", [], coa.t1);
+ coa.charges.push({charge: c2, t: t2, p: p2}, {charge: c3, t: t3, p: p3}, {charge: c4, t: t4, p: p4});
+ }
+ else if (allowCounter && p.length > 1) coa.charges[0].divided = "counter"; // counterchanged, 40%
+ }
+
+ coa.charges.forEach(c => defineChargeAttributes(c));
+ function defineChargeAttributes(c) {
+ // define size
+ c.size = (c.size || 1) * getSize(c.p, ordinary, division);
+
+ // clean-up position
+ c.p = [...new Set(c.p)].join("");
+
+ // define orientation
+ if (P(.02) && charges.sinister.includes(c.charge)) c.sinister = 1;
+ if (P(.02) && charges.reversed.includes(c.charge)) c.reversed = 1;
+ }
+ }
+
+ // dominions have canton with parent coa
+ if (P(dominion) && parent.charges) {
+ const invert = isSameType(parent.t1, coa.t1);
+ const t = invert ? getTincture("division", usedTinctures, coa.t1) : parent.t1;
+ const canton = {ordinary: "canton", t};
+ if (coa.charges) {
+ coa.charges.forEach((charge, i) => {
+ if (charge.size === 1.5) charge.size = 1.4;
+ if (charge.p.includes("a")) charge.p = charge.p.replaceAll("a", "");
+ if (charge.p.includes("j")) charge.p = charge.p.replaceAll("j", "");
+ if (charge.p.includes("y")) charge.p = charge.p.replaceAll("y", "");
+ if (!charge.p) coa.charges.splice(i, 1);
+ });
+ }
+
+ let charge = parent.charges[0].charge;
+ if (charge === "inescutcheon" && parent.charges[1]) charge = parent.charges[1].charge;
+
+ let t2 = invert ? parent.t1 : parent.charges[0].t;
+ if (isSameType(t, t2)) t2 = getTincture("charge", usedTinctures, t);
+
+ if (!coa.charges) coa.charges = [];
+ coa.charges.push({charge, t: t2, p: "y", size: 0.5});
+
+ coa.ordinaries ? coa.ordinaries.push(canton) : coa.ordinaries = [canton];
+ }
+
+ function selectCharge(set) {
+ const type = set ? rw(set) : ordinary || divisioned ? rw(charges.types): rw(charges.single);
+ return type === "inescutcheon" ? "inescutcheon" : rw(charges[type]);
+ }
+
+ // select tincture: element type (field, division, charge), used field tinctures, field type to follow RoT
+ function getTincture(element, fields = [], RoT) {
+ const base = RoT ? RoT.includes("-") ? RoT.split("-")[1] : RoT : null;
+
+ let type = rw(tinctures[element]); // metals, colours, stains, patterns
+ if (RoT && type !== "patterns") type = getType(base) === "metals" ? "colours" : "metals"; // follow RoT
+ if (type === "metals" && fields.includes("or") && fields.includes("argent")) type = "colours"; // exclude metals overuse
+ let tincture = rw(tinctures[type]);
+
+ while (tincture === base || fields.includes(tincture)) {tincture = rw(tinctures[type]);} // follow RoT
+
+ if (type !== "patterns" && element !== "charge") usedTinctures.push(tincture); // add field tincture
+
+ if (type === "patterns") {
+ usedPattern = tincture;
+ tincture = definePattern(tincture, element);
+ }
+
+ return tincture;
+ }
+
+ function getType(t) {
+ const tincture = t.includes("-") ? t.split("-")[1] : t;
+ if (Object.keys(tinctures.metals).includes(tincture)) return "metals";
+ if (Object.keys(tinctures.colours).includes(tincture)) return "colours";
+ if (Object.keys(tinctures.stains).includes(tincture)) return "stains";
+ }
+
+ function isSameType(t1, t2) {
+ return type(t1) === type(t2);
+
+ function type(tincture) {
+ if (Object.keys(tinctures.metals).includes(tincture)) return "metals";
+ if (Object.keys(tinctures.colours).includes(tincture)) return "colours";
+ if (Object.keys(tinctures.stains).includes(tincture)) return "stains";
+ else return "pattern";
+ }
+
+ }
+
+ function definePattern(pattern, element, size = "") {
+ let t1 = null, t2 = null;
+ if (P(.15)) size = "-small";
+ else if (P(.05)) size = "-smaller";
+ else if (P(.035)) size = "-big";
+ else if (P(.001)) size = "-smallest";
+
+ // apply standard tinctures
+ if (P(.5) && ["vair", "vairInPale", "vairEnPointe"].includes(pattern)) {t1 = "azure"; t2 = "argent";}
+ else if (P(.8) && pattern === "ermine") {t1 = "argent"; t2 = "sable";}
+ else if (pattern === "pappellony") {
+ if (P(.2)) {t1 = "gules"; t2 = "or";}
+ else if (P(.2)) {t1 = "argent"; t2 = "sable";}
+ else if (P(.2)) {t1 = "azure"; t2 = "argent";}
+ }
+ else if (pattern === "masoned") {
+ if (P(.3)) {t1 = "gules"; t2 = "argent";}
+ else if (P(.3)) {t1 = "argent"; t2 = "sable";}
+ else if (P(.1)) {t1 = "or"; t2 = "sable";}
+ }
+ else if (pattern === "fretty") {
+ if (t2 === "sable" || P(.35)) {t1 = "argent"; t2 = "gules";}
+ else if (P(.25)) {t1 = "sable"; t2 = "or";}
+ else if (P(.15)) {t1 = "gules"; t2 = "argent";}
+ }
+ else if (pattern === "semy") pattern += "_of_" + selectCharge(charges.semy);
+
+
+ if (!t1 || !t2) {
+ const startWithMetal = P(.7);
+ t1 = startWithMetal ? rw(tinctures.metals) : rw(tinctures.colours);
+ t2 = startWithMetal ? rw(tinctures.colours) : rw(tinctures.metals);
+ }
+
+ // division should not be the same tincture as base field
+ if (element === "division") {
+ if (usedTinctures.includes(t1)) t1 = replaceTincture(t1);
+ if (usedTinctures.includes(t2)) t2 = replaceTincture(t2);
+ }
+
+ usedTinctures.push(t1, t2);
+ return `${pattern}-${t1}-${t2}${size}`;
+ }
+
+ function replaceTincture(t, n) {
+ const type = getType(t);
+ while (!n || n === t) {n = rw(tinctures[type]);}
+ return n;
+ }
+
+ function getSize(p, o = null, d = null) {
+ if (p === "e" && (o === "bordure" || o === "orle")) return 1.1;
+ if (p === "e") return 1.5;
+ if (p === "jln" || p === "jlh") return .7;
+ if (p === "abcpqh" || p === "ez" || p === "be") return .5;
+ if (["a", "b", "c", "d", "f", "g", "h", "i", "bh", "df"].includes(p)) return .5;
+ if (["j", "l", "m", "o", "jlmo"].includes(p) && d === "perCross") return .6;
+ if (p.length > 10) return .18; // >10 (bordure)
+ if (p.length > 7) return .3; // 8, 9, 10
+ if (p.length > 4) return .4; // 5, 6, 7
+ if (p.length > 2) return .5; // 3, 4
+ return .7; // 1, 2
+ }
+
+ return coa;
+ }
+
+ const getShield = function(culture, state) {
+ const emblemShape = document.getElementById("emblemShape").value;
+ if (emblemShape === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield;
+ if (pack.cultures[culture].shield) return pack.cultures[culture].shield;
+ console.error("Emblem shape is not defined on culture level", pack.cultures[culture]);
+ return "heater";
+ }
+
+ const toString = coa => JSON.stringify(coa).replaceAll("#", "%23");
+ const copy = coa => JSON.parse(JSON.stringify(coa));
+
+ return {generate, toString, copy, getShield};
+
+})));
\ No newline at end of file
diff --git a/modules/coa-renderer.js b/modules/coa-renderer.js
new file mode 100644
index 00000000..e956f322
--- /dev/null
+++ b/modules/coa-renderer.js
@@ -0,0 +1,1056 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.COArenderer = factory());
+}(this, (function () {'use strict';
+
+ const colors = {
+ argent: "#fafafa",
+ or: "#ffe066",
+ gules: "#d7374a",
+ sable: "#333333",
+ azure: "#377cd7",
+ vert: "#26c061",
+ purpure: "#522d5b",
+ murrey: "#85185b",
+ sanguine: "#b63a3a",
+ tenné: "#cc7f19"
+ }
+
+ const shieldPositions = {
+ // shield-specific position: [x, y] (relative to center)
+ heater: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-43.75, 0], e: [0, 0], f: [43.75, 0],
+ g: [-32.25, 37.5], h: [0, 50], i: [32.25, 37.5],
+ y: [-50, -50], z: [0, 62.5],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-30, 30], n: [0, 42.5], o: [30, 30],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66.2, -66.6], B: [-22, -66.6], C: [22, -66.6], D: [66.2, -66.6],
+ K: [-66.2, -20], E: [66.2, -20],
+ J: [-55.5, 26], F: [55.5, 26],
+ I: [-33, 62], G: [33, 62],
+ H: [0, 89.5]
+ },
+ spanish: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-43.75, 0], e: [0, 0], f: [43.75, 0],
+ g: [-43.75, 50], h: [0, 50], i: [43.75, 50],
+ y: [-50, -50], z: [0, 50],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 37.5], o: [37.5, 37.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66.2, -66.6], B: [-22, -66.6], C: [22, -66.6], D: [66.2, -66.6],
+ K: [-66.4, -20], E: [66.4, -20],
+ J: [-66.4, 26], F: [66.4, 26],
+ I: [-49, 70], G: [49, 70],
+ H: [0, 92]
+ },
+ french: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-43.75, 0], e: [0, 0], f: [43.75, 0],
+ g: [-43.75, 50], h: [0, 50], i: [43.75, 50],
+ y: [-50, -50], z: [0, 65],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 37.5], o: [37.5, 37.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66.2, -66.6], B: [-22, -66.6], C: [22, -66.6], D: [66.2, -66.6],
+ K: [-66.4, -20], E: [66.4, -20],
+ J: [-66.4, 26], F: [66.4, 26],
+ I: [-65.4, 70], G: [65.4, 70],
+ H: [0, 89]
+ },
+ horsehead: {
+ a: [-43.75, -47.5], b: [0, -50], c: [43.75, -47.5],
+ d: [-35, 0], e: [0, 0], f: [35, 0],
+ h: [0, 50],
+ y: [-50, -50], z: [0, 55],
+ j: [-35, -35], k: [0, -40], l: [35, -35],
+ m: [-30, 30], n: [0, 40], o: [30, 30],
+ p: [-27.5, 0], q: [27.5, 0],
+ A: [-71, -52], B: [-24, -73], C: [24, -73], D: [71, -52],
+ K: [-62, -16], E: [62, -16],
+ J: [-39, 20], F: [39, 20],
+ I: [-33.5, 60], G: [33.5, 60],
+ H: [0, 91.5]
+ },
+ horsehead2: {
+ a: [-37.5, -47.5], b: [0, -50], c: [37.5, -47.5],
+ d: [-35, 0], e: [0, 0], f: [35, 0],
+ g: [-35, 47.5], h: [0, 50], i: [35, 47.5],
+ y: [-50, -50], z: [0, 55],
+ j: [-30, -30], k: [0, -40], l: [30, -30],
+ m: [-30, 30], n: [0, 40], o: [30, 30],
+ p: [-27.5, 0], q: [27.5, 0],
+ A: [-49, -39], B: [-22, -70], C: [22, -70], D: [49, -39],
+ K: [-51, -2], E: [51, -2],
+ J: [-38.5, 31], F: [38.5, 31],
+ I: [-35, 67], G: [35, 67],
+ H: [0, 85]
+ },
+ polish: {
+ a: [-35, -50], b: [0, -50], c: [35, -50],
+ d: [-40, 0], e: [0, 0], f: [40, 0],
+ g: [-37.5, 50], h: [0, 50], i: [37.5, 50],
+ y: [-50, -50], z: [0, 65],
+ j: [-27.5, -27.5], k: [0, -45], l: [27.5, -27.5],
+ m: [-27.5, 27.5], n: [0, 45], o: [27.5, 27.5],
+ p: [-32.5, 0], q: [32.5, 0],
+ A: [-48, -52], B: [-23, -80], C: [23, -80], D: [48, -52],
+ K: [-47, -10], E: [47, -10],
+ J: [-62, 32], F: [62, 32],
+ I: [-37, 68], G: [37, 68],
+ H: [0, 86]
+ },
+ hessen: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-43.75, 0], e: [0, 0], f: [43.75, 0],
+ g: [-43.75, 50], h: [0, 50], i: [43.75, 50],
+ y: [-50, -50], z: [0, 52.5],
+ j: [-40, -40], k: [0, -40], l: [40, -40],
+ m: [-40, 40], n: [0, 40], o: [40, 40],
+ p: [-40, 0], q: [40, 0],
+ A: [-69, -64], B: [-22, -76], C: [22, -76], D: [69, -64],
+ K: [-66.4, -20], E: [66.4, -20],
+ J: [-62, 26], F: [62, 26],
+ I: [-46, 70], G: [46, 70],
+ H: [0, 91.5]
+ },
+ swiss: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-43.75, 0], e: [0, 0], f: [43.75, 0],
+ g: [-32, 37.5], h: [0, 50], i: [32, 37.5],
+ y: [-50, -50], z: [0, 62.5],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-32, 32.5], n: [0, 42.5], o: [32, 32.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66.2, -66.6], B: [-22, -66], C: [22, -66], D: [66.2, -66.6],
+ K: [-63, -20], E: [63, -20],
+ J: [-50, 26], F: [50, 26],
+ I: [-29, 62], G: [29, 62],
+ H: [0, 89.5]
+ },
+ boeotian: {
+ a: [-37.5, -47.5], b: [0, -47.5], c: [37.5, -47.5],
+ d: [-25, 0], e: [0, 0], f: [25, 0],
+ g: [-37.5, 47.5], h: [0, 47.5], i: [37.5, 47.5],
+ y: [-48, -48], z: [0, 60],
+ j: [-32.5, -37.5], k: [0, -45], l: [32.5, -37.5],
+ m: [-32.5, 37.5], n: [0, 45], o: [32.5, 37.5],
+ p: [-20, 0], q: [20, 0],
+ A: [-45, -55], B: [-20, -77], C: [20, -77], D: [45, -55],
+ K: [-59, -25], E: [59, -25],
+ J: [-58, 27], F: [58, 27],
+ I: [-39, 63], G: [39, 63],
+ H: [0, 81]
+ },
+ roman: {
+ a: [-40, -52.5], b: [0, -52.5], c: [40, -52.5],
+ d: [-40, 0], e: [0, 0], f: [40, 0],
+ g: [-40, 52.5], h: [0, 52.5], i: [40, 52.5],
+ y: [-42.5, -52.5], z: [0, 65],
+ j: [-30, -37.5], k: [0, -37.5], l: [30, -37.5],
+ m: [-30, 37.5], n: [0, 37.5], o: [30, 37.5],
+ p: [-30, 0], q: [30, 0],
+ A: [-51.5, -65], B: [-17, -75], C: [17, -75], D: [51.5, -65],
+ K: [-51.5, -21], E: [51.5, -21],
+ J: [-51.5, 21], F: [51.5, 21],
+ I: [-51.5, 65], G: [51.5, 65],
+ H: [-17, 75], L: [17, 75]
+ },
+ kite: {
+ b: [0, -65], e: [0, -15], h: [0, 35],
+ z: [0, 35], k: [0, -50], n: [0, 20],
+ p: [-20, -15], q: [20, -15],
+ A: [-38, -52], B: [-29, -78], C: [29, -78], D: [38, -52],
+ K: [-33, -20], E: [33, -20],
+ J: [-25, 11], F: [25, 11],
+ I: [-15, 42], G: [15, 42],
+ H: [0, 73], L: [0, -91]
+ },
+ oldFrench: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-43.75, 0], e: [0, 0], f: [43.75, 0],
+ g: [-37.5, 50], h: [0, 50], i: [37.5, 50],
+ y: [-50, -50], z: [0, 62.5],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 45], o: [37.5, 37.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66.2, -66.6], B: [-22, -66.6], C: [22, -66.6], D: [66.2, -66.6],
+ K: [-66.2, -20], E: [66.2, -20],
+ J: [-64, 26], F: [64, 26],
+ I: [-45, 62], G: [45, 62],
+ H: [0, 91],
+ },
+ renaissance: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-41.5, 0], e: [0, 0], f: [41.5, 0],
+ g: [-43.75, 50], h: [0, 50], i: [43.75, 50],
+ y: [-50, -50], z: [0, 62.5],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 37.5], o: [37.5, 37.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-61, -55], B: [-23, -67], C: [23, -67], D: [61, -55],
+ K: [-55, -11], E: [55, -11],
+ J: [-65, 31], F: [65, 31],
+ I: [-45, 76], G: [45, 76],
+ H: [0, 87]
+ },
+ baroque: {
+ a: [-43.75, -45], b: [0, -45], c: [43.75, -45],
+ d: [-43.75, 0], e: [0, 0], f: [43.75, 0],
+ g: [-43.75, 50], h: [0, 50], i: [43.75, 50],
+ y: [-50, -50], z: [0, 60],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 37.5], o: [37.5, 37.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-65, -54.5], B: [-22, -65], C: [22, -65], D: [65, -54.5],
+ K: [-58.5, -15], E: [58.5, -15],
+ J: [-65, 31], F: [66, 31],
+ I: [-35, 73], G: [35, 73],
+ H: [0, 89]
+ },
+ targe: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-43.75, 0], e: [0, 0], f: [43.75, 0],
+ g: [-43.75, 50], h: [0, 50], i: [43.75, 50],
+ y: [-50, -50], z: [0, 50],
+ j: [-40, -40], k: [0, -40], l: [40, -40],
+ m: [-40, 40], n: [0, 40], o: [40, 40],
+ p: [-32.5, 0], q: [32.5, 0],
+ A: [-66.2, -60], B: [-22, -77], C: [22, -86], D: [60, -66.6],
+ K: [-28, -20], E: [57, -20],
+ J: [-61, 26], F: [61, 26],
+ I: [-49, 63], G: [49, 59],
+ H: [0, 80]
+ },
+ targe2: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-40, 0], e: [0, 0], f: [40, 0],
+ g: [-43.75, 50], h: [0, 50], i: [43.75, 50],
+ y: [-50, -50], z: [0, 60],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 37.5], o: [37.5, 37.5],
+ p: [-32.5, 0], q: [32.5, 0],
+ A: [-55, -59], B: [-15, -59], C: [24, -79], D: [51, -58],
+ K: [-40, -14], E: [51, -14],
+ J: [-64, 26], F: [62, 26],
+ I: [-46, 66], G: [48, 67],
+ H: [0, 83]
+ },
+ pavise: {
+ a: [-40, -52.5], b: [0, -52.5], c: [40, -52.5],
+ d: [-40, 0], e: [0, 0], f: [40, 0],
+ g: [-40, 52.5], h: [0, 52.5], i: [40, 52.5],
+ y: [-42.5, -52.5], z: [0, 60],
+ j: [-30, -35], k: [0, -37.5], l: [30, -35],
+ m: [-30, 35], n: [0, 37.5], o: [30, 35],
+ p: [-30, 0], q: [30, 0],
+ A: [-57, -55], B: [-22, -74], C: [22, -74], D: [57, -55],
+ K: [-54, -11], E: [54, -11],
+ J: [-50, 36], F: [50, 36],
+ I: [-46, 81], G: [46, 81],
+ H: [0, 81]
+ },
+ wedged: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-43.75, 0], e: [0, 0], f: [43.75, 0],
+ g: [-32.25, 37.5], h: [0, 50], i: [32.25, 37.5],
+ y: [-50, -50], z: [0, 62.5],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-32.5, 32.5], n: [0, 42.5], o: [32.5, 32.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66, -53], B: [-22, -72.5], C: [22, -72.5], D: [66, -53],
+ K: [-62.6, -13], E: [62.6, -13],
+ J: [-50, 26], F: [50, 26],
+ I: [-27, 62], G: [27, 62],
+ H: [0, 87]
+ },
+ flag: {
+ a: [-60, -40], b: [0, -40], c: [60, -40],
+ d: [-60, 0], e: [0, 0], f: [60, 0],
+ g: [-60, 40], h: [0, 40], i: [60, 40],
+ y: [-60, -42.5], z: [0, 40],
+ j: [-45, -30], k: [0, -30], l: [45, -30],
+ m: [-45, 30], n: [0, 30], o: [45, 30],
+ p: [-45, 0], q: [45, 0],
+ A: [-81, -51], B: [-27, -51], C: [27, -51], D: [81, -51],
+ K: [-81, -17], E: [81, -17],
+ J: [-81, 17], F: [81, 17],
+ I: [-81, 51], G: [81, 51],
+ H: [-27, 51], L: [27, 51]
+ },
+ pennon: {
+ a: [-75, -40],
+ d: [-75, 0], e: [-25, 0], f: [25, 0],
+ g: [-75, 40],
+ y: [-70, -42.5],
+ j: [-60, -30],
+ m: [-60, 30],
+ p: [-60, 0], q: [5, 0],
+ A: [-81, -48], B: [-43, -36], C: [-4.5, -24], D: [33, -12],
+ E: [72, 0],
+ F: [33, 12], G: [-4.5, 24], H: [-43, 36], I: [-81, 48],
+ J: [-81, 17], K: [-81, -17]
+ },
+ guidon: {
+ a: [-60, -40], b: [0, -40], c: [60, -40],
+ d: [-60, 0], e: [0, 0],
+ g: [-60, 40], h: [0, 40], i: [60, 40],
+ y: [-60, -42.5], z: [0, 40],
+ j: [-45, -30], k: [0, -30], l: [45, -30],
+ m: [-45, 30], n: [0, 30], o: [45, 30],
+ p: [-45, 0],
+ A: [-81, -51], B: [-27, -51], C: [27, -51], D: [78, -51],
+ K: [-81, -17], E: [40.5, -17],
+ J: [-81, 17], F: [40.5, 17],
+ I: [-81, 51], G: [78, 51],
+ H: [-27, 51], L: [27, 51]
+ },
+ banner: {
+ a: [-50, -50], b: [0, -50], c: [50, -50],
+ d: [-50, 0], e: [0, 0], f: [50, 0],
+ g: [-50, 40], h: [0, 40], i: [50, 40],
+ y: [-50, -50], z: [0, 40],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 27.5], n: [0, 27.5], o: [37.5, 27.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66.5, -66.5], B: [-22, -66.5], C: [22, -66.5], D: [66.5, -66.5],
+ K: [-66.5, -20], E: [66.5, -20],
+ J: [-66.5, 26], F: [66.5, 26],
+ I: [-66.5, 66.5], G: [66.5, 66.5],
+ H: [-25, 75], L: [25, 75]
+ },
+ dovetail: {
+ a: [-49.75, -50], b: [0, -50], c: [49.75, -50],
+ d: [-49.75, 0], e: [0, 0], f: [49.75, 0],
+ g: [-49.75, 50], i: [49.75, 50],
+ y: [-50, -50], z: [0, 40],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 32.5], o: [37.5, 37.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66.5, -66.5], B: [-22, -66.5], C: [22, -66.5], D: [66.5, -66.5],
+ K: [-66.5, -16.5], E: [66.5, -16.5],
+ J: [-66.5, 34.5], F: [66.5, 34.5],
+ I: [-66.5, 84.5], G: [66.5, 84.5],
+ H: [-25, 64], L: [25, 64]
+ },
+ gonfalon: {
+ a: [-49.75, -50], b: [0, -50], c: [49.75, -50],
+ d: [-49.75, 0], e: [0, 0], f: [49.75, 0],
+ g: [-49.75, 50], h: [0, 50], i: [49.75, 50],
+ y: [-50, -50], z: [0, 50],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 37.5], o: [37.5, 37.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66.5, -66.5], B: [-22, -66.5], C: [22, -66.5], D: [66.5, -66.5],
+ K: [-66.5, -20], E: [66.5, -20],
+ J: [-66.5, 26], F: [66.5, 26],
+ I: [-40, 63], G: [40, 63],
+ H: [0, 88]
+ },
+ pennant: {
+ a: [-45, -50], b: [0, -50], c: [45, -50],
+ e: [0, 0], h: [0, 50],
+ y: [-50, -50], z: [0, 50],
+ j: [-32.5, -37.5], k: [0, -37.5], l: [32.5, -37.5],
+ n: [0, 37.5],
+ A: [-60, -76], B: [-22, -76], C: [22, -76], D: [60, -76],
+ K: [-46, -38], E: [46, -38],
+ J: [-31, 0], F: [31, 0],
+ I: [-16, 38], G: [16, 38],
+ H: [0, 76]
+ },
+ round: {
+ a: [-40, -40], b: [0, -40], c: [40, -40],
+ d: [-40, 0], e: [0, 0], f: [40, 0],
+ g: [-40, 40], h: [0, 40], i: [40, 40],
+ y: [-48, -48], z: [0, 57.5],
+ j: [-35.5, -35.5], k: [0, -37.5], l: [35.5, -35.5],
+ m: [-35.5, 35.5], n: [0, 37.5], o: [35.5, 35.5],
+ p: [-36.5, 0], q: [36.5, 0],
+ A: [-59, -48], B: [-23, -73], C: [23, -73], D: [59, -48],
+ K: [-76, -10], E: [76, -10],
+ J: [-70, 31], F: [70, 31],
+ I: [-42, 64], G: [42, 64],
+ H: [0, 77]
+ },
+ oval: {
+ a: [-37.5, -50], b: [0, -50], c: [37.5, -50],
+ d: [-43, 0], e: [0, 0], f: [43, 0],
+ g: [-37.5, 50], h: [0, 50], i: [37.5, 50],
+ y: [-48, -48], z: [0, 60],
+ j: [-35.5, -37.5], k: [0, -37.5], l: [35.5, -37.5],
+ m: [-35.5, 37.5], n: [0, 50], o: [35.5, 37.5],
+ p: [-36.5, 0], q: [36.5, 0],
+ A: [-48, -48], B: [-23, -78], C: [23, -78], D: [48, -48],
+ K: [-59, -10], E: [59, -10],
+ J: [-55, 31], F: [55, 31],
+ I: [-36, 68], G: [36, 68],
+ H: [0, 85]
+ },
+ vesicaPiscis: {
+ a: [-32, -37], b: [0, -50], c: [32, -37],
+ d: [-32, 0], e: [0, 0], f: [32, 0],
+ g: [-32, 37], h: [0, 50], i: [32, 37],
+ y: [-50, -50], z: [0, 62],
+ j: [-27.5, -27.5], k: [0, -37], l: [27.5, -27.5],
+ m: [-27.5, 27.5], n: [0, 42], o: [27.5, 27.5],
+ p: [-27.5, 0], q: [27.5, 0],
+ A: [-45, -32], B: [-29, -63], C: [29, -63], D: [45, -32],
+ K: [-50, 0], E: [50, 0],
+ J: [-45, 32], F: [45, 32],
+ I: [-29, 63], G: [29, 63],
+ H: [0, 89], L: [0, -89]
+ },
+ square: {
+ a: [-49.75, -50], b: [0, -50], c: [49.75, -50],
+ d: [-49.75, 0], e: [0, 0], f: [49.75, 0],
+ g: [-49.75, 50], h: [0, 50], i: [49.75, 50],
+ y: [-50, -50], z: [0, 50],
+ j: [-37.5, -37.5], k: [0, -37.5], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 37.5], o: [37.5, 37.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-66.5, -66.5], B: [-22, -66.5], C: [22, -66.5], D: [66.5, -66.5],
+ K: [-66.5, -20], E: [66.5, -20],
+ J: [-66.5, 26], F: [66.5, 26],
+ I: [-66.5, 66.5], G: [66.5, 66.5],
+ H: [-22, 66.5], L: [22, 66.5]
+ },
+ diamond: {
+ a: [-32, -37], b: [0, -50], c: [32, -37],
+ d: [-43, 0], e: [0, 0], f: [43, 0],
+ g: [-32, 37], h: [0, 50], i: [32, 37],
+ y: [-50, -50], z: [0, 62],
+ j: [-27.5, -27.5], k: [0, -37], l: [27.5, -27.5],
+ m: [-27.5, 27.5], n: [0, 42], o: [27.5, 27.5],
+ p: [-37, 0], q: [37, 0],
+ A: [-43, -28], B: [-22, -56], C: [22, -56], D: [43, -28],
+ K: [-63, 0], E: [63, 0],
+ J: [-42, 28], F: [42, 28],
+ I: [-22, 56], G: [22, 56],
+ H: [0, 83], L: [0, -82]
+ },
+ no: {
+ a: [-66.5, -66.5], b: [0, -66.5], c: [66.5, -66.5],
+ d: [-66.5, 0], e: [0, 0], f: [66.5, 0],
+ g: [-66.5, 66.5], h: [0, 66.5], i: [66.5, 66.5],
+ y: [-50, -50], z: [0, 75],
+ j: [-50, -50], k: [0, -50], l: [50, -50],
+ m: [-50, 50], n: [0, 50], o: [50, 50],
+ p: [-50, 0], q: [50, 0],
+ A: [-91.5, -91.5], B: [-30.5, -91.5], C: [30.5, -91.5], D: [91.5, -91.5],
+ K: [-91.5, -30.5], E: [91.5, -30.5],
+ J: [-91.5, 30.5], F: [91.5, 30.5],
+ I: [-91.5, 91.5], G: [91.5, 91.5],
+ H: [-30.5, 91.5], L: [30.5, 91.5]
+ },
+ fantasy1: {
+ a: [-45, -45], b: [0, -50], c: [45, -45],
+ d: [-40, 0], e: [0, 0], f: [40, 0],
+ g: [-36, 42.5], h: [0, 50], i: [36, 42.5],
+ y: [-50, -50], z: [0, 60],
+ j: [-37, -37], k: [0, -40], l: [37, -37],
+ m: [-32, 32], n: [0, 40], o: [32, 32],
+ p: [-28.5, 0], q: [28.5, 0],
+ A: [-66, -55], B: [-22, -67], C: [22, -67], D: [66, -55],
+ K: [-53, -20], E: [53, -20],
+ J: [-46, 26], F: [46, 26],
+ I: [-29, 62], G: [29, 62],
+ H: [0, 84]
+ },
+ fantasy2: {
+ a: [-45, -45], b: [0, -45], c: [45, -45],
+ d: [-35, 0], e: [0, 0], f: [35, 0],
+ g: [-36, 42.5], h: [0, 45], i: [36, 42.5],
+ y: [-50, -50], z: [0, 55],
+ j: [-32.5, -32.5], k: [0, -40], l: [32.5, -32.5],
+ m: [-30, 30], n: [0, 40], o: [30, 30],
+ p: [-27.5, 0], q: [27.5, 0],
+ A: [-58, -35], B: [-44, -67], C: [44, -67], D: [58, -35],
+ K: [-39, -5], E: [39, -5],
+ J: [-57, 26], F: [57, 26],
+ I: [-32, 58], G: [32, 58],
+ H: [0, 83], L: [0, -72]
+ },
+ fantasy3: {
+ a: [-40, -45], b: [0, -50], c: [40, -45],
+ d: [-35, 0], e: [0, 0], f: [35, 0],
+ g: [-36, 42.5], h: [0, 50], i: [36, 42.5],
+ y: [-50, -50], z: [0, 55],
+ j: [-32.5, -32.5], k: [0, -40], l: [32.5, -32.5],
+ m: [-30, 30], n: [0, 40], o: [30, 30],
+ p: [-27.5, 0], q: [27.5, 0],
+ A: [-56, -42], B: [-22, -72], C: [22, -72], D: [56, -42],
+ K: [-37, -11], E: [37, -11],
+ J: [-60, 20], F: [60, 20],
+ I: [-34, 56], G: [34, 56],
+ H: [0, 83]
+ },
+ fantasy4: {
+ a: [-50, -45], b: [0, -50], c: [50, -45],
+ d: [-45, 0], e: [0, 0], f: [45, 0],
+ g: [-40, 45], h: [0, 50], i: [40, 45],
+ y: [-50, -50], z: [0, 62.5],
+ j: [-37.5, -37.5], k: [0, -45], l: [37.5, -37.5],
+ m: [-37.5, 37.5], n: [0, 45], o: [37.5, 37.5],
+ p: [-35, 0], q: [35, 0],
+ A: [-75, -56], B: [-36, -61], C: [36, -61], D: [75, -56],
+ K: [-67, -12], E: [67, -12],
+ J: [-63, 32], F: [63, 32],
+ I: [-42, 75], G: [42, 75],
+ H: [0, 91.5], L: [0, -79]
+ },
+ fantasy5: {
+ a: [-45, -50], b: [0, -50], c: [45, -50],
+ d: [-40, 0], e: [0, 0], f: [40, 0],
+ g: [-30, 45], h: [0, 50], i: [30, 45],
+ y: [-50, -50], z: [0, 60],
+ j: [-37, -37], k: [0, -40], l: [37, -37],
+ m: [-32, 32], n: [0, 40], o: [32, 32],
+ p: [-28.5, 0], q: [28.5, 0],
+ A: [-61, -67], B: [-22, -76], C: [22, -76], D: [61, -67],
+ K: [-58, -25], E: [58, -25],
+ J: [-48, 20], F: [48, 20],
+ I: [-28.5, 60], G: [28.5, 60],
+ H: [0, 89]
+ },
+ noldor: {
+ b: [0, -65], e: [0, -15], h: [0, 35],
+ z: [0, 35], k: [0, -50], n: [0, 30],
+ p: [-20, -15], q: [20, -15],
+ A: [-34, -47], B: [-20, -68], C: [20, -68], D: [34, -47],
+ K: [-18, -20], E: [18, -20],
+ J: [-26, 11], F: [26, 11],
+ I: [-14, 43], G: [14, 43],
+ H: [0, 74], L: [0, -85]
+ },
+ gondor: {
+ a: [-32.5, -50], b: [0, -50], c: [32.5, -50],
+ d: [-32.5, 0], e: [0, 0], f: [32.5, 0],
+ g: [-32.5, 50], h: [0, 50], i: [32.5, 50],
+ y: [-42.5, -52.5], z: [0, 65],
+ j: [-25, -37.5], k: [0, -37.5], l: [25, -37.5],
+ m: [-25, 30], n: [0, 37.5], o: [25, 30],
+ p: [-25, 0], q: [25, 0],
+ A: [-42, -52], B: [-17, -75], C: [17, -75], D: [42, -52],
+ K: [-42, -15], E: [42, -15],
+ J: [-42, 22], F: [42, 22],
+ I: [-26, 60], G: [26, 60],
+ H: [0, 87]
+ },
+ easterling: {
+ a: [-40, -47.5], b: [0, -47.5], c: [40, -47.5],
+ d: [-40, 0], e: [0, 0], f: [40, 0],
+ g: [-40, 47.5], h: [0, 47.5], i: [40, 47.5],
+ y: [-42.5, -52.5], z: [0, 65],
+ j: [-30, -37.5], k: [0, -37.5], l: [30, -37.5],
+ m: [-30, 37.5], n: [0, 37.5], o: [30, 37.5],
+ p: [-30, 0], q: [30, 0],
+ A: [-52, -72], B: [0, -65], D: [52, -72],
+ K: [-52, -24], E: [52, -24],
+ J: [-52, 24], F: [52, 24],
+ I: [-52, 72], G: [52, 72],
+ H: [0, 65]
+ },
+ erebor: {
+ a: [-40, -40], b: [0, -55], c: [40, -40],
+ d: [-40, 0], e: [0, 0], f: [40, 0],
+ g: [-40, 40], h: [0, 55], i: [40, 40],
+ y: [-50, -50], z: [0, 50],
+ j: [-35, -35], k: [0, -45], l: [35, -35],
+ m: [-35, 35], n: [0, 45], o: [35, 35],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-47, -46], B: [-22, -81], C: [22, -81], D: [47, -46],
+ K: [-66.5, 0], E: [66.5, 0],
+ J: [-47, 46], F: [47, 46],
+ I: [-22, 81], G: [22, 81]
+ },
+ ironHills: {
+ a: [-43.75, -50], b: [0, -50], c: [43.75, -50],
+ d: [-43.25, 0], e: [0, 0], f: [43.25, 0],
+ g: [-42.5, 42.5], h: [0, 50], i: [42.5, 42.5],
+ y: [-50, -50], z: [0, 62.5],
+ j: [-32.5, -32.5], k: [0, -40], l: [32.5, -32.5],
+ m: [-32.5, 32.5], n: [0, 40], o: [32.5, 32.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-61, -67], B: [-22, -74], C: [22, -74], D: [61, -67],
+ K: [-59, -20], E: [59, -20],
+ J: [-57, 26], F: [57, 26],
+ I: [-33, 64], G: [33, 64],
+ H: [0, 88]
+ },
+ urukHai: {
+ a: [-40, -45], b: [0, -45], c: [40, -45],
+ d: [-36, 0], e: [0, 0], f: [36, 0],
+ g: [-32.25, 40], h: [0, 40], i: [32.25, 40],
+ y: [-50, -50], z: [0, 40],
+ j: [-32.5, -32.5], k: [0, -37.5], l: [32.5, -32.5],
+ m: [-27.5, 27.5], n: [0, 32.5], o: [27.5, 27.5],
+ p: [-37.5, 0], q: [37.5, 0],
+ A: [-31, -79], B: [-1, -90], C: [31, -74], D: [61, -57],
+ K: [-55, -19], E: [53, -19],
+ J: [-45, 19], F: [45, 19],
+ I: [-33, 57], G: [35, 57],
+ H: [0, 57], L: [-39, -50]
+ },
+ moriaOrc: {
+ a: [-37.5, -37.5], b: [0, -37.5], c: [37.5, -37.5],
+ d: [-37.5, 0], e: [0, 0], f: [37.5, 0],
+ g: [-37.5, 37.5], h: [0, 37.5], i: [37.5, 37.5],
+ y: [-50, -50], z: [0, 40],
+ j: [-30, -30], k: [0, -30], l: [30, -30],
+ m: [-30, 30], n: [0, 30], o: [30, 30],
+ p: [-30, 0], q: [30, 0],
+ A: [-48, -48], B: [-16, -50], C: [16, -46], D: [39, -61],
+ K: [-52, -19], E: [52, -26],
+ J: [-42, 9], F: [52, 9],
+ I: [-31, 40], G: [40, 43],
+ H: [4, 47]
+ }
+ }
+
+ const shieldSize = {
+ horsehead: .9, horsehead2: .9, polish: .85, swiss: .95,
+ boeotian: .75, roman: .95, kite: .65, targe2: .9, pavise: .9, wedged: .95,
+ flag: .7, pennon: .5, guidon: .65, banner: .8, dovetail: .8, pennant: .6,
+ oval: .95, vesicaPiscis: .8, diamond: .8, no: 1.2,
+ fantasy1: .8, fantasy2: .7, fantasy3: .7, fantasy5: .9,
+ noldor: .5, gondor: .75, easterling: .8, erebor: .9, urukHai: .8, moriaOrc: .7
+ }
+
+ const shieldBox = {
+ heater: "0 10 200 200",
+ spanish: "0 10 200 200",
+ french: "0 10 200 200",
+
+ horsehead: "0 10 200 200",
+ horsehead2: "0 10 200 200",
+ polish: "0 0 200 200",
+ hessen: "0 5 200 200",
+ swiss: "0 10 200 200",
+
+ boeotian: "0 0 200 200",
+ roman: "0 0 200 200",
+ kite: "0 0 200 200",
+ oldFrench: "0 10 200 200",
+ renaissance: "0 5 200 200",
+ baroque: "0 10 200 200",
+
+ targe: "0 0 200 200",
+ targe2: "0 0 200 200",
+ pavise: "0 0 200 200",
+ wedged: "0 10 200 200",
+
+ flag: "0 0 200 200",
+ pennon: "2.5 0 200 200",
+ guidon: "2.5 0 200 200",
+ banner: "0 10 200 200",
+ dovetail: "0 10 200 200",
+ gonfalon: "0 10 200 200",
+ pennant: "0 0 200 200",
+
+ round: "0 0 200 200",
+ oval: "0 0 200 200",
+ vesicaPiscis: "0 0 200 200",
+ square: "0 0 200 200",
+ diamond: "0 0 200 200",
+ no: "0 0 200 200",
+
+ fantasy1: "0 0 200 200",
+ fantasy2: "0 5 200 200",
+ fantasy3: "0 5 200 200",
+ fantasy4: "0 5 200 200",
+ fantasy5: "0 0 200 200",
+
+ noldor: "0 0 200 200",
+ gondor: "0 5 200 200",
+ easterling: "0 0 200 200",
+ erebor: "0 0 200 200",
+ ironHills: "0 5 200 200",
+ urukHai: "0 0 200 200",
+ moriaOrc: "0 0 200 200"
+ }
+
+ const shieldPaths = {
+ heater: "m25,25 h150 v50 a150,150,0,0,1,-75,125 a150,150,0,0,1,-75,-125 z",
+ spanish: "m25,25 h150 v100 a75,75,0,0,1,-150,0 z",
+ french: "m 25,25 h 150 v 139.15 c 0,41.745 -66,18.15 -75,36.3 -9,-18.15 -75,5.445 -75,-36.3 v 0 z",
+ horsehead: "m 20,40 c 0,60 40,80 40,100 0,10 -4,15 -0.35,30 C 65,185.7 81,200 100,200 c 19.1,0 35.3,-14.6 40.5,-30.4 C 144.2,155 140,150 140,140 140,120 180,100 180,40 142.72,40 150,15 100,15 55,15 55,40 20,40 Z",
+ horsehead2: "M60 20c-5 20-10 35-35 55 25 35 35 65 30 100 20 0 35 10 45 26 10-16 30-26 45-26-5-35 5-65 30-100a87 87 0 01-35-55c-25 3-55 3-80 0z",
+ polish: "m 90.3,6.3 c -12.7,0 -20.7,10.9 -40.5,14 0,11.8 -4.9,23.5 -11.4,31.1 0,0 12.7,6 12.7,19.3 C 51.1,90.8 30,90.8 30,90.8 c 0,0 -3.6,7.4 -3.6,22.4 0,34.3 23.1,60.2 40.7,68.2 17.6,8 27.7,11.4 32.9,18.6 5.2,-7.3 15.3,-10.7 32.8,-18.6 17.6,-8 40.7,-33.9 40.7,-68.2 0,-15 -3.6,-22.4 -3.6,-22.4 0,0 -21.1,0 -21.1,-20.1 0,-13.3 12.7,-19.3 12.7,-19.3 C 155.1,43.7 150.2,32.1 150.2,20.3 130.4,17.2 122.5,6.3 109.7,6.3 102.5,6.3 100,10 100,10 c 0,0 -2.5,-3.7 -9.7,-3.7 z",
+ hessen: "M170 20c4 5 8 13 15 20 0 0-10 0-10 15 0 100-15 140-75 145-65-5-75-45-75-145 0-15-10-15-10-15l15-20c0 15 10-5 70-5s70 20 70 5z",
+ swiss: "m 25,20 c -0.1,0 25.2,8.5 37.6,8.5 C 75.1,28.5 99.1,20 100,20 c 0.6,0 24.9,8.5 37.3,8.5 C 149.8,28.5 174.4,20 175,20 l -0.3,22.6 C 173.2,160.3 100,200 100,200 100,200 26.5,160.9 25.2,42.6 Z",
+ boeotian: "M150 115c-5 0-10-5-10-15s5-15 10-15c10 0 7 10 15 10 10 0 0-30 0-30-10-25-30-55-65-55S45 40 35 65c0 0-10 30 0 30 8 0 5-10 15-10 5 0 10 5 10 15s-5 15-10 15c-10 0-7-10-15-10-10 0 0 30 0 30 10 25 30 55 65 55s55-30 65-55c0 0 10-30 0-30-8 0-5 10-15 10z",
+ roman: "m 160,170 c -40,20 -80,20 -120,0 V 30 C 80,10 120,10 160,30 Z",
+ kite: "m 53.3,46.4 c 0,4.1 1,12.3 1,12.3 7.1,55.7 45.7,141.3 45.7,141.3 0,0 38.6,-85.6 45.7,-141.2 0,0 1,-8.1 1,-12.3 C 146.7,20.9 125.8,0.1 100,0.1 74.2,0.1 53.3,20.9 53.3,46.4 Z",
+ oldFrench: "m25,25 h150 v75 a100,100,0,0,1,-75,100 a100,100,0,0,1,-75,-100 z",
+ renaissance: "M 25,33.9 C 33.4,50.3 36.2,72.9 36.2,81.7 36.2,109.9 25,122.6 25,141 c 0,29.4 24.9,44.1 40.2,47.7 15.3,3.7 29.3,0 34.8,11.3 5.5,-11.3 19.6,-7.6 34.8,-11.3 C 150.1,185 175,170.3 175,141 c 0,-18.4 -11.2,-31.1 -11.2,-59.3 0,-8.8 2.8,-31.3 11.2,-47.7 L 155.7,14.4 C 138.2,21.8 119.3,25.7 100,25.7 c -19.3,0 -38.2,-3.9 -55.7,-11.3 z",
+ baroque: "m 100,25 c 18,0 50,2 75,14 v 37 l -2.7,3.2 c -4.9,5.4 -6.6,9.6 -6.7,16.2 0,6.5 2,11.6 6.9,17.2 l 2.8,3.1 v 10.2 c 0,17.7 -2.2,27.7 -7.8,35.9 -5,7.3 -11.7,11.3 -32.3,19.4 -12.6,5 -20.2,8.8 -28.6,14.5 C 103.3,198 100,200 100,200 c 0,0 -2.8,-2.3 -6.4,-4.7 C 85.6,189.8 78,186 65,180.9 32.4,168.1 26.9,160.9 25.8,129.3 L 25,116 l 3.3,-3.3 c 4.8,-5.2 7,-10.7 7,-17.3 0,-6.8 -1.8,-11.1 -6.5,-16.1 L 25,76 V 39 C 50,27 82,25 100,25 Z",
+ targe: "m 20,35 c 15,0 115,-60 155,-10 -5,10 -15,15 -10,50 5,45 10,70 -10,90 C 125,195 75,195 50,175 25,150 30,130 35,85 50,95 65,85 65,70 65,50 50,45 40,50 30,55 27,65 30,70 23,73 20,70 14,70 11,60 20,45 20,35 Z",
+ targe2: "m 84,32.2 c 6.2,-1 19.5,-31.4 94.1,-20.2 -30.57,33.64 -21.66,67.37 -11.2,95 20.2,69.5 -41.17549,84.7 -66.88,84.7 C 74.32,191.7071 8.38,168.95 32,105.9 36.88,92.88 31,89 31,82.6 35.15,82.262199 56.79,86.17 56.5,69.8 56.20,52.74 42.2,47.9 25.9,55.2 25.9,51.4 39.8,6.7 84,32.2 Z",
+ pavise: "M95 7L39.9 37.3a10 10 0 00-5.1 9.5L46 180c.4 5.2 3.7 10 9 10h90c5.3 0 9.6-4.8 10-10l10.6-133.2a10 10 0 00-5-9.5L105 7c-4.2-2.3-6.2-2.3-10 0z",
+ wedged: "m 51.2,19 h 96.4 c 3.1,12.7 10.7,20.9 26.5,20.8 C 175.7,94.5 165.3,144.3 100,200 43.5,154.2 22.8,102.8 25.1,39.7 37,38.9 47.1,34.7 51.2,19 Z",
+ round: "m 185,100 a 85,85 0 0 1 -85,85 85,85 0 0 1 -85,-85 85,85 0 0 1 85,-85 85,85 0 0 1 85,85",
+ oval: "m 32.3,99.5 a 67.7,93.7 0 1 1 0,1.3 z",
+ vesicaPiscis: "M 100,0 C 63.9,20.4 41,58.5 41,100 c 0,41.5 22.9,79.6 59,100 36.1,-20.4 59,-58.5 59,-100 C 159,58.5 136.1,20.4 100,0 Z",
+ square: "M 25,25 H 175 V 175 H 25 Z",
+ diamond: "M 25,100 100,200 175,100 100,0 Z",
+ no: "m0,0 h200 v200 h-200 z",
+ flag: "M 10,40 h180 v120 h-180 Z",
+ pennon: "M 10,40 l190,60 -190,60 Z",
+ guidon: "M 10,40 h190 l-65,60 65,60 h-190 Z",
+ banner: "m 25,25 v 170 l 25,-40 25,40 25,-40 25,40 25,-40 25,40 V 25 Z",
+ dovetail: "m 25,25 v 175 l 75,-40 75,40 V 25 Z",
+ gonfalon: "m 25,25 v 125 l 75,50 75,-50 V 25 Z",
+ pennant: "M 25,15 100,200 175,15 Z",
+ fantasy1: "M 100,5 C 85,30 40,35 15,40 c 40,35 20,90 40,115 15,25 40,30 45,45 5,-15 30,-20 45,-45 20,-25 0,-80 40,-115 C 160,35 115,30 100,5 Z",
+ fantasy2: "m 152,21 c 0,0 -27,14 -52,-4 C 75,35 48,21 48,21 50,45 30,55 30,75 60,75 60,115 32,120 c 3,40 53,50 68,80 15,-30 65,-40 68,-80 -28,-5 -28,-45 2,-45 C 170,55 150,45 152,21 Z",
+ fantasy3: "M 167,67 C 165,0 35,0 33,67 c 32,-7 27,53 -3,43 -5,45 60,65 70,90 10,-25 75,-47.51058 70,-90 -30,10 -35,-50 -3,-43 z",
+ fantasy4: "M100 9C55 48 27 27 13 39c23 50 3 119 49 150 14 9 28 11 38 11s27-4 38-11c55-39 24-108 49-150-14-12-45 7-87-30z",
+ fantasy5: "M 100,0 C 75,25 30,25 30,25 c 0,69 20,145 70,175 50,-30 71,-106 70,-175 0,0 -45,0 -70,-25 z",
+ noldor: "m 55,75 h 2 c 3,-25 38,-10 3,20 15,50 30,75 40,105 10,-30 25,-55 40,-105 -35,-30 0,-45 3,-20 h 2 C 150,30 110,20 100,0 90,20 50,30 55,75 Z",
+ gondor: "m 100,200 c 15,-15 38,-35 45,-60 h 5 V 30 h -5 C 133,10 67,10 55,30 h -5 v 110 h 5 c 7,25 30,45 45,60 z",
+ easterling: "M 160,185 C 120,170 80,170 40,185 V 15 c 40,15 80,15 120,0 z",
+ erebor: "M25 135 V60 l22-13 16-37 h75 l15 37 22 13 v75l-22 18-16 37 H63l-16-37z",
+ ironHills: "m 30,25 60,-10 10,10 10,-10 60,10 -5,125 -65,50 -65,-50 z",
+ urukHai: "M 30,60 C 40,60 60,50 60,20 l -5,-3 45,-17 75,40 -5,5 -35,155 -5,-35 H 70 v 35 z",
+ moriaOrc: "M45 35c5 3 7 10 13 9h19c4-2 7-4 9-9 6 1 9 9 16 11 7-2 14 0 21 0 6-3 6-10 10-15 2-5 1-10-2-15-2-4-5-14-4-16 3 6 7 11 12 14 7 3 3 12 7 16 3 6 4 12 9 18 2 4 6 8 5 14 0 6-1 12 3 18-3 6-2 13-1 20 1 6-2 12-1 18 0 6-3 13 0 18 8 4 0 8-5 7-4 3-9 3-13 9-5 5-5 13-8 19 0 6 0 15-7 16-1 6-7 6-10 12-1-6 0-6-2-9l2-19c2-4 5-12-3-12-4-5-11-5-15 1l-13-18c-3-4-2 9-3 12 2 2-4-6-7-5-8-2-8 7-11 11-2 4-5 10-8 9 3-10 3-16 1-23-1-4 2-9-4-11 0-6 1-13-2-19-4-2-9-6-13-7V91c4-7-5-13 0-19-3-7 2-11 2-18-1-6 1-12 3-17v-1z"
+ }
+
+ const lines = {
+ straight: "m 0,100 v15 h 200 v -15 z",
+ engrailed: "m 0,95 a 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 6.25,6.25 0 0 0 12.5,0 v 20 H 0 Z",
+ invecked: "M0,102.5 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 a6.25,6.25,0,0,1,12.5,0 v12.5 H0 z",
+ embattled: "M 0,105 H 2.5 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 15 V 95 h 15 v 10 h 2.5 v 10 H 0 Z",
+ wavy: "m 200,115 v -15 c -8.9,3.5 -16,3.1 -25,0 -8.9,-3.5 -16,-3.1 -25,0 -8.9,3.5 -16,3.2 -25,0 -8.9,-3.5 -16,-3.2 -25,0 -8.9,3.5 -16,3.1 -25,0 -8.9,-3.5 -16,-3.1 -25,0 -8.9,3.5 -16,3.2 -25,0 -8.9,-3.5 -16,-3.2 -25,0 v 15 z",
+ raguly: "m 200,95 h -3 l -5,10 h -10 l 5,-10 h -10 l -5,10 h -10 l 5,-10 h -10 l -5,10 h -10 l 5,-10 h -10 l -5,10 h -10 l 5,-10 h -10 l -5,10 h -10 l 5,-10 H 97 l -5,10 H 82 L 87,95 H 77 l -5,10 H 62 L 67,95 H 57 l -5,10 H 42 L 47,95 H 37 l -5,10 H 22 L 27,95 H 17 l -5,10 H 2 L 7,95 H 0 v 20 h 200 z",
+ dancetty: "m 0,105 10,-15 15,20 15,-20 15,20 15,-20 15,20 15,-20 15,20 15,-20 15,20 15,-20 15,20 15,-20 10,15 v 10 H 0 Z",
+ dentilly: "M 180,105 170,95 v 10 L 160,95 v 10 L 150,95 v 10 L 140,95 v 10 L 130,95 v 10 L 120,95 v 10 L 110,95 v 10 L 100,95 v 10 L 90,95 v 10 L 80,95 v 10 L 70,95 v 10 L 60,95 v 10 L 50,95 v 10 L 40,95 v 10 L 30,95 v 10 L 20,95 v 10 L 10,95 v 10 L 0,95 v 20 H 200 V 105 L 190,95 v 10 L 180,95 Z",
+ angled: "m 0,95 h 100 v 10 h 100 v 10 H 0 Z",
+ urdy: "m 200,90 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,6 -5,-6 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,6 -5,-6 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 l -5,-5 -5,5 v 10 l -5,5 -5,-5 V 95 L 0,90 v 25 h 200",
+ indented: "m 100,95 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 v 20 H 0 V 95 l 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 5,-10 5,10 z",
+ bevilled: "m 0,92.5 h 110 l -20,15 H 200 V 115 H 0 Z",
+ nowy: "m 0,95 h 80 c 0,0 0.1,20.1 20,20 19.9,-0.1 20,-20 20,-20 h 80 v 20 H 0 Z",
+ nowyReversed: "m 200,105 h -80 c 0,0 -0.1,-20.1 -20,-20 -19.9,0.1 -20,20 -20,20 H 0 v 10 h 200 z",
+ potenty: "m 3,95 v 5 h 5 v 5 H 0 v 10 h 200 l 0.5,-10 H 193 v -5 h 5 v -5 h -15 v 5 h 5 v 5 h -15 v -5 h 5 v -5 h -15 v 5 h 5 v 5 h -15 v -5 h 5 v -5 h -15 v 5 h 5 v 5 h -15 v -5 h 5 v -5 h -15 v 5 h 5 v 5 h -15 v -5 h 5 v -5 h -15 v 5 h 5 v 5 H 100.5 93 v -5 h 5 V 95 H 83 v 5 h 5 v 5 H 73 v -5 h 5 V 95 H 63 v 5 h 5 v 5 H 53 v -5 h 5 V 95 H 43 v 5 h 5 v 5 H 33 v -5 h 5 V 95 H 23 v 5 h 5 v 5 H 13 v -5 h 5 v -5 z",
+ potentyDexter: "m 200,105 h -2 v -10 0 0 h -10 v 5 h 5 v 5 H 183 V 95 h -10 v 5 h 5 v 5 H 168 V 95 h -10 v 5 h 5 v 5 H 153 V 95 h -10 v 5 h 5 v 5 H 138 V 95 h -10 v 5 h 5 v 5 H 123 V 95 h -10 v 5 h 5 v 5 h -10 v 0 0 -10 H 98 v 5 h 5 v 5 H 93 V 95 H 83 v 5 h 5 v 5 H 78 V 95 H 68 v 5 h 5 v 5 H 63 V 95 H 53 v 5 h 5 v 5 H 48 V 95 H 38 v 5 h 5 v 5 H 33 V 95 H 23 v 5 h 5 v 5 H 18 V 95 H 8 v 5 h 5 v 5 H 3 V 95 H 0 v 20 h 200 z",
+ potentySinister: "m 2.5,95 v 10 H 0 v 10 h 202.5 v -15 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 h -10 v 10 h -10 v -5 h 5 v -5 z",
+ embattledGhibellin: "M 200,200 V 100 l -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 -5,-5 v 10 l -5,-5 -5,5 V 95 l -5,5 v 15 h 200",
+ embattledNotched: "m 200,105 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 h -5 V 95 l -5,5 -5,-5 v 10 H 90 V 95 l -5,5 -5,-5 v 10 H 75 V 95 l -5,5 -5,-5 v 10 H 60 V 95 l -5,5 -5,-5 v 10 H 45 V 95 l -5,5 -5,-5 v 10 H 30 V 95 l -5,5 -5,-5 v 10 H 15 V 95 l -5,5 -5,-5 v 10 H 0 v 10 h 200",
+ embattledGrady: "m 0,95 v 20 H 200 V 95 h -2.5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 h -5 v 5 h -5 v 5 h -5 v -5 h -5 v -5 z",
+ dovetailed: "m 200,95 h -7 l 4,10 h -14 l 4,-10 h -14 l 4,10 h -14 l 4,-10 h -14 l 4,10 h -14 l 4,-10 h -14 l 4,10 h -14 l 4,-10 h -14 l 4,10 h -14 l 4,-10 H 93 l 4,10 H 83 L 87,95 H 73 l 4,10 H 63 L 67,95 H 53 l 4,10 H 43 L 47,95 H 33 l 4,10 H 23 L 27,95 H 13 l 4,10 H 3 L 7,95 H 0 v 20 h 200",
+ dovetailedIndented: "m 200,100 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 -7,-5 4,10 -7,-5 -7,5 4,-10 -7,5 v 15 h 200",
+ nebuly: "m 13.1,89.8 c -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.2,4.5 -7.3,4.5 -0.5,0 -2.2,-0.2 -2.2,-0.2 V 115 h 200 v -10.1 c -3.7,-0.2 -6.7,-2.2 -6.7,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.8,-1.9 1.8,-3.1 0,-2.5 -3.2,-4.5 -7.2,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.8,-1.9 1.8,-3.1 0,-2.5 -3.2,-4.5 -7.2,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 -1.5,4.1 -4.2,4.4 -8.8,4.5 -4.7,-0.1 -8.7,-1.5 -8.9,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 -4.1,0 -7.3,2 -7.3,4.5 0,1.2 0.7,2.3 1.8,3.1 1.2,0.7 1.9,1.8 1.9,3 0,2.5 -3.3,4.5 -7.3,4.5 -4,0 -7.3,-2 -7.3,-4.5 0,-1.2 0.7,-2.3 1.9,-3 1.2,-0.8 1.9,-1.9 1.9,-3.1 0,-2.5 -3.3,-4.5 -7.3,-4.5 z",
+ rayonne: "M0 115l-.1-6 .2.8c1.3-1 2.3-2.5 2.9-4.4.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4A9 9 0 015.5 90c-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 2.1 3.1 3.1 4.6 1 1.6 2.4 3.1 2.7 4.8.3 1.7.3 3.3 0 5.2 1.3-1 2.6-2.7 3.2-4.6.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.5 2 1.7 3.6 3.1 4.6a9 9 0 013.1 4.6c.5 2 .4 3.9-.3 5.4a9 9 0 003.1-4.6c.5-2 .4-3.9-.3-5.4-.7-1.5-.8-3.4-.3-5.4.5-2 1.7-3.6 3.1-4.6-.7 1.5-.8 3.4-.3 5.4.75 2.79 2.72 4.08 4.45 5.82L200 115z",
+ seaWaves: "m 28.83,94.9 c -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.44,-3.6 3.6,-3.6 0.7,0 1.36,0.17 1.93,0.48 -0.33,-2.03 -2.19,-3.56 -4.45,-3.56 -4.24,0 -6.91,3.13 -8.5,5.13 V 115 h 200 v -14.89 c -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.2,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.2,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.44,-3.6 3.6,-3.6 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 -4.25,0 -6.6,3.09 -8.19,5.09 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.2,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.2,-3.55 -4.46,-3.55 -4.25,0 -7.16,3.17 -8.75,5.18 -1.59,2.01 -4.5,5.18 -8.75,5.18 -2.16,0 -3.91,-1.63 -3.91,-3.64 0,-2.01 1.75,-3.64 3.91,-3.64 0.7,0 1.36,0.17 1.93,0.48 -0.34,-2.01 -2.21,-3.55 -4.46,-3.55 z",
+ dragonTeeth: "M 9.4,85 C 6.5,88.1 4.1,92.9 3,98.8 1.9,104.6 2.3,110.4 3.8,115 2.4,113.5 0,106.6 0,109.3 v 5.7 h 200 v -5.7 c -1.1,-2.4 -2,-5.1 -2.6,-8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.9 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.9 -0.7,11.6 0.8,16.2 -1.4,-1.5 -2.8,-3.9 -3.8,-6.1 -1.1,-2.4 -2.3,-6.1 -2.6,-7.7 -0.2,-5.9 0.2,-11.7 1.7,-16.3 -3,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.8 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1,-5.8 -0.7,-11.6 0.9,-16.2 -3,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.8 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.8 -0.7,-11.6 0.9,-16.2 -3,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.8 -0.7,11.6 0.8,16.2 -2.9,-3.1 -5.3,-7.9 -6.4,-13.8 C 63,95.4 63.4,89.6 64.9,85 c -2.9,3.1 -5.3,7.9 -6.3,13.8 -1.1,5.8 -0.7,11.6 0.8,16.2 -3,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.8 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1,5.8 -0.6,11.6 0.9,16.2 -3,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.8 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1,5.8 -0.7,11.6 0.9,16.2 -3,-3.1 -5.3,-7.9 -6.4,-13.8 -1.1,-5.8 -0.7,-11.6 0.8,-16.2 -2.9,3.1 -5.3,7.9 -6.4,13.8 -1.1,5.8 -0.7,11.6 0.9,16.2 -3,-3.1 -5.3,-7.9 -6.4,-13.8 C 18.6,95.4 19,89.6 20.5,85 17.6,88.1 15.2,92.9 14.1,98.8 13,104.6 13.4,110.4 14.9,115 12,111.9 9.6,107.1 8.6,101.2 7.5,95.4 7.9,89.6 9.4,85 Z",
+ firTrees: "m 3.9,90 -4,7 2,-0.5 L 0,100 v 15 h 200 v -15 l -1.9,-3.5 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4.1,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4.1,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 -4,-7 -4,7 2,-0.5 -4,7 2,-0.5 -4,7 -4,-7 2,0.5 -4,-7 2,0.5 z",
+ flechy: "m 0,100 h 85 l 15,-15 15,15 h 85 v 15 H 0 Z",
+ barby: "m 0,100 h 85 l 15,15 15,-15 h 85 v 15 H 0 Z",
+ enclavy: "M 0,100 H 85 V 85 h 30 v 15 h 85 v 15 H 0 Z",
+ escartely: "m 0,100 h 85 v 15 h 30 v -15 h 85 v 15 H 0 Z",
+ arched: "m 100,95 c 40,-0.2 100,20 100,20 H 0 c 0,0 60,-19.8 100,-20 z",
+ archedReversed: "m 0,85 c 0,0 60,20.2 100,20 40,-0.2 100,-20 100,-20 v 30 H 0 Z"
+ }
+
+ const templates = {
+ // divisions
+ perFess: line => ` `,
+ perPale: line => ` `,
+ perBend: line => ` `,
+ perBendSinister: line => ` `,
+ perChevron: line => ` `,
+ perChevronReversed: line => ` `,
+ perCross: line => ` `,
+ perPile: line => ` `,
+ perSaltire: () => ` `,
+ gyronny: () => ` `,
+ chevronny: () => ` `,
+ // oprinaries
+ fess: line => ` `,
+ pale: line => ` `,
+ bend: line => ` `,
+ bendSinister: line => ` `,
+ chief: line => ` `,
+ bar: line => ` `,
+ gemelle: line => ` `,
+ fessCotissed: line => ` `,
+ fessDoubleCotissed: line => ` `,
+ bendlet: line => ` `,
+ bendletSinister: line => ` `,
+ terrace: line => ` `,
+ cross: line => ` `,
+ crossParted: line => ` `,
+ saltire: line => ` `,
+ saltireParted: line => ` `,
+ mount: () => ` `,
+ point: () => ` `,
+ flaunches: () => ` `,
+ gore: () => ` `,
+ pall: () => ` `,
+ pallReversed: () => ` `,
+ chevron: () => ` `,
+ chevronReversed: () => ` `,
+ gyron: () => ` `,
+ quarter: () => ` `,
+ canton: () => ` `,
+ pile: () => ` `,
+ pileInBend: () => ` `,
+ pileInBendSinister: () => ` `,
+ piles: () => ` `,
+ pilesInPoint: () => ` `,
+ label: () => ` `
+ }
+
+ const patterns = {
+ semy: (p, c1, c2, size, chargeId) => ` `,
+ vair: (p, c1, c2, size) => ` `,
+ vairInPale: (p, c1, c2, size) => ` `,
+ vairEnPointe: (p, c1, c2, size) => ` `,
+ ermine: (p, c1, c2, size) => ` `,
+ chequy: (p, c1, c2, size) => ` `,
+ lozengy: (p, c1, c2, size) => ` `,
+ fusily: (p, c1, c2, size) => ` `,
+ pally: (p, c1, c2, size) => ` `,
+ barry: (p, c1, c2, size) => ` `,
+ gemelles: (p, c1, c2, size) => ` `,
+ bendy: (p, c1, c2, size) => ` `,
+ bendySinister: (p, c1, c2, size) => ` `,
+ palyBendy: (p, c1, c2, size) => ` `,
+ pappellony: (p, c1, c2, size) => ` `,
+ masoned: (p, c1, c2, size) => ` `,
+ fretty: (p, c1, c2, size) => ` `
+ }
+
+ const draw = async function(id, coa) {
+ const {shield, division, ordinaries = [], charges = []} = coa;
+
+ const ordinariesRegular = ordinaries.filter(o => !o.above);
+ const ordinariesAboveCharges = ordinaries.filter(o => o.above);
+ const shieldPath = shieldPaths[shield];
+ const tDiv = division ? (division.t.includes("-") ? division.t.split("-")[1] : division.t) : null;
+ const positions = shieldPositions[shield];
+ const sizeModifier = shieldSize[shield] || 1;
+ const viewBox = shieldBox[shield] || "0 0 200 200";
+
+ const shieldClip = ` `;
+ const divisionClip = division ? `${getTemplate(division.division, division.line)} ` : "";
+ const loadedCharges = await getCharges(coa, id, shieldPath);
+ const loadedPatterns = getPatterns(coa, id);
+ const blacklight = `
+
+
+
+ `;
+ const field = ` `;
+ const divisionGroup = division ? templateDivision() : "";
+ const overlay = ` `;
+
+ const svg= ``;
+
+ // insert coa svg to defs
+ document.getElementById("coas").insertAdjacentHTML("beforeend", svg);
+
+ function templateDivision() {
+ let svg = "";
+
+ // In field part
+ for (const ordinary of ordinariesRegular) {
+ if (ordinary.divided === "field") svg += templateOrdinary(ordinary, ordinary.t);
+ else if (ordinary.divided === "counter") svg += templateOrdinary(ordinary, tDiv);
+ }
+
+ for (const charge of charges) {
+ if (charge.divided === "field") svg += templateCharge(charge, charge.t);
+ else if (charge.divided === "counter") svg += templateCharge(charge, tDiv);
+ }
+
+ for (const ordinary of ordinariesAboveCharges) {
+ if (ordinary.divided === "field") svg += templateOrdinary(ordinary, ordinary.t);
+ else if (ordinary.divided === "counter") svg += templateOrdinary(ordinary, tDiv);
+ }
+
+ // In division part
+ svg += ` `;
+
+ for (const ordinary of ordinariesRegular) {
+ if (ordinary.divided === "division") svg += templateOrdinary(ordinary, ordinary.t);
+ else if (ordinary.divided === "counter") svg += templateOrdinary(ordinary, coa.t1);
+ }
+
+ for (const charge of charges) {
+ if (charge.divided === "division") svg += templateCharge(charge, charge.t);
+ else if (charge.divided === "counter") svg += templateCharge(charge, coa.t1);
+ }
+
+ for (const ordinary of ordinariesAboveCharges) {
+ if (ordinary.divided === "division") svg += templateOrdinary(ordinary, ordinary.t);
+ else if (ordinary.divided === "counter") svg += templateOrdinary(ordinary, coa.t1);
+ }
+
+ return (svg += ` `);
+ }
+
+ function templateAboveAll() {
+ let svg = "";
+
+ ordinariesRegular.filter(o => !o.divided)
+ .forEach(ordinary => {
+ svg += templateOrdinary(ordinary, ordinary.t);
+ });
+
+ charges.filter(o => !o.divided || !division)
+ .forEach(charge => {
+ svg += templateCharge(charge, charge.t);
+ });
+
+ ordinariesAboveCharges.filter(o => !o.divided)
+ .forEach(ordinary => {
+ svg += templateOrdinary(ordinary, ordinary.t);
+ });
+
+ return svg;
+ }
+
+ function templateOrdinary(ordinary, tincture) {
+ const fill = clr(tincture);
+ let svg = ``;
+ if (ordinary.ordinary === "bordure") svg += ` `;
+ else if (ordinary.ordinary === "orle") svg += ``;
+ else svg += getTemplate(ordinary.ordinary, ordinary.line);
+ return svg + ` `;
+ }
+
+ function templateCharge(charge, tincture) {
+ const fill = clr(tincture);
+ const chargePositions = [...new Set(charge.p)].filter(position => positions[position]);
+
+ let svg = "";
+ svg += ``;
+ for (const p of chargePositions) {
+ const transform = getElTransform(charge, p);
+ svg += ``;
+ }
+ return svg + ` `;
+
+ function getElTransform(c, p) {
+ const s = (c.size || 1) * sizeModifier;
+ const sx = c.sinister ? -s : s;
+ const sy = c.reversed ? -s : s;
+ let [x, y] = positions[p];
+ x = x - 100 * (sx - 1);
+ y = y - 100 * (sy - 1);
+ const scale = c.sinister || c.reversed ? `${sx} ${sy}` : s;
+ return `translate(${x} ${y}) scale(${scale})`;
+ }
+ }
+ }
+
+ async function getCharges(coa, id, shieldPath) {
+ let charges = coa.charges ? coa.charges.map(charge => charge.charge) : []; // add charges
+ if (semy(coa.t1)) charges.push(semy(coa.t1)); // add field semy charge
+ if (semy(coa.division?.t)) charges.push(semy(coa.division.t)); // add division semy charge
+
+ const uniqueCharges = [...new Set(charges)];
+ const fetchedCharges = await Promise.all(
+ uniqueCharges.map(async charge => {
+ if (charge === "inescutcheon") return ` `;
+ const fetched = await fetchCharge(charge, id);
+ return fetched;
+ })
+ );
+ return fetchedCharges.join("");
+ }
+
+ const url = PRODUCTION ? "./charges/" : "http://armoria.herokuapp.com/charges/"; // on local machine fetch files from server
+ async function fetchCharge(charge, id) {
+ const fetched = fetch(url + charge + ".svg").then(res => {
+ if (res.ok) return res.text();
+ else throw new Error("Cannot fetch charge");
+ }).then(text => {
+ const html = document.createElement("html");
+ html.innerHTML = text;
+ const g = html.querySelector("g");
+ g.setAttribute("id", charge + "_" + id);
+ return g.outerHTML;
+ }).catch(err => console.error(err));
+ return fetched;
+ }
+
+ function getPatterns(coa, id) {
+ const isPattern = string => string.includes("-");
+ let patternsToAdd = [];
+ if (coa.t1.includes("-")) patternsToAdd.push(coa.t1); // add field pattern
+ if (coa.division && isPattern(coa.division.t)) patternsToAdd.push(coa.division.t); // add division pattern
+ if (coa.ordinaries) coa.ordinaries.filter(ordinary => isPattern(ordinary.t)).forEach(ordinary => patternsToAdd.push(ordinary.t)); // add ordinaries pattern
+ if (coa.charges) coa.charges.filter(charge => isPattern(charge.t)).forEach(charge => patternsToAdd.push(charge.t)); // add charges pattern
+ if (!patternsToAdd.length) return "";
+
+ return [...new Set(patternsToAdd)].map(patternString => {
+ const [pattern, t1, t2, size] = patternString.split("-");
+ const charge = semy(patternString);
+ if (charge) return patterns.semy(patternString, clr(t1), clr(t2), getSizeMod(size), charge + "_" + id);
+ return patterns[pattern](patternString, clr(t1), clr(t2), getSizeMod(size), charge);
+ }).join("");
+ }
+
+ function getSizeMod(size) {
+ if (size === "small") return .5;
+ if (size === "smaller") return .25;
+ if (size === "smallest") return .125;
+ if (size === "big") return 2;
+ return 1;
+ }
+
+ function getTemplate(templateId, lineId) {
+ if (!lineId) return templates[templateId]();
+ const line = lines[lineId] || lines.straight;
+ return templates[templateId](line);
+ }
+
+ // get color or link to pattern
+ function clr(tincture) {
+ if (colors[tincture]) return colors[tincture];
+ return `url(#${tincture})`;
+ }
+
+ // get charge is string starts with "semy"
+ function semy(string) {
+ const isSemy = /^semy/.test(string);
+ if (!isSemy) return false;
+ return string.match(/semy_of_(.*?)-/)[1];
+ }
+
+ // render coa if does not exist
+ const trigger = function(id, coa) {
+ if (coa === "custom") {
+ console.warn("Cannot render custom emblem", coa);
+ return;
+ }
+ if (!coa) {
+ console.warn(`Emblem ${id} is undefined`);
+ return;
+ }
+ if (!document.getElementById(id)) draw(id, coa);
+ }
+
+ const add = function(type, i, coa, x, y) {
+ const id = type + "COA" + i;
+ const g = document.getElementById(type+"Emblems");
+
+ if (emblems.selectAll("use").size()) {
+ const size = +g.getAttribute("font-size") || 50;
+ const use = ``;
+ g.insertAdjacentHTML("beforeend", use);
+ }
+ if (layerIsOn("toggleEmblems")) trigger(id, coa);
+ }
+
+ return {trigger, add};
+
+})));
diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js
index cf18c94b..3fcf8170 100644
--- a/modules/cultures-generator.js
+++ b/modules/cultures-generator.js
@@ -7,7 +7,7 @@
let cells;
const generate = function() {
- console.time('generateCultures');
+ TIME && console.time('generateCultures');
cells = pack.cells;
cells.culture = new Uint16Array(cells.i.length); // cell cultures
let count = Math.min(+culturesInput.value, +culturesSet.selectedOptions[0].dataset.max);
@@ -16,8 +16,8 @@
if (populated.length < count * 25) {
count = Math.floor(populated.length / 50);
if (!count) {
- console.warn(`There are no populated cells. Cannot generate cultures`);
- pack.cultures = [{name:"Wildlands", i:0, base:1}];
+ WARN && console.warn(`There are no populated cells. Cannot generate cultures`);
+ pack.cultures = [{name:"Wildlands", i:0, base:1, shield:"round"}];
alertMessage.innerHTML = `
The climate is harsh and people cannot live in this world.
No cultures, states and burgs will be created.
@@ -27,7 +27,7 @@
});
return;
} else {
- console.warn(`Not enought populated cells (${populated.length}). Will generate only ${count} cultures`);
+ WARN && console.warn(`Not enough populated cells (${populated.length}). Will generate only ${count} cultures`);
alertMessage.innerHTML = `
There are only ${populated.length} populated cells and it's insufficient livable area.
Only ${count} out of ${culturesInput.value} requested cultures will be generated.
@@ -41,6 +41,7 @@
const cultures = pack.cultures = getRandomCultures(count);
const centers = d3.quadtree();
const colors = getColors(count);
+ const emblemShape = document.getElementById("emblemShape").value;
cultures.forEach(function(c, i) {
const cell = c.center = placeCenter(c.sort ? c.sort : (i) => cells.s[i]);
@@ -54,8 +55,26 @@
c.origin = 0;
c.code = getCode(c.name);
cells.culture[cell] = i+1;
+ if (emblemShape === "random") c.shield = getRandomShiled();
+ else if (emblemShape !== "culture" && emblemShape !== "state") c.shield = emblemShape;
});
+ function getRandomShiled() {
+ const shields = {
+ types: {basic: 10, regional: 2, historical: 1, specific: 1, banner: 1, simple: 2, fantasy: 1, middleEarth: 0},
+ basic: {heater: 12, spanish: 6, french: 1},
+ regional: {horsehead: 1, horsehead2: 1, polish: 1, hessen: 1, swiss: 1},
+ historical: {boeotian: 1, roman: 2, kite: 1, oldFrench: 5, renaissance: 2, baroque: 2},
+ specific: {targe: 1, targe2: 0, pavise: 5, wedged: 10},
+ banner: {flag: 1, pennon: 0, guidon: 0, banner: 0, dovetail: 1, gonfalon: 5, pennant: 0},
+ simple: {round: 12, oval: 6, vesicaPiscis: 1, square: 1, diamond: 2, no: 0},
+ fantasy: {fantasy1: 2, fantasy2: 2, fantasy3: 1, fantasy4: 1, fantasy5: 3},
+ middleEarth: {noldor: 1, gondor: 1, easterling: 1, erebor: 1, ironHills: 1, urukHai: 1, moriaOrc: 1}
+ }
+ const type = rw(shields.types);
+ return rw(shields[type]);
+ }
+
function placeCenter(v) {
let c, spacing = (graphWidth + graphHeight) / 2 / count;
const sorted = [...populated].sort((a, b) => v(b) - v(a)), max = Math.floor(sorted.length / 2);
@@ -65,10 +84,13 @@
}
// the first culture with id 0 is for wildlands
- cultures.unshift({name:"Wildlands", i:0, base:1, origin:null});
+ cultures.unshift({name:"Wildlands", i:0, base:1, origin:null, shield:"round"});
// make sure all bases exist in nameBases
- if (!nameBases.length) {console.error("Name base is empty, default nameBases will be applied"); nameBases = Names.getNameBases();}
+ if (!nameBases.length) {
+ ERROR && console.error("Name base is empty, default nameBases will be applied");
+ nameBases = Names.getNameBases();
+ }
cultures.forEach(c => c.base = c.base % nameBases.length);
function getRandomCultures(c) {
@@ -108,11 +130,12 @@
return rn((Math.random() * powerInput.value / 2 + 1) * base, 1);
}
- console.timeEnd('generateCultures');
+ TIME && console.timeEnd('generateCultures');
}
// assign a unique two-letters code (abbreviation)
function getCode(name) {
+ name = name.replace(/[()]/g, "");
const words = name.split(" "), letters = words.join("");
let code = words.length === 2 ? words[0][0]+words[1][0] : letters.slice(0,2);
for (let i=1; i < letters.length-1 && pack.cultures.some(c => c.code === code); i++) {
@@ -148,164 +171,145 @@
const td = (cell, goal) => {const d = Math.abs(temp[cells.g[cell]] - goal); return d ? d+1 : 1;} // temperature difference fee
const bd = (cell, biomes, fee = 4) => biomes.includes(cells.biome[cell]) ? 1 : fee; // biome difference fee
const sf = (cell, fee = 4) => cells.haven[cell] && pack.features[cells.f[cells.haven[cell]]].type !== "lake" ? 1 : fee; // not on sea coast fee
- // https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
if (culturesSet.value === "european") {
return [
- {name:"Shwazen", base:0, odd: 1, sort: i => n(i) / td(i, 10) / bd(i, [6, 8])},
- {name:"Angshire", base:1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i)},
- {name:"Luari", base:2, odd: 1, sort: i => n(i) / td(i, 12) / bd(i, [6, 8])},
- {name:"Tallian", base:3, odd: 1, sort: i => n(i) / td(i, 15)},
- {name:"Astellian", base:4, odd: 1, sort: i => n(i) / td(i, 16)},
- {name:"Slovan", base:5, odd: 1, sort: i => n(i) / td(i, 6) * t[i]},
- {name:"Norse", base:6, odd: 1, sort: i => n(i) / td(i, 5)},
- {name:"Elladan", base:7, odd: 1, sort: i => n(i) / td(i, 18) * h[i]},
- {name:"Romian", base:8, odd: .2, sort: i => n(i) / td(i, 15) / t[i]},
- {name:"Soumi", base:9, odd: 1, sort: i => n(i) / td(i, 5) / bd(i, [9]) * t[i]},
- {name:"Portuzian", base:13, odd: 1, sort: i => n(i) / td(i, 17) / sf(i)},
- {name:"Vengrian", base: 15, odd: 1, sort: i => n(i) / td(i, 11) / bd(i, [4]) * t[i]},
- {name:"Turchian", base: 16, odd: .05, sort: i => n(i) / td(i, 14)},
- {name:"Euskati", base: 20, odd: .05, sort: i => n(i) / td(i, 15) * h[i]},
- {name:"Keltan", base: 22, odd: .05, sort: i => n(i) / td(i, 11) / bd(i, [6, 8]) * t[i]}
+ {name:"Shwazen", base:0, odd: 1, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield:"swiss"},
+ {name:"Angshire", base:1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i), shield:"wedged"},
+ {name:"Luari", base:2, odd: 1, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield:"french"},
+ {name:"Tallian", base:3, odd: 1, sort: i => n(i) / td(i, 15), shield:"horsehead"},
+ {name:"Astellian", base:4, odd: 1, sort: i => n(i) / td(i, 16), shield:"spanish"},
+ {name:"Slovan", base:5, odd: 1, sort: i => n(i) / td(i, 6) * t[i], shield:"polish"},
+ {name:"Norse", base:6, odd: 1, sort: i => n(i) / td(i, 5), shield:"heater"},
+ {name:"Elladan", base:7, odd: 1, sort: i => n(i) / td(i, 18) * h[i], shield:"boeotian"},
+ {name:"Romian", base:8, odd: .2, sort: i => n(i) / td(i, 15) / t[i], shield:"roman"},
+ {name:"Soumi", base:9, odd: 1, sort: i => n(i) / td(i, 5) / bd(i, [9]) * t[i], shield:"pavise"},
+ {name:"Portuzian", base:13, odd: 1, sort: i => n(i) / td(i, 17) / sf(i), shield:"renaissance"},
+ {name:"Vengrian", base: 15, odd: 1, sort: i => n(i) / td(i, 11) / bd(i, [4]) * t[i], shield:"horsehead2"},
+ {name:"Turchian", base: 16, odd: .05, sort: i => n(i) / td(i, 14), shield:"round"},
+ {name:"Euskati", base: 20, odd: .05, sort: i => n(i) / td(i, 15) * h[i], shield:"oldFrench"},
+ {name:"Keltan", base: 22, odd: .05, sort: i => n(i) / td(i, 11) / bd(i, [6, 8]) * t[i], shield:"oval"}
];
}
if (culturesSet.value === "oriental") {
return [
- {name:"Koryo", base:10, odd: 1, sort: i => n(i) / td(i, 12) / t[i]},
- {name:"Hantzu", base:11, odd: 1, sort: i => n(i) / td(i, 13)},
- {name:"Yamoto", base:12, odd: 1, sort: i => n(i) / td(i, 15) / t[i]},
- {name:"Turchian", base: 16, odd: 1, sort: i => n(i) / td(i, 12)},
- {name:"Berberan", base: 17, odd: .2, sort: i => n(i) / td(i, 19) / bd(i, [1, 2, 3], 7) * t[i]},
- {name:"Eurabic", base: 18, odd: 1, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i]},
- {name:"Efratic", base: 23, odd: .1, sort: i => n(i) / td(i, 22) * t[i]},
- {name:"Tehrani", base: 24, odd: 1, sort: i => n(i) / td(i, 18) * h[i]},
- {name:"Maui", base: 25, odd: .2, sort: i => n(i) / td(i, 24) / sf(i) / t[i]},
- {name:"Carnatic", base: 26, odd: .5, sort: i => n(i) / td(i, 26)},
- {name:"Vietic", base: 29, odd: .8, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i]},
- {name:"Guantzu", base:30, odd: .5, sort: i => n(i) / td(i, 17)},
- {name:"Ulus", base:31, odd: 1, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i]}
+ {name:"Koryo", base:10, odd: 1, sort: i => n(i) / td(i, 12) / t[i], shield:"round"},
+ {name:"Hantzu", base:11, odd: 1, sort: i => n(i) / td(i, 13), shield:"banner"},
+ {name:"Yamoto", base:12, odd: 1, sort: i => n(i) / td(i, 15) / t[i], shield:"round"},
+ {name:"Turchian", base: 16, odd: 1, sort: i => n(i) / td(i, 12), shield:"round"},
+ {name:"Berberan", base: 17, odd: .2, sort: i => n(i) / td(i, 19) / bd(i, [1, 2, 3], 7) * t[i], shield:"oval"},
+ {name:"Eurabic", base: 18, odd: 1, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i], shield:"oval"},
+ {name:"Efratic", base: 23, odd: .1, sort: i => n(i) / td(i, 22) * t[i], shield:"round"},
+ {name:"Tehrani", base: 24, odd: 1, sort: i => n(i) / td(i, 18) * h[i], shield:"round"},
+ {name:"Maui", base: 25, odd: .2, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield:"vesicaPiscis"},
+ {name:"Carnatic", base: 26, odd: .5, sort: i => n(i) / td(i, 26), shield:"round"},
+ {name:"Vietic", base: 29, odd: .8, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield:"banner"},
+ {name:"Guantzu", base:30, odd: .5, sort: i => n(i) / td(i, 17), shield:"banner"},
+ {name:"Ulus", base:31, odd: 1, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i], shield:"banner"}
];
}
if (culturesSet.value === "english") {
const getName = () => Names.getBase(1, 5, 9, "", 0);
return [
- {name:getName(), base:1, odd: 1},
- {name:getName(), base:1, odd: 1},
- {name:getName(), base:1, odd: 1},
- {name:getName(), base:1, odd: 1},
- {name:getName(), base:1, odd: 1},
- {name:getName(), base:1, odd: 1},
- {name:getName(), base:1, odd: 1},
- {name:getName(), base:1, odd: 1},
- {name:getName(), base:1, odd: 1},
- {name:getName(), base:1, odd: 1}
+ {name:getName(), base:1, odd: 1, shield:"heater"},
+ {name:getName(), base:1, odd: 1, shield:"wedged"},
+ {name:getName(), base:1, odd: 1, shield:"swiss"},
+ {name:getName(), base:1, odd: 1, shield:"oldFrench"},
+ {name:getName(), base:1, odd: 1, shield:"swiss"},
+ {name:getName(), base:1, odd: 1, shield:"spanish"},
+ {name:getName(), base:1, odd: 1, shield:"hessen"},
+ {name:getName(), base:1, odd: 1, shield:"fantasy5"},
+ {name:getName(), base:1, odd: 1, shield:"fantasy4"},
+ {name:getName(), base:1, odd: 1, shield:"fantasy1"}
];
}
if (culturesSet.value === "antique") {
return [
- {name:"Roman", base:8, odd: 1, sort: i => n(i) / td(i, 14) / t[i]}, // Roman
- {name:"Roman", base:8, odd: 1, sort: i => n(i) / td(i, 15) / sf(i)}, // Roman
- {name:"Roman", base:8, odd: 1, sort: i => n(i) / td(i, 16) / sf(i)}, // Roman
- {name:"Roman", base:8, odd: 1, sort: i => n(i) / td(i, 17) / t[i]}, // Roman
- {name:"Hellenic", base:7, odd: 1, sort: i => n(i) / td(i, 18) / sf(i) * h[i]}, // Greek
- {name:"Hellenic", base:7, odd: 1, sort: i => n(i) / td(i, 19) / sf(i) * h[i]}, // Greek
- {name:"Macedonian", base:7, odd: .5, sort: i => n(i) / td(i, 12) * h[i]}, // Greek
- {name:"Celtic", base:22, odd: 1, sort: i => n(i) / td(i, 11) ** .5 / bd(i, [6, 8])},
- {name:"Germanic", base:0, odd: 1, sort: i => n(i) / td(i, 10) ** .5 / bd(i, [6, 8])},
- {name:"Persian", base:24, odd: .8, sort: i => n(i) / td(i, 18) * h[i]}, // Iranian
- {name:"Scythian", base:24, odd: .5, sort: i => n(i) / td(i, 11) ** .5 / bd(i, [4])}, // Iranian
- {name:"Cantabrian", base: 20, odd: .5, sort: i => n(i) / td(i, 16) * h[i]}, // Basque
- {name:"Estian", base: 9, odd: .2, sort: i => n(i) / td(i, 5) * t[i]}, // Finnic
- {name:"Carthaginian", base: 17, odd: .3, sort: i => n(i) / td(i, 19) / sf(i)}, // Berber
- {name:"Mesopotamian", base: 23, odd: .2, sort: i => n(i) / td(i, 22) / bd(i, [1, 2, 3])} // Mesopotamian
+ {name:"Roman", base:8, odd: 1, sort: i => n(i) / td(i, 14) / t[i], shield:"roman"}, // Roman
+ {name:"Roman", base:8, odd: 1, sort: i => n(i) / td(i, 15) / sf(i), shield:"roman"}, // Roman
+ {name:"Roman", base:8, odd: 1, sort: i => n(i) / td(i, 16) / sf(i), shield:"roman"}, // Roman
+ {name:"Roman", base:8, odd: 1, sort: i => n(i) / td(i, 17) / t[i], shield:"roman"}, // Roman
+ {name:"Hellenic", base:7, odd: 1, sort: i => n(i) / td(i, 18) / sf(i) * h[i], shield:"boeotian"}, // Greek
+ {name:"Hellenic", base:7, odd: 1, sort: i => n(i) / td(i, 19) / sf(i) * h[i], shield:"boeotian"}, // Greek
+ {name:"Macedonian", base:7, odd: .5, sort: i => n(i) / td(i, 12) * h[i], shield:"round"}, // Greek
+ {name:"Celtic", base:22, odd: 1, sort: i => n(i) / td(i, 11) ** .5 / bd(i, [6, 8]), shield:"round"},
+ {name:"Germanic", base:0, odd: 1, sort: i => n(i) / td(i, 10) ** .5 / bd(i, [6, 8]), shield:"round"},
+ {name:"Persian", base:24, odd: .8, sort: i => n(i) / td(i, 18) * h[i], shield:"oval"}, // Iranian
+ {name:"Scythian", base:24, odd: .5, sort: i => n(i) / td(i, 11) ** .5 / bd(i, [4]), shield:"round"}, // Iranian
+ {name:"Cantabrian", base: 20, odd: .5, sort: i => n(i) / td(i, 16) * h[i], shield:"oval"}, // Basque
+ {name:"Estian", base: 9, odd: .2, sort: i => n(i) / td(i, 5) * t[i], shield:"pavise"}, // Finnic
+ {name:"Carthaginian", base: 17, odd: .3, sort: i => n(i) / td(i, 19) / sf(i), shield:"oval"}, // Berber
+ {name:"Mesopotamian", base: 23, odd: .2, sort: i => n(i) / td(i, 22) / bd(i, [1, 2, 3]), shield:"oval"} // Mesopotamian
];
}
if (culturesSet.value === "highFantasy") {
return [
// fantasy races
- {name:"Quenian", base: 33, odd: 1, sort: i => n(i) / bd(i, [6,7,8,9], 10) * t[i]}, // Elves
- {name:"Eldar", base: 33, odd: 1, sort: i => n(i) / bd(i, [6,7,8,9], 10) * t[i]}, // Elves
- {name:"Lorian", base: 33, odd: .5, sort: i => n(i) / bd(i, [6,7,8,9], 10)}, // Elves
- {name:"Trow", base: 34, odd: .9, sort: i => n(i) / bd(i, [7,8,9,12], 10) * t[i]}, // Dark Elves
- {name:"Dokalfar", base: 34, odd: .3, sort: i => n(i) / bd(i, [7,8,9,12], 10) * t[i]}, // Dark Elves
- {name:"Durinn", base: 35, odd: 1, sort: i => n(i) + h[i]}, // Dwarven
- {name:"Khazadur", base: 35, odd: 1, sort: i => n(i) + h[i]}, // Dwarven
- {name:"Kobblin", base: 36, odd: 1, sort: i => t[i] - s[i]}, // Goblin
- {name:"Uruk", base: 37, odd: 1, sort: i => h[i] * t[i]}, // Orc
- {name:"Ugluk", base: 37, odd: .7, sort: i => h[i] * t[i] / bd(i, [1,2,10,11])}, // Orc
- {name:"Yotunn", base: 38, odd: .9, sort: i => td(i, -10)}, // Giant
- {name:"Drake", base: 39, odd: .7, sort: i => -s[i]}, // Draconic
- {name:"Rakhnid", base: 40, odd: .9, sort: i => t[i] - s[i]}, // Arachnid
- {name:"Aj'Snaga", base: 41, odd: .9, sort: i => n(i) / bd(i, [12], 10)}, // Serpents
- // common fantasy human
- {name:"Gozdor", base:32, odd: 1, sort: i => n(i) / td(i, 18)},
- {name:"Anor", base:32, odd: 1, sort: i => n(i) / td(i, 10)},
- {name:"Dail", base:32, odd: 1, sort: i => n(i) / td(i, 13)},
- {name:"Duland", base:32, odd: 1, sort: i => n(i) / td(i, 14)},
- {name:"Rohand", base:32, odd: 1, sort: i => n(i) / td(i, 16)},
- // rare real-world western
- {name:"Norse", base:6, odd: .5, sort: i => n(i) / td(i, 5) / sf(i)},
- {name:"Izenlute", base:0, odd: .1, sort: i => n(i) / td(i, 5)},
- {name:"Lurian", base:2, odd: .1, sort: i => n(i) / td(i, 12) / bd(i, [6, 8])},
- {name:"Getalian", base:3, odd: .1, sort: i => n(i) / td(i, 15)},
- {name:"Astelan", base:4, odd: .05, sort: i => n(i) / td(i, 16)},
- // rare real-world exotic
- {name:"Yoruba", base:21, odd: .05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7])},
- {name:"Ryoko", base:10, odd: .05, sort: i => n(i) / td(i, 12) / t[i]},
- {name:"Toyamo", base:12, odd: .05, sort: i => n(i) / td(i, 15) / t[i]},
- {name:"Guan-Tsu", base:30, odd: .05, sort: i => n(i) / td(i, 17)},
- {name:"Ulus-Khan", base:31, odd: .05, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i]},
- {name:"Turan", base: 16, odd: .05, sort: i => n(i) / td(i, 13)},
- {name:"Al'Uma", base: 18, odd: .05, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i]},
- {name:"Druidas", base: 22, odd: .05, sort: i => n(i) / td(i, 11) / bd(i, [6, 8]) * t[i]},
- {name:"Gorodian", base:5, odd: .05, sort: i => n(i) / td(i, 6) * t[i]}
+ {name:"Quenian (Elfish)", base: 33, odd: 1, sort: i => n(i) / bd(i, [6,7,8,9], 10) * t[i], shield:"gondor"}, // Elves
+ {name:"Eldar (Elfish)", base: 33, odd: 1, sort: i => n(i) / bd(i, [6,7,8,9], 10) * t[i], shield:"noldor"}, // Elves
+ {name:"Trow (Dark Elfish)", base: 34, odd: .9, sort: i => n(i) / bd(i, [7,8,9,12], 10) * t[i], shield:"hessen"}, // Dark Elves
+ {name:"Lothian (Dark Elfish)", base: 34, odd: .3, sort: i => n(i) / bd(i, [7,8,9,12], 10) * t[i], shield:"wedged"}, // Dark Elves
+ {name:"Dunirr (Dwarven)", base: 35, odd: 1, sort: i => n(i) + h[i], shield:"ironHills"}, // Dwarfs
+ {name:"Khazadur (Dwarven)", base: 35, odd: 1, sort: i => n(i) + h[i], shield:"erebor"}, // Dwarfs
+ {name:"Kobold (Goblin)", base: 36, odd: 1, sort: i => t[i] - s[i], shield:"moriaOrc"}, // Goblin
+ {name:"Uruk (Orkish)", base: 37, odd: 1, sort: i => h[i] * t[i], shield:"urukHai"}, // Orc
+ {name:"Ugluk (Orkish)", base: 37, odd: .5, sort: i => h[i] * t[i] / bd(i, [1,2,10,11]), shield:"moriaOrc"}, // Orc
+ {name:"Yotunn (Giants)", base: 38, odd: .7, sort: i => td(i, -10), shield:"pavise"}, // Giant
+ {name:"Rake (Drakonic)", base: 39, odd: .7, sort: i => -s[i], shield:"fantasy2"}, // Draconic
+ {name:"Arago (Arachnid)", base: 40, odd: .7, sort: i => t[i] - s[i], shield:"horsehead2"}, // Arachnid
+ {name:"Aj'Snaga (Serpents)", base: 41, odd: .7, sort: i => n(i) / bd(i, [12], 10), shield:"fantasy1"}, // Serpents
+ // fantasy human
+ {name:"Anor (Human)", base:32, odd: 1, sort: i => n(i) / td(i, 10), shield:"fantasy5"},
+ {name:"Dail (Human)", base:32, odd: 1, sort: i => n(i) / td(i, 13), shield:"roman"},
+ {name:"Rohand (Human)", base:16, odd: 1, sort: i => n(i) / td(i, 16), shield:"round"},
+ {name:"Dulandir (Human)", base:31, odd: 1, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i], shield:"easterling"},
];
}
if (culturesSet.value === "darkFantasy") {
return [
// common real-world English
- {name:"Angshire", base:1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i)},
- {name:"Enlandic", base:1, odd: 1, sort: i => n(i) / td(i, 12)},
- {name:"Westen", base:1, odd: 1, sort: i => n(i) / td(i, 10)},
- {name:"Nortumbic", base:1, odd: 1, sort: i => n(i) / td(i, 7)},
- {name:"Mercian", base:1, odd: 1, sort: i => n(i) / td(i, 9)},
- {name:"Kentian", base:1, odd: 1, sort: i => n(i) / td(i, 12)},
+ {name:"Angshire", base:1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i), shield:"heater"},
+ {name:"Enlandic", base:1, odd: 1, sort: i => n(i) / td(i, 12), shield:"heater"},
+ {name:"Westen", base:1, odd: 1, sort: i => n(i) / td(i, 10), shield:"heater"},
+ {name:"Nortumbic", base:1, odd: 1, sort: i => n(i) / td(i, 7), shield:"heater"},
+ {name:"Mercian", base:1, odd: 1, sort: i => n(i) / td(i, 9), shield:"heater"},
+ {name:"Kentian", base:1, odd: 1, sort: i => n(i) / td(i, 12), shield:"heater"},
// rare real-world western
- {name:"Norse", base:6, odd: .7, sort: i => n(i) / td(i, 5) / sf(i)},
- {name:"Schwarzen", base:0, odd: .3, sort: i => n(i) / td(i, 10) / bd(i, [6, 8])},
- {name:"Luarian", base:2, odd: .3, sort: i => n(i) / td(i, 12) / bd(i, [6, 8])},
- {name:"Hetallian", base:3, odd: .3, sort: i => n(i) / td(i, 15)},
- {name:"Astellian", base:4, odd: .3, sort: i => n(i) / td(i, 16)},
+ {name:"Norse", base:6, odd: .7, sort: i => n(i) / td(i, 5) / sf(i), shield:"oldFrench"},
+ {name:"Schwarzen", base:0, odd: .3, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield:"gonfalon"},
+ {name:"Luarian", base:2, odd: .3, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield:"oldFrench"},
+ {name:"Hetallian", base:3, odd: .3, sort: i => n(i) / td(i, 15), shield:"oval"},
+ {name:"Astellian", base:4, odd: .3, sort: i => n(i) / td(i, 16), shield:"spanish"},
// rare real-world exotic
- {name:"Kiswaili", base:28, odd: .05, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7])},
- {name:"Yoruba", base:21, odd: .05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7])},
- {name:"Koryo", base:10, odd: .05, sort: i => n(i) / td(i, 12) / t[i]},
- {name:"Hantzu", base:11, odd: .05, sort: i => n(i) / td(i, 13)},
- {name:"Yamoto", base:12, odd: .05, sort: i => n(i) / td(i, 15) / t[i]},
- {name:"Guantzu", base:30, odd: .05, sort: i => n(i) / td(i, 17)},
- {name:"Ulus", base:31, odd: .05, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i]},
- {name:"Turan", base: 16, odd: .05, sort: i => n(i) / td(i, 12)},
- {name:"Berberan", base: 17, odd: .05, sort: i => n(i) / td(i, 19) / bd(i, [1, 2, 3], 7) * t[i]},
- {name:"Eurabic", base: 18, odd: .05, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i]},
- {name:"Slovan", base:5, odd: .05, sort: i => n(i) / td(i, 6) * t[i]},
- {name:"Keltan", base: 22, odd: .1, sort: i => n(i) / td(i, 11) ** .5 / bd(i, [6, 8])},
- {name:"Elladan", base:7, odd: .2, sort: i => n(i) / td(i, 18) / sf(i) * h[i]},
- {name:"Romian", base:8, odd: .2, sort: i => n(i) / td(i, 14) / t[i]},
+ {name:"Kiswaili", base:28, odd: .05, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield:"vesicaPiscis"},
+ {name:"Yoruba", base:21, odd: .05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield:"vesicaPiscis"},
+ {name:"Koryo", base:10, odd: .05, sort: i => n(i) / td(i, 12) / t[i], shield:"round"},
+ {name:"Hantzu", base:11, odd: .05, sort: i => n(i) / td(i, 13), shield:"banner"},
+ {name:"Yamoto", base:12, odd: .05, sort: i => n(i) / td(i, 15) / t[i], shield:"round"},
+ {name:"Guantzu", base:30, odd: .05, sort: i => n(i) / td(i, 17), shield:"banner"},
+ {name:"Ulus", base:31, odd: .05, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i], shield:"banner"},
+ {name:"Turan", base: 16, odd: .05, sort: i => n(i) / td(i, 12), shield:"round"},
+ {name:"Berberan", base: 17, odd: .05, sort: i => n(i) / td(i, 19) / bd(i, [1, 2, 3], 7) * t[i], shield:"round"},
+ {name:"Eurabic", base: 18, odd: .05, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i], shield:"round"},
+ {name:"Slovan", base:5, odd: .05, sort: i => n(i) / td(i, 6) * t[i], shield:"round"},
+ {name:"Keltan", base: 22, odd: .1, sort: i => n(i) / td(i, 11) ** .5 / bd(i, [6, 8]), shield:"vesicaPiscis"},
+ {name:"Elladan", base:7, odd: .2, sort: i => n(i) / td(i, 18) / sf(i) * h[i], shield:"boeotian"},
+ {name:"Romian", base:8, odd: .2, sort: i => n(i) / td(i, 14) / t[i], shield:"roman"},
// fantasy races
- {name:"Eldar", base: 33, odd: .5, sort: i => n(i) / bd(i, [6,7,8,9], 10) * t[i]}, // Elves
- {name:"Trow", base: 34, odd: .8, sort: i => n(i) / bd(i, [7,8,9,12], 10) * t[i]}, // Dark Elves
- {name:"Durinn", base: 35, odd: .8, sort: i => n(i) + h[i]}, // Dwarven
- {name:"Kobblin", base: 36, odd: .8, sort: i => t[i] - s[i]}, // Goblin
- {name:"Uruk", base: 37, odd: .8, sort: i => h[i] * t[i] / bd(i, [1,2,10,11])}, // Orc
- {name:"Yotunn", base: 38, odd: .8, sort: i => td(i, -10)}, // Giant
- {name:"Drake", base: 39, odd: .9, sort: i => -s[i]}, // Draconic
- {name:"Rakhnid", base: 40, odd: .9, sort: i => t[i] - s[i]}, // Arachnid
- {name:"Aj'Snaga", base: 41, odd: .9, sort: i => n(i) / bd(i, [12], 10)}, // Serpents
+ {name:"Eldar", base: 33, odd: .5, sort: i => n(i) / bd(i, [6,7,8,9], 10) * t[i], shield:"fantasy5"}, // Elves
+ {name:"Trow", base: 34, odd: .8, sort: i => n(i) / bd(i, [7,8,9,12], 10) * t[i], shield:"hessen"}, // Dark Elves
+ {name:"Durinn", base: 35, odd: .8, sort: i => n(i) + h[i], shield:"erebor"}, // Dwarven
+ {name:"Kobblin", base: 36, odd: .8, sort: i => t[i] - s[i], shield:"moriaOrc"}, // Goblin
+ {name:"Uruk", base: 37, odd: .8, sort: i => h[i] * t[i] / bd(i, [1,2,10,11]), shield:"urukHai"}, // Orc
+ {name:"Yotunn", base: 38, odd: .8, sort: i => td(i, -10), shield:"pavise"}, // Giant
+ {name:"Drake", base: 39, odd: .9, sort: i => -s[i], shield:"fantasy2"}, // Draconic
+ {name:"Rakhnid", base: 40, odd: .9, sort: i => t[i] - s[i], shield:"horsehead2"}, // Arachnid
+ {name:"Aj'Snaga", base: 41, odd: .9, sort: i => n(i) / bd(i, [12], 10), shield:"fantasy1"}, // Serpents
]
}
@@ -318,44 +322,44 @@
// all-world
return [
- {name:"Shwazen", base:0, odd: .7, sort: i => n(i) / td(i, 10) / bd(i, [6, 8])},
- {name:"Angshire", base:1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i)},
- {name:"Luari", base:2, odd: .6, sort: i => n(i) / td(i, 12) / bd(i, [6, 8])},
- {name:"Tallian", base:3, odd: .6, sort: i => n(i) / td(i, 15)},
- {name:"Astellian", base:4, odd: .6, sort: i => n(i) / td(i, 16)},
- {name:"Slovan", base:5, odd: .7, sort: i => n(i) / td(i, 6) * t[i]},
- {name:"Norse", base:6, odd: .7, sort: i => n(i) / td(i, 5)},
- {name:"Elladan", base:7, odd: .7, sort: i => n(i) / td(i, 18) * h[i]},
- {name:"Romian", base:8, odd: .7, sort: i => n(i) / td(i, 15)},
- {name:"Soumi", base:9, odd: .3, sort: i => n(i) / td(i, 5) / bd(i, [9]) * t[i]},
- {name:"Koryo", base:10, odd: .1, sort: i => n(i) / td(i, 12) / t[i]},
- {name:"Hantzu", base:11, odd: .1, sort: i => n(i) / td(i, 13)},
- {name:"Yamoto", base:12, odd: .1, sort: i => n(i) / td(i, 15) / t[i]},
- {name:"Portuzian", base:13, odd: .4, sort: i => n(i) / td(i, 17) / sf(i)},
- {name:"Nawatli", base:14, odd: .1, sort: i => h[i] / td(i, 18) / bd(i, [7])},
- {name:"Vengrian", base: 15, odd: .2, sort: i => n(i) / td(i, 11) / bd(i, [4]) * t[i]},
- {name:"Turchian", base: 16, odd: .2, sort: i => n(i) / td(i, 13)},
- {name:"Berberan", base: 17, odd: .1, sort: i => n(i) / td(i, 19) / bd(i, [1, 2, 3], 7) * t[i]},
- {name:"Eurabic", base: 18, odd: .2, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i]},
- {name:"Inuk", base: 19, odd: .05, sort: i => td(i, -1) / bd(i, [10, 11]) / sf(i)},
- {name:"Euskati", base: 20, odd: .05, sort: i => n(i) / td(i, 15) * h[i]},
- {name:"Yoruba", base: 21, odd: .05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7])},
- {name:"Keltan", base: 22, odd: .05, sort: i => n(i) / td(i, 11) / bd(i, [6, 8]) * t[i]},
- {name:"Efratic", base: 23, odd: .05, sort: i => n(i) / td(i, 22) * t[i]},
- {name:"Tehrani", base: 24, odd: .1, sort: i => n(i) / td(i, 18) * h[i]},
- {name:"Maui", base: 25, odd: .05, sort: i => n(i) / td(i, 24) / sf(i) / t[i]},
- {name:"Carnatic", base: 26, odd: .05, sort: i => n(i) / td(i, 26)},
- {name:"Inqan", base: 27, odd: .05, sort: i => h[i] / td(i, 13)},
- {name:"Kiswaili", base: 28, odd: .1, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7])},
- {name:"Vietic", base: 29, odd: .1, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i]},
- {name:"Guantzu", base:30, odd: .1, sort: i => n(i) / td(i, 17)},
- {name:"Ulus", base:31, odd: .1, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i]}
+ {name:"Shwazen", base:0, odd: .7, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield:"hessen"},
+ {name:"Angshire", base:1, odd: 1, sort: i => n(i) / td(i, 10) / sf(i), shield:"heater"},
+ {name:"Luari", base:2, odd: .6, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield:"oldFrench"},
+ {name:"Tallian", base:3, odd: .6, sort: i => n(i) / td(i, 15), shield:"horsehead2"},
+ {name:"Astellian", base:4, odd: .6, sort: i => n(i) / td(i, 16), shield:"spanish"},
+ {name:"Slovan", base:5, odd: .7, sort: i => n(i) / td(i, 6) * t[i], shield:"round"},
+ {name:"Norse", base:6, odd: .7, sort: i => n(i) / td(i, 5), shield:"heater"},
+ {name:"Elladan", base:7, odd: .7, sort: i => n(i) / td(i, 18) * h[i], shield:"boeotian"},
+ {name:"Romian", base:8, odd: .7, sort: i => n(i) / td(i, 15), shield:"roman"},
+ {name:"Soumi", base:9, odd: .3, sort: i => n(i) / td(i, 5) / bd(i, [9]) * t[i], shield:"pavise"},
+ {name:"Koryo", base:10, odd: .1, sort: i => n(i) / td(i, 12) / t[i], shield:"round"},
+ {name:"Hantzu", base:11, odd: .1, sort: i => n(i) / td(i, 13), shield:"banner"},
+ {name:"Yamoto", base:12, odd: .1, sort: i => n(i) / td(i, 15) / t[i], shield:"round"},
+ {name:"Portuzian", base:13, odd: .4, sort: i => n(i) / td(i, 17) / sf(i), shield:"spanish"},
+ {name:"Nawatli", base:14, odd: .1, sort: i => h[i] / td(i, 18) / bd(i, [7]), shield:"square"},
+ {name:"Vengrian", base: 15, odd: .2, sort: i => n(i) / td(i, 11) / bd(i, [4]) * t[i], shield:"wedged"},
+ {name:"Turchian", base: 16, odd: .2, sort: i => n(i) / td(i, 13), shield:"round"},
+ {name:"Berberan", base: 17, odd: .1, sort: i => n(i) / td(i, 19) / bd(i, [1, 2, 3], 7) * t[i], shield:"round"},
+ {name:"Eurabic", base: 18, odd: .2, sort: i => n(i) / td(i, 26) / bd(i, [1, 2], 7) * t[i], shield:"round"},
+ {name:"Inuk", base: 19, odd: .05, sort: i => td(i, -1) / bd(i, [10, 11]) / sf(i), shield:"square"},
+ {name:"Euskati", base: 20, odd: .05, sort: i => n(i) / td(i, 15) * h[i], shield:"spanish"},
+ {name:"Yoruba", base: 21, odd: .05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield:"vesicaPiscis"},
+ {name:"Keltan", base: 22, odd: .05, sort: i => n(i) / td(i, 11) / bd(i, [6, 8]) * t[i], shield:"vesicaPiscis"},
+ {name:"Efratic", base: 23, odd: .05, sort: i => n(i) / td(i, 22) * t[i], shield:"diamond"},
+ {name:"Tehrani", base: 24, odd: .1, sort: i => n(i) / td(i, 18) * h[i], shield:"round"},
+ {name:"Maui", base: 25, odd: .05, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield:"round"},
+ {name:"Carnatic", base: 26, odd: .05, sort: i => n(i) / td(i, 26), shield:"round"},
+ {name:"Inqan", base: 27, odd: .05, sort: i => h[i] / td(i, 13), shield:"square"},
+ {name:"Kiswaili", base: 28, odd: .1, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield:"vesicaPiscis"},
+ {name:"Vietic", base: 29, odd: .1, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield:"banner"},
+ {name:"Guantzu", base:30, odd: .1, sort: i => n(i) / td(i, 17), shield:"banner"},
+ {name:"Ulus", base:31, odd: .1, sort: i => n(i) / td(i, 5) / bd(i, [2, 4, 10], 7) * t[i], shield:"banner"}
];
}
// expand cultures across the map (Dijkstra-like algorithm)
const expand = function() {
- console.time('expandCultures');
+ TIME && console.time('expandCultures');
cells = pack.cells;
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
@@ -384,15 +388,10 @@
if (cells.s[e] > 0) cells.culture[e] = c; // assign culture to populated cell
cost[e] = totalCost;
queue.queue({e, p:totalCost, c});
-
- //debug.append("text").attr("x", (cells.p[n][0]+cells.p[e][0])/2 - 1).attr("y", (cells.p[n][1]+cells.p[e][1])/2 - 1).text(rn(totalCost-p)).attr("font-size", .8);
- //const points = [cells.p[n][0], cells.p[n][1], (cells.p[n][0]+cells.p[e][0])/2, (cells.p[n][1]+cells.p[e][1])/2, cells.p[e][0], cells.p[e][1]];
- //debug.append("polyline").attr("points", points.toString()).attr("marker-mid", "url(#arrow)").attr("opacity", .6);
}
});
}
- //debug.selectAll(".text").data(cost).enter().append("text").attr("x", (d, e) => cells.p[e][0]-1).attr("y", (d, e) => cells.p[e][1]-1).text(d => d ? rn(d) : "").attr("font-size", 2);
- console.timeEnd('expandCultures');
+ TIME && console.timeEnd('expandCultures');
}
function getBiomeCost(c, biome, type) {
diff --git a/modules/heightmap-generator.js b/modules/heightmap-generator.js
index dc2cf52b..e469b4dc 100644
--- a/modules/heightmap-generator.js
+++ b/modules/heightmap-generator.js
@@ -7,7 +7,7 @@
let cells, p;
const generate = function() {
- console.time('generateHeightmap');
+ TIME && console.time('generateHeightmap');
cells = grid.cells, p = grid.points;
cells.h = new Uint8Array(grid.points.length);
@@ -25,7 +25,7 @@
case "Shattered": templateShattered(); break;
}
- console.timeEnd('generateHeightmap');
+ TIME && console.timeEnd('generateHeightmap');
}
// parse template step
@@ -507,7 +507,7 @@
}
function getPointInRange(range, length) {
- if (typeof range !== "string") {console.error("Range should be a string"); return;}
+ if (typeof range !== "string") {ERROR && console.error("Range should be a string"); return;}
const min = range.split("-")[0]/100 || 0;
const max = range.split("-")[1]/100 || 100;
return rand(min * length, max * length);
diff --git a/modules/military-generator.js b/modules/military-generator.js
index a4b578e6..2102e2b9 100644
--- a/modules/military-generator.js
+++ b/modules/military-generator.js
@@ -7,7 +7,7 @@
let cells, p, states;
const generate = function() {
- console.time("generateMilitaryForces");
+ TIME && console.time("generateMilitaryForces");
cells = pack.cells, p = cells.p, states = pack.states;
const valid = states.filter(s => s.i && !s.removed); // valid states
if (!options.military) options.military = getDefaultOptions();
@@ -52,7 +52,7 @@
for (const unit of options.military) {
if (!stateModifier[unit.type]) continue;
let modifier = stateModifier[unit.type][s.type] || 1;
- if (unit.type === "mounted" && s.form === "Horde") modifier *= 2; else
+ if (unit.type === "mounted" && s.formName.includes("Horde")) modifier *= 2; else
if (unit.type === "naval" && s.form === "Republic") modifier *= 1.2;
temp[unit.name] = modifier * s.alert;
}
@@ -176,7 +176,7 @@
return regiments;
}
- console.timeEnd("generateMilitaryForces");
+ TIME && console.timeEnd("generateMilitaryForces");
}
const getDefaultOptions = function() {
diff --git a/modules/names-generator.js b/modules/names-generator.js
index 98776011..32367be1 100644
--- a/modules/names-generator.js
+++ b/modules/names-generator.js
@@ -56,13 +56,13 @@
// generate name using Markov's chain
const getBase = function(base, min, max, dupl) {
- if (base === undefined) {console.error("Please define a base"); return;}
+ if (base === undefined) {ERROR && console.error("Please define a base"); return;}
if (!chains[base]) updateChain(base);
const data = chains[base];
if (!data || data[""] === undefined) {
tip("Namesbase " + base + " is incorrect. Please check in namesbase editor", false, "error");
- console.error("Namebase " + base + " is incorrect!");
+ ERROR && console.error("Namebase " + base + " is incorrect!");
return "ERROR";
}
@@ -106,7 +106,7 @@
if (name.split(" ").some(part => part.length < 2)) name = name.split(" ").map((p,i) => i ? p.toLowerCase() : p).join("");
if (name.length < 2) {
- console.error("Name is too short! Random name will be selected");
+ ERROR && console.error("Name is too short! Random name will be selected");
name = ra(nameBases[base].b.split(","));
}
@@ -115,14 +115,14 @@
// generate name for culture
const getCulture = function(culture, min, max, dupl) {
- if (culture === undefined) {console.error("Please define a culture"); return;}
+ if (culture === undefined) {ERROR && console.error("Please define a culture"); return;}
const base = pack.cultures[culture].base;
return getBase(base, min, max, dupl);
}
// generate short name for culture
const getCultureShort = function(culture) {
- if (culture === undefined) {console.error("Please define a culture"); return;}
+ if (culture === undefined) {ERROR && console.error("Please define a culture"); return;}
return getBaseShort(pack.cultures[culture].base);
}
@@ -139,8 +139,8 @@
// generate state name based on capital or random name and culture-specific suffix
const getState = function(name, culture, base) {
- if (name === undefined) {console.error("Please define a base name"); return;}
- if (culture === undefined && base === undefined) {console.error("Please define a culture"); return;}
+ if (name === undefined) {ERROR && console.error("Please define a base name"); return;}
+ if (culture === undefined && base === undefined) {ERROR && console.error("Please define a culture"); return;}
if (base === undefined) base = pack.cultures[culture].base;
// exclude endings inappropriate for states name
diff --git a/modules/ocean-layers.js b/modules/ocean-layers.js
index b5be82f4..a39bbdd7 100644
--- a/modules/ocean-layers.js
+++ b/modules/ocean-layers.js
@@ -9,7 +9,7 @@
const OceanLayers = function OceanLayers() {
const outline = oceanLayers.attr("layers");
if (outline === "none") return;
- console.time("drawOceanLayers");
+ TIME && console.time("drawOceanLayers");
lineGen.curve(d3.curveBasisClosed);
cells = grid.cells, pointsN = grid.cells.i.length, vertices = grid.vertices;
@@ -51,7 +51,7 @@
return cells.v[i][cells.c[i].findIndex(c => cells.t[c] < t || !cells.t[c])];
}
- console.timeEnd("drawOceanLayers");
+ TIME && console.timeEnd("drawOceanLayers");
}
function randomizeOutline() {
@@ -89,7 +89,7 @@
if (v[0] !== undefined && v[0] !== prev && c0 !== c1) current = v[0];
else if (v[1] !== undefined && v[1] !== prev && c1 !== c2) current = v[1];
else if (v[2] !== undefined && v[2] !== prev && c0 !== c2) current = v[2];
- if (current === chain[chain.length - 1]) {console.error("Next vertex is not found"); break;}
+ if (current === chain[chain.length - 1]) {ERROR && console.error("Next vertex is not found"); break;}
}
chain.push(chain[0]); // push first vertex as the last one
return chain;
diff --git a/modules/relief-icons.js b/modules/relief-icons.js
index 0d806e8b..ab6d5490 100644
--- a/modules/relief-icons.js
+++ b/modules/relief-icons.js
@@ -5,7 +5,7 @@
}(this, (function () {'use strict';
const ReliefIcons = function() {
- console.time('drawRelief');
+ TIME && console.time('drawRelief');
terrain.selectAll("*").remove();
const density = terrain.attr("density") || .4;
const size = 1.6 * (terrain.attr("size") || 1);
@@ -35,7 +35,7 @@
let h = rn((4 + Math.random()) * size, 2);
const icon = getBiomeIcon(i, biomesData.icons[b]);
if (icon === "#relief-grass-1") h *= 1.3;
- relief.push({i: icon, x: rn(cx-h, 2), y: rn(cy-h, 2), s: h*2});
+ relief.push({i: icon, x: rn(cx-h, 2), y: rn(cy-h, 2), s: rn(h*2, 2)});
}
}
@@ -45,7 +45,7 @@
for (const [cx, cy] of poissonDiscSampler(e[0], e[1], e[2], e[3], radius)) {
if (!d3.polygonContains(polygon, [cx, cy])) continue;
- relief.push({i: icon, x: rn(cx-h, 2), y: rn(cy-h, 2), s: h*2});
+ relief.push({i: icon, x: rn(cx-h, 2), y: rn(cy-h, 2), s: rn(h*2, 2)});
}
}
@@ -64,12 +64,12 @@
void function renderRelief() {
let reliefHTML = "";
for (const r of relief) {
- reliefHTML += ``;
+ reliefHTML += ``;
}
terrain.html(reliefHTML);
}()
- console.timeEnd('drawRelief');
+ TIME && console.timeEnd('drawRelief');
}
function getBiomeIcon(i, b) {
diff --git a/modules/religions-generator.js b/modules/religions-generator.js
index 096b3260..1563f966 100644
--- a/modules/religions-generator.js
+++ b/modules/religions-generator.js
@@ -54,7 +54,7 @@
};
const generate = function() {
- console.time('generateReligions');
+ TIME && console.time('generateReligions');
const cells = pack.cells, states = pack.states, cultures = pack.cultures;
const religions = pack.religions = [];
cells.religion = new Uint16Array(cells.culture); // cell religion; initially based on culture
@@ -164,7 +164,7 @@
expandHeresies();
checkCenters();
- console.timeEnd('generateReligions');
+ TIME && console.timeEnd('generateReligions');
}
const add = function(center) {
@@ -279,6 +279,17 @@
});
}
+ function updateCultures() {
+ TIME && console.time('updateCulturesForReligions');
+ pack.religions = pack.religions.map( (religion, index) => {
+ if(index === 0) {
+ return religion;
+ }
+ return {...religion, culture: pack.cells.culture[religion.center]};
+ });
+ TIME && console.timeEnd('updateCulturesForReligions');
+ }
+
// assign a unique two-letters code (abbreviation)
function getCode(rawName) {
const name = rawName.replace("Old ", ""); // remove Old prefix
@@ -292,7 +303,7 @@
// get supreme deity name
const getDeityName = function(culture) {
- if (culture === undefined) {console.error("Please define a culture"); return;}
+ if (culture === undefined) {ERROR && console.error("Please define a culture"); return;}
const meaning = generateMeaning();
const cultureName = Names.getCulture(culture, null, null, "", .8);
return cultureName + ", The " + meaning;
@@ -350,6 +361,6 @@
return type() + " of the " + generateMeaning();
};
- return {generate, add, getDeityName, expandReligions};
+ return {generate, add, getDeityName, expandReligions, updateCultures};
})));
diff --git a/modules/river-generator.js b/modules/river-generator.js
index 3431aca7..e0c5cdbe 100644
--- a/modules/river-generator.js
+++ b/modules/river-generator.js
@@ -5,8 +5,8 @@
}(this, (function () {'use strict';
const generate = function(changeHeights = true) {
- console.time('generateRivers');
- Math.seedrandom(seed);
+ TIME && console.time('generateRivers');
+ Math.random = aleaPRNG(seed);
const cells = pack.cells, p = cells.p, features = pack.features;
// build distance field in cells from water (cells.t)
@@ -56,7 +56,7 @@
//const min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell
let min = cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell
- // allow only one river can flow thought a lake
+ // allow only one river can flow through a lake
const cf = features[cells.f[i]]; // current cell feature
if (cf.river && cf.river !== cells.r[i]) {
cells.fl[i] = 0;
@@ -126,17 +126,14 @@
}
}
- // drawRivers
- rivers.selectAll("path").remove();
- rivers.selectAll("path").data(riverPaths).enter()
- .append("path").attr("d", d => d[1]).attr("id", d => "river"+d[0])
- .attr("data-width", d => d[2]).attr("data-increment", d => d[3]);
+ const html = riverPaths.map(r =>` `).join("");
+ rivers.html(html);
}()
// apply change heights as basic one
if (changeHeights) cells.h = Uint8Array.from(h);
- console.timeEnd('generateRivers');
+ TIME && console.timeEnd('generateRivers');
}
// depression filling algorithm (for a correct water flux modeling)
@@ -253,6 +250,7 @@
const specify = function() {
if (!pack.rivers.length) return;
+ Math.random = aleaPRNG(seed);
const smallLength = pack.rivers.map(r => r.length||0).sort((a,b) => a-b)[Math.ceil(pack.rivers.length * .15)];
const smallType = {"Creek":9, "River":3, "Brook":3, "Stream":1}; // weighted small river types
diff --git a/modules/routes-generator.js b/modules/routes-generator.js
index e02c4a6f..d9d1905c 100644
--- a/modules/routes-generator.js
+++ b/modules/routes-generator.js
@@ -5,10 +5,10 @@
}(this, (function () {'use strict';
const getRoads = function() {
- console.time("generateMainRoads");
+ TIME && console.time("generateMainRoads");
const cells = pack.cells, burgs = pack.burgs.filter(b => b.i && !b.removed);
const capitals = burgs.filter(b => b.capital);
- if (capitals.length < 2) return []; // not enought capitals to build main roads
+ if (capitals.length < 2) return []; // not enough capitals to build main roads
const paths = []; // array to store path segments
for (const b of capitals) {
@@ -21,14 +21,14 @@
}
cells.i.forEach(i => cells.s[i] += cells.road[i] / 2); // add roads to suitability score
- console.timeEnd("generateMainRoads");
+ TIME && console.timeEnd("generateMainRoads");
return paths;
}
const getTrails = function() {
- console.time("generateTrails");
+ TIME && console.time("generateTrails");
const cells = pack.cells, burgs = pack.burgs.filter(b => b.i && !b.removed);
- if (burgs.length < 2) return []; // not enought burgs to build trails
+ if (burgs.length < 2) return []; // not enough burgs to build trails
let paths = []; // array to store path segments
for (const f of pack.features.filter(f => f.land)) {
@@ -55,12 +55,12 @@
});
}
- console.timeEnd("generateTrails");
+ TIME && console.timeEnd("generateTrails");
return paths;
}
const getSearoutes = function() {
- console.time("generateSearoutes");
+ TIME && console.time("generateSearoutes");
const allPorts = pack.burgs.filter(b => b.port > 0 && !b.removed);
if (allPorts.length < 2) return [];
@@ -93,12 +93,12 @@
});
- console.timeEnd("generateSearoutes");
+ TIME && console.timeEnd("generateSearoutes");
return paths;
}
const draw = function(main, small, ocean) {
- console.time("drawRoutes");
+ TIME && console.time("drawRoutes");
const cells = pack.cells, burgs = pack.burgs;
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
@@ -133,7 +133,7 @@
return [x, y];
})), 1));
- console.timeEnd("drawRoutes");
+ TIME && console.timeEnd("drawRoutes");
}
const regenerate = function() {
@@ -243,4 +243,4 @@
return [from, exit, false];
}
-})));
\ No newline at end of file
+})));
diff --git a/modules/save-and-load.js b/modules/save-and-load.js
index a6eefa6d..3a42be49 100644
--- a/modules/save-and-load.js
+++ b/modules/save-and-load.js
@@ -3,21 +3,20 @@
// download map as SVG
async function saveSVG() {
- console.time("saveSVG");
+ TIME && console.time("saveSVG");
const url = await getMapURL("svg");
const link = document.createElement("a");
link.download = getFileName() + ".svg";
link.href = url;
- document.body.appendChild(link);
link.click();
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
- console.timeEnd("saveSVG");
+ TIME && console.timeEnd("saveSVG");
}
// download map as PNG
async function savePNG() {
- console.time("savePNG");
+ TIME && console.time("savePNG");
const url = await getMapURL("png");
const link = document.createElement("a");
@@ -33,7 +32,6 @@ async function savePNG() {
link.download = getFileName() + ".png";
canvas.toBlob(function(blob) {
link.href = window.URL.createObjectURL(blob);
- document.body.appendChild(link);
link.click();
window.setTimeout(function() {
canvas.remove();
@@ -43,12 +41,12 @@ async function savePNG() {
});
}
- console.timeEnd("savePNG");
+ TIME && console.timeEnd("savePNG");
}
// download map as JPEG
async function saveJPEG() {
- console.time("saveJPEG");
+ TIME && console.time("saveJPEG");
const url = await getMapURL("png");
const canvas = document.createElement("canvas");
@@ -64,13 +62,12 @@ async function saveJPEG() {
const link = document.createElement("a");
link.download = getFileName() + ".jpeg";
link.href = URL;
- document.body.appendChild(link);
link.click();
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
}
- console.timeEnd("saveJPEG");
+ TIME && console.timeEnd("saveJPEG");
}
// parse map svg to object url
@@ -81,6 +78,9 @@ async function getMapURL(type, subtype) {
const clone = d3.select(cloneEl);
clone.select("#debug").remove();
+ const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
+ const svgDefs = document.getElementById("defElements");
+
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove();
if (subtype === "globe") clone.select("#scaleBar").remove();
@@ -94,14 +94,89 @@ async function getMapURL(type, subtype) {
if (customization && type === "mesh") updateMeshCells(clone);
inlineStyle(clone);
- const fontStyle = await GFontToDataURI(getFontsToLoad()); // load non-standard fonts
- if (fontStyle) clone.select("defs").append("style").text(fontStyle.join('\n')); // add font to style
+ // remove unused filters
+ const filters = cloneEl.querySelectorAll("filter");
+ for (let i=0; i < filters.length; i++) {
+ const id = filters[i].id;
+ if (cloneEl.querySelector("[filter='url(#"+id+")']")) continue;
+ if (cloneEl.getAttribute("filter") === "url(#"+id+")") continue;
+ filters[i].remove();
+ }
- clone.append("metadata").text("image/svg+xml ");
- const serialized = (new XMLSerializer()).serializeToString(clone.node());
- const svg_xml = `` + serialized;
+ // remove unused patterns
+ const patterns = cloneEl.querySelectorAll("pattern");
+ for (let i=0; i < patterns.length; i++) {
+ const id = patterns[i].id;
+ if (cloneEl.querySelector("[fill='url(#"+id+")']")) continue;
+ patterns[i].remove();
+ }
+
+ // remove unused symbols
+ const symbols = cloneEl.querySelectorAll("symbol");
+ for (let i=0; i < symbols.length; i++) {
+ const id = symbols[i].id;
+ if (cloneEl.querySelector("use[href='#"+id+"']")) continue;
+ symbols[i].remove();
+ }
+
+ // add displayed emblems
+ if (layerIsOn("toggleEmblems")) {
+ Array.from(cloneEl.getElementById("emblems").querySelectorAll("use")).forEach(el => {
+ const href = el.getAttribute("href");
+ if (!href) return;
+ const emblem = document.getElementById(href.slice(1)).cloneNode(true); // clone emblem
+ cloneDefs.append(emblem);
+ });
+ }
+
+ // add ocean pattern
+ if (cloneEl.getElementById("oceanicPattern")) {
+ const patternId = cloneEl.getElementById("oceanicPattern").getAttribute("filter").slice(5,-1);
+ const pattern = svgDefs.getElementById(patternId);
+ if (patternId) cloneDefs.appendChild(pattern.cloneNode(true));
+ }
+
+ // add relief icons
+ if (cloneEl.getElementById("terrain")) {
+ const uniqueElements = new Set();
+ const terrainElements = cloneEl.getElementById("terrain").childNodes;
+ for (let i=0; i < terrainElements.length; i++) {
+ uniqueElements.add(terrainElements[i].getAttribute("href"));
+ }
+
+ const defsRelief = svgDefs.getElementById("defs-relief");
+ for (const terrain of [...uniqueElements]) {
+ const element = defsRelief.querySelector(terrain);
+ if (element) cloneDefs.appendChild(element.cloneNode(true));
+ }
+ }
+
+ // add wind rose
+ if (cloneEl.getElementById("compass")) {
+ const rose = svgDefs.getElementById("rose");
+ if (rose) cloneDefs.appendChild(rose.cloneNode(true));
+ }
+
+ // add port icon
+ if (cloneEl.getElementById("anchors")) {
+ const anchor = svgDefs.getElementById("icon-anchor");
+ if (anchor) cloneDefs.appendChild(anchor.cloneNode(true));
+ }
+
+ if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching").remove(); //remove unused hatching group
+ if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog").remove(); //remove unused fog
+ if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths").remove(); // removed unused statePaths
+ if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths").remove(); // removed unused textPaths
+
+ // add armies style
+ if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", "");
+
+ const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts
+ if (fontStyle) clone.select("defs").append("style").text(fontStyle.join('\n')); // add font to style
clone.remove();
- const blob = new Blob([svg_xml], {type: 'image/svg+xml;charset=utf-8'});
+
+ const serialized = `` + (new XMLSerializer()).serializeToString(cloneEl);
+ const blob = new Blob([serialized], {type: 'image/svg+xml;charset=utf-8'});
const url = window.URL.createObjectURL(blob);
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
return url;
@@ -115,7 +190,7 @@ function removeUnusedElements(clone) {
for (let empty = 1; empty;) {
empty = 0;
clone.selectAll("g").each(function() {
- if (!this.hasChildNodes() || this.style.display === "none") {empty++; this.remove();}
+ if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) {empty++; this.remove();}
if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display");
});
}
@@ -154,6 +229,15 @@ function inlineStyle(clone) {
style += key + ':' + value + ';';
}
+ for (const key in compStyle) {
+ const value = compStyle.getPropertyValue(key);
+
+ if (key === "cursor") continue; // cursor should be default
+ if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
+ if (value === defaultStyles.getPropertyValue(key)) continue;
+ style += key + ':' + value + ';';
+ }
+
if (style != "") this.setAttribute('style', style);
});
@@ -161,11 +245,11 @@ function inlineStyle(clone) {
}
// get non-standard fonts used for labels to fetch them from web
-function getFontsToLoad() {
+function getFontsToLoad(clone) {
const webSafe = ["Georgia", "Times+New+Roman", "Comic+Sans+MS", "Lucida+Sans+Unicode", "Courier+New", "Verdana", "Arial", "Impact"]; // fonts to not fetch
const fontsInUse = new Set(); // to store fonts currently in use
- labels.selectAll("g").each(function() {
+ clone.selectAll("#labels > g").each(function() {
if (!this.hasChildNodes()) return;
const font = this.dataset.font;
if (!font || webSafe.includes(font)) return;
@@ -219,7 +303,7 @@ function GFontToDataURI(url) {
// prepare map data for saving
function getMapData() {
- console.time("createMapDataBlob");
+ TIME && console.time("createMapDataBlob");
return new Promise(resolve => {
const date = new Date();
@@ -237,12 +321,14 @@ function getMapData() {
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
const notesData = JSON.stringify(notes);
- const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
+ // clone svg
+ const cloneEl = document.getElementById("map").cloneNode(true);
// set transform values to default
cloneEl.setAttribute("width", graphWidth);
cloneEl.setAttribute("height", graphHeight);
cloneEl.querySelector("#viewbox").removeAttribute("transform");
+
const svg_xml = (new XMLSerializer()).serializeToString(cloneEl);
const gridGeneral = JSON.stringify({spacing:grid.spacing, cellsX:grid.cellsX, cellsY:grid.cellsY, boundary:grid.boundary, points:grid.points, features:grid.features});
@@ -274,10 +360,9 @@ function getMapData() {
namesData, rivers].join("\r\n");
const blob = new Blob([data], {type: "text/plain"});
- console.timeEnd("createMapDataBlob");
+ TIME && console.timeEnd("createMapDataBlob");
resolve(blob);
});
-
}
// Download .map file
@@ -290,123 +375,109 @@ async function saveMap() {
const link = document.createElement("a");
link.download = getFileName() + ".map";
link.href = URL;
- document.body.appendChild(link);
link.click();
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
window.URL.revokeObjectURL(URL);
}
function saveGeoJSON_Cells() {
- let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
- const cells = pack.cells, v = pack.vertices;
+ const json = {type: "FeatureCollection", features: []};
+ const cells = pack.cells;
const getPopulation = i => {const [r, u] = getCellPopulation(i); return rn(r+u)};
+ const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]]));
cells.i.forEach(i => {
- data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [[";
- cells.v[i].forEach(n => {
- let x = mapCoordinates.lonW + (v.p[n][0] / graphWidth) * mapCoordinates.lonT;
- let y = mapCoordinates.latN - (v.p[n][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
- data += "["+x+","+y+"],";
- });
- // close the ring
- let x = mapCoordinates.lonW + (v.p[cells.v[i][0]][0] / graphWidth) * mapCoordinates.lonT;
- let y = mapCoordinates.latN - (v.p[cells.v[i][0]][1] / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
- data += "["+x+","+y+"]";
- data += "]] },\n \"properties\": {\n";
+ const coordinates = getCellCoordinates(cells.v[i]);
+ const height = getHeight(i);
+ const biome = cells.biome[i];
+ const type = pack.features[cells.f[i]].type;
+ const population = getPopulation(i);
+ const state = cells.state[i];
+ const province = cells.province[i];
+ const culture = cells.culture[i];
+ const religion = cells.religion[i];
+ const neighbors = cells.c[i];
- const height = parseInt(getFriendlyHeight([cells.p[i][0],cells.p[i][1]]));
-
- data += " \"id\": \""+i+"\",\n";
- data += " \"height\": \""+height+"\",\n";
- data += " \"biome\": \""+cells.biome[i]+"\",\n";
- data += " \"type\": \""+pack.features[cells.f[i]].type+"\",\n";
- data += " \"population\": \""+getPopulation(i)+"\",\n";
- data += " \"state\": \""+cells.state[i]+"\",\n";
- data += " \"province\": \""+cells.province[i]+"\",\n";
- data += " \"culture\": \""+cells.culture[i]+"\",\n";
- data += " \"religion\": \""+cells.religion[i]+"\",\n";
- data += " \"neighbors\": ["+cells.c[i]+"]\n";
- data +=" }\n},\n";
+ const properties = {id:i, height, biome, type, population, state, province, culture, religion, neighbors}
+ const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties};
+ json.features.push(feature);
});
- data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
- data += "]}";
-
const name = getFileName("Cells") + ".geojson";
- downloadFile(data, name, "application/json");
+ downloadFile(JSON.stringify(json), name, "application/json");
}
-function saveGeoJSON_Roads() {
- let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
+function saveGeoJSON_Routes() {
+ const json = {type: "FeatureCollection", features: []};
- routes._groups[0][0].childNodes.forEach(n => {
- n.childNodes.forEach(r => {
- data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"LineString\", \"coordinates\": ";
- data += JSON.stringify(getRoadPoints(r));
- data += " },\n \"properties\": {\n";
- data += " \"id\": \""+r.id+"\",\n";
- data += " \"type\": \""+n.id+"\"\n";
- data +=" }\n},\n";
- });
+ routes.selectAll("g > path").each(function() {
+ const coordinates = getRoutePoints(this);
+ const id = this.id;
+ const type = this.parentElement.id;
+
+ const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}};
+ json.features.push(feature);
});
- data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
- data += "]}";
const name = getFileName("Routes") + ".geojson";
- downloadFile(data, name, "application/json");
+ downloadFile(JSON.stringify(json), name, "application/json");
}
function saveGeoJSON_Rivers() {
- let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
+ const json = {type: "FeatureCollection", features: []};
- rivers._groups[0][0].childNodes.forEach(n => {
- data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"LineString\", \"coordinates\": ";
- data += JSON.stringify(getRiverPoints(n));
- data += " },\n \"properties\": {\n";
- data += " \"id\": \""+n.id+"\",\n";
- data += " \"width\": \""+n.dataset.width+"\",\n";
- data += " \"increment\": \""+n.dataset.increment+"\"\n";
- data +=" }\n},\n";
+ rivers.selectAll("path").each(function() {
+ const coordinates = getRiverPoints(this);
+ const id = this.id;
+ const width = +this.dataset.increment;
+ const increment = +this.dataset.increment;
+ const river = pack.rivers.find(r => r.i === +id.slice(5));
+ const name = river ? river.name : "";
+ const type = river ? river.type : "";
+ const i = river ? river.i : "";
+ const basin = river ? river.basin : "";
+
+ const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, i, basin, name, type, width, increment}};
+ json.features.push(feature);
});
- data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
- data += "]}";
const name = getFileName("Rivers") + ".geojson";
- downloadFile(data, name, "application/json");
+ downloadFile(JSON.stringify(json), name, "application/json");
}
function saveGeoJSON_Markers() {
- let data = "{ \"type\": \"FeatureCollection\", \"features\": [\n";
+ const json = {type: "FeatureCollection", features: []};
- markers._groups[0][0].childNodes.forEach(n => {
- let x = mapCoordinates.lonW + (n.dataset.x / graphWidth) * mapCoordinates.lonT;
- let y = mapCoordinates.latN - (n.dataset.y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
-
- data += "{\n \"type\": \"Feature\",\n \"geometry\": { \"type\": \"Point\", \"coordinates\": ["+x+", "+y+"]";
- data += " },\n \"properties\": {\n";
- data += " \"id\": \""+n.id+"\",\n";
- data += " \"type\": \""+n.dataset.id.substring(8)+"\"\n";
- data +=" }\n},\n";
+ markers.selectAll("use").each(function() {
+ const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y);
+ const id = this.id;
+ const type = (this.dataset.id).substring(1);
+ const icon = document.getElementById(type).textContent;
+ const note = notes.length ? notes.find(note => note.id === this.id) : null;
+ const name = note ? note.name : "";
+ const legend = note ? note.legend : "";
+ const feature = {type: "Feature", geometry: {type: "Point", coordinates}, properties: {id, type, icon, name, legend}};
+ json.features.push(feature);
});
- data = data.substring(0, data.length - 2)+"\n"; // remove trailing comma
- data += "]}";
const name = getFileName("Markers") + ".geojson";
- downloadFile(data, name, "application/json");
+ downloadFile(JSON.stringify(json), name, "application/json");
}
-function getRoadPoints(node) {
+function getCellCoordinates(vertices) {
+ const p = pack.vertices.p;
+ const coordinates = vertices.map(n => getQGIScoordinates(p[n][0], p[n][1]));
+ return [coordinates.concat([coordinates[0]])];
+}
+
+function getRoutePoints(node) {
let points = [];
const l = node.getTotalLength();
const increment = l / Math.ceil(l / 2);
for (let i=0; i <= l; i += increment) {
const p = node.getPointAtLength(i);
-
- let x = mapCoordinates.lonW + (p.x / graphWidth) * mapCoordinates.lonT;
- let y = mapCoordinates.latN - (p.y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
-
- points.push([x,y]);
+ points.push(getQGIScoordinates(p.x, p.y));
}
return points;
}
@@ -418,9 +489,7 @@ function getRiverPoints(node) {
for (let i=l, c=i; i >= 0; i -= increment, c += increment) {
const p1 = node.getPointAtLength(i);
const p2 = node.getPointAtLength(c);
-
- let x = mapCoordinates.lonW + (((p1.x+p2.x)/2) / graphWidth) * mapCoordinates.lonT;
- let y = mapCoordinates.latN - (((p1.y+p2.y)/2) / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise
+ const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
points.push([x,y]);
}
return points;
@@ -430,7 +499,7 @@ async function quickSave() {
if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;}
const blob = await getMapData();
if (blob) ldb.set("lastMap", blob); // auto-save map
- tip("Map is saved to browser memory", true, "success", 2000);
+ tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000);
}
function quickLoad() {
@@ -439,7 +508,7 @@ function quickLoad() {
loadMapPrompt(blob);
} else {
tip("No map stored. Save map to storage first", true, "error", 2000);
- console.error("No map stored");
+ ERROR && console.error("No map stored");
}
});
}
@@ -458,12 +527,12 @@ function loadMapPrompt(blob) {
});
function loadLastSavedMap() {
- console.warn("Load last saved map");
+ WARN && console.warn("Load last saved map");
try {
uploadMap(blob);
}
catch(error) {
- console.error(error);
+ ERROR && console.error(error);
tip("Cannot load last saved map", true, "error", 2000);
}
}
@@ -508,6 +577,8 @@ function uploadMap(file, callback) {
const fileReader = new FileReader();
fileReader.onload = function(fileLoadedEvent) {
if (callback) callback();
+ document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
+
const dataLoaded = fileLoadedEvent.target.result;
const data = dataLoaded.split("\r\n");
@@ -526,7 +597,7 @@ function uploadMap(file, callback) {
} else {
load = true;
message = `The map version (${mapVersion}) does not match the Generator version (${version}).
-
The map will be auto-updated. In case of issues please keep using an ${archive} of the Generator`;
+
Click OK to get map auto-updated. In case of issues please keep using an ${archive} of the Generator`;
}
alertMessage.innerHTML = message;
$("#alert").dialog({title: "Version conflict", width: "38em", buttons: {
@@ -555,7 +626,7 @@ function parseLoadedData(data) {
mapId = params[6] ? +params[6] : Date.now();
}()
- console.group("Loaded Map " + seed);
+ INFO && console.group("Loaded Map " + seed);
void function parseSettings() {
const settings = data[1].split("|");
@@ -645,6 +716,7 @@ function parseLoadedData(data) {
coastline = viewbox.select("#coastline");
prec = viewbox.select("#prec");
population = viewbox.select("#population");
+ emblems = viewbox.select("#emblems");
labels = viewbox.select("#labels");
icons = viewbox.select("#icons");
burgIcons = icons.select("#burgIcons");
@@ -705,36 +777,43 @@ function parseLoadedData(data) {
}
}()
- void function restoreLayersState() {
- if (texture.style("display") !== "none" && texture.select("image").size()) turnButtonOn("toggleTexture"); else turnButtonOff("toggleTexture");
- if (terrs.selectAll("*").size()) turnButtonOn("toggleHeight"); else turnButtonOff("toggleHeight");
- if (biomes.selectAll("*").size()) turnButtonOn("toggleBiomes"); else turnButtonOff("toggleBiomes");
- if (cells.selectAll("*").size()) turnButtonOn("toggleCells"); else turnButtonOff("toggleCells");
- if (gridOverlay.selectAll("*").size()) turnButtonOn("toggleGrid"); else turnButtonOff("toggleGrid");
- if (coordinates.selectAll("*").size()) turnButtonOn("toggleCoordinates"); else turnButtonOff("toggleCoordinates");
- if (compass.style("display") !== "none" && compass.select("use").size()) turnButtonOn("toggleCompass"); else turnButtonOff("toggleCompass");
- if (rivers.style("display") !== "none") turnButtonOn("toggleRivers"); else turnButtonOff("toggleRivers");
- if (terrain.style("display") !== "none" && terrain.selectAll("*").size()) turnButtonOn("toggleRelief"); else turnButtonOff("toggleRelief");
- if (relig.selectAll("*").size()) turnButtonOn("toggleReligions"); else turnButtonOff("toggleReligions");
- if (cults.selectAll("*").size()) turnButtonOn("toggleCultures"); else turnButtonOff("toggleCultures");
- if (statesBody.selectAll("*").size()) turnButtonOn("toggleStates"); else turnButtonOff("toggleStates");
- if (provs.selectAll("*").size()) turnButtonOn("toggleProvinces"); else turnButtonOff("toggleProvinces");
- if (zones.selectAll("*").size() && zones.style("display") !== "none") turnButtonOn("toggleZones"); else turnButtonOff("toggleZones");
- if (borders.style("display") !== "none") turnButtonOn("toggleBorders"); else turnButtonOff("toggleBorders");
- if (routes.style("display") !== "none" && routes.selectAll("path").size()) turnButtonOn("toggleRoutes"); else turnButtonOff("toggleRoutes");
- if (temperature.selectAll("*").size()) turnButtonOn("toggleTemp"); else turnButtonOff("toggleTemp");
- if (prec.selectAll("circle").size()) turnButtonOn("togglePrec"); else turnButtonOff("togglePrec");
- if (labels.style("display") !== "none") turnButtonOn("toggleLabels"); else turnButtonOff("toggleLabels");
- if (icons.style("display") !== "none") turnButtonOn("toggleIcons"); else turnButtonOff("toggleIcons");
- if (armies.selectAll("*").size() && armies.style("display") !== "none") turnButtonOn("toggleMilitary"); else turnButtonOff("toggleMilitary");
- if (markers.selectAll("*").size() && markers.style("display") !== "none") turnButtonOn("toggleMarkers"); else turnButtonOff("toggleMarkers");
- if (ruler.style("display") !== "none") turnButtonOn("toggleRulers"); else turnButtonOff("toggleRulers");
- if (scaleBar.style("display") !== "none") turnButtonOn("toggleScaleBar"); else turnButtonOff("toggleScaleBar");
+ const notHidden = selection => selection.style("display") !== "none";
+ const hasChildren = selection => selection.node()?.hasChildNodes();
+ const hasChild = (selection, selector) => selection.node()?.querySelector(selector);
+ const turnOn = el => document.getElementById(el).classList.remove("buttonoff");
- // special case for population bars
- const populationIsOn = population.selectAll("line").size();
- if (populationIsOn) drawPopulation();
- if (populationIsOn) turnButtonOn("togglePopulation"); else turnButtonOff("togglePopulation");
+ void function restoreLayersState() {
+ // turn all layers off
+ document.getElementById("mapLayers").querySelectorAll("li").forEach(el => el.classList.add("buttonoff"));
+
+ // turn on active layers
+ if (notHidden(texture) && hasChild(texture, "image")) turnOn("toggleTexture");
+ if (hasChildren(terrs)) turnOn("toggleHeight");
+ if (hasChildren(biomes)) turnOn("toggleBiomes");
+ if (hasChildren(cells)) turnOn("toggleCells");
+ if (hasChildren(gridOverlay)) turnOn("toggleGrid");
+ if (hasChildren(coordinates)) turnOn("toggleCoordinates");
+ if (notHidden(compass) && hasChild(compass, "use")) turnOn("toggleCompass");
+ if (notHidden(rivers)) turnOn("toggleRivers");
+ if (notHidden(terrain) && hasChildren(terrain)) turnOn("toggleRelief");
+ if (hasChildren(relig)) turnOn("toggleReligions");
+ if (hasChildren(cults)) turnOn("toggleCultures");
+ if (hasChildren(statesBody)) turnOn("toggleStates");
+ if (hasChildren(provs)) turnOn("toggleProvinces");
+ if (hasChildren(zones) && notHidden(zones)) turnOn("toggleZones");
+ if (notHidden(borders) && hasChild(compass, "use")) turnOn("toggleBorders");
+ if (notHidden(routes) && hasChild(routes, "path")) turnOn("toggleRoutes");
+ if (hasChildren(temperature)) turnOn("toggleTemp");
+ if (hasChild(population, "line")) turnOn("togglePopulation");
+ if (hasChildren(ice)) turnOn("toggleIce");
+ if (hasChild(prec, "circle")) turnOn("togglePrec");
+ if (hasChild(emblems, "use")) turnOn("toggleEmblems");
+ if (notHidden(labels)) turnOn("toggleLabels");
+ if (notHidden(icons)) turnOn("toggleIcons");
+ if (hasChildren(armies) && notHidden(armies)) turnOn("toggleMilitary");
+ if (hasChildren(markers) && notHidden(markers)) turnOn("toggleMarkers");
+ if (notHidden(ruler)) turnOn("toggleRulers");
+ if (notHidden(scaleBar)) turnOn("toggleScaleBar");
getCurrentPreset();
}()
@@ -748,7 +827,7 @@ function parseLoadedData(data) {
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd));
- scaleBar.on("mousemove", () => tip("Click to open Units Editor"));
+ scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => editUnits());
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
}()
@@ -924,20 +1003,13 @@ function parseLoadedData(data) {
}
if (version < 1.22) {
- // v 1.21 had incorrect style formatting
- localStorage.removeItem("styleClean");
- localStorage.removeItem("styleGloom");
- localStorage.removeItem("styleAncient");
- localStorage.removeItem("styleMonochrome");
- addDefaulsStyles();
-
// v 1.22 changed state neighbors from Set object to array
BurgsAndStates.collectStatistics();
}
if (version < 1.3) {
// v 1.3 added global options object
- const winds = options.slice(); // previostly wnd was saved in settings[19]
+ const winds = options.slice(); // previostly wind was saved in settings[19]
const year = rand(100, 2000);
const era = Names.getBaseShort(P(.7) ? 1 : rand(nameBases.length)) + " Era";
const eraShort = era[0] + "E";
@@ -948,7 +1020,6 @@ function parseLoadedData(data) {
BurgsAndStates.generateCampaigns();
// v 1.3 added militry layer
- svg.select("defs").append("style").text(armiesStyle()); // add armies style
armies = viewbox.insert("g", "#icons").attr("id", "armies");
armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", .3);
turnButtonOn("toggleMilitary");
@@ -988,6 +1059,30 @@ function parseLoadedData(data) {
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => r.state = s.i));
}
+ if (version < 1.5) {
+ // v 1.5 added emblems
+ emblems = viewbox.append("g").attr("id", "emblems").style("display", "none");
+ emblems.append("g").attr("id", "burgEmblems");
+ emblems.append("g").attr("id", "provinceEmblems");
+ emblems.append("g").attr("id", "stateEmblems");
+ regenerateEmblems();
+ toggleEmblems();
+
+ // not need to store default styles from v 1.5
+ localStorage.removeItem("styleClean");
+ localStorage.removeItem("styleGloom");
+ localStorage.removeItem("styleAncient");
+ localStorage.removeItem("styleMonochrome");
+
+ // v 1.5 added burg type value
+ pack.burgs.forEach(burg => {
+ if (!burg.i || burg.removed) return;
+ burg.type = BurgsAndStates.getType(burg.cell, burg.port);
+ });
+
+ BurgsAndStates.getType(cell, false);
+ }
+
}()
void function checkDataIntegrity() {
@@ -997,49 +1092,49 @@ function parseLoadedData(data) {
invalidStates.forEach(s => {
const invalidCells = cells.i.filter(i => cells.state[i] === s);
invalidCells.forEach(i => cells.state[i] = 0);
- console.error("Data Integrity Check. Invalid state", s, "is assigned to cells", invalidCells);
+ ERROR && console.error("Data Integrity Check. Invalid state", s, "is assigned to cells", invalidCells);
});
const invalidProvinces = [...new Set(cells.province)].filter(p => p && (!pack.provinces[p] || pack.provinces[p].removed));
invalidProvinces.forEach(p => {
const invalidCells = cells.i.filter(i => cells.province[i] === p);
invalidCells.forEach(i => cells.province[i] = 0);
- console.error("Data Integrity Check. Invalid province", p, "is assigned to cells", invalidCells);
+ ERROR && console.error("Data Integrity Check. Invalid province", p, "is assigned to cells", invalidCells);
});
const invalidCultures = [...new Set(cells.culture)].filter(c => !pack.cultures[c] || pack.cultures[c].removed);
invalidCultures.forEach(c => {
const invalidCells = cells.i.filter(i => cells.culture[i] === c);
invalidCells.forEach(i => cells.province[i] = 0);
- console.error("Data Integrity Check. Invalid culture", c, "is assigned to cells", invalidCells);
+ ERROR && console.error("Data Integrity Check. Invalid culture", c, "is assigned to cells", invalidCells);
});
const invalidReligions = [...new Set(cells.religion)].filter(r => !pack.religions[r] || pack.religions[r].removed);
invalidReligions.forEach(r => {
const invalidCells = cells.i.filter(i => cells.religion[i] === r);
invalidCells.forEach(i => cells.religion[i] = 0);
- console.error("Data Integrity Check. Invalid religion", c, "is assigned to cells", invalidCells);
+ ERROR && console.error("Data Integrity Check. Invalid religion", c, "is assigned to cells", invalidCells);
});
const invalidFeatures = [...new Set(cells.f)].filter(f => f && !pack.features[f]);
invalidFeatures.forEach(f => {
const invalidCells = cells.i.filter(i => cells.f[i] === f);
// No fix as for now
- console.error("Data Integrity Check. Invalid feature", f, "is assigned to cells", invalidCells);
+ ERROR && console.error("Data Integrity Check. Invalid feature", f, "is assigned to cells", invalidCells);
});
const invalidBurgs = [...new Set(cells.burg)].filter(b => b && (!pack.burgs[b] || pack.burgs[b].removed));
invalidBurgs.forEach(b => {
const invalidCells = cells.i.filter(i => cells.burg[i] === b);
invalidCells.forEach(i => cells.burg[i] = 0);
- console.error("Data Integrity Check. Invalid burg", b, "is assigned to cells", invalidCells);
+ ERROR && console.error("Data Integrity Check. Invalid burg", b, "is assigned to cells", invalidCells);
});
pack.burgs.forEach(b => {
if (!b.i || b.removed) return;
- if (b.port < 0) {console.error("Data Integrity Check. Burg", b.i, "has invalid port value", b.port); b.port = 0;}
+ if (b.port < 0) {ERROR && console.error("Data Integrity Check. Burg", b.i, "has invalid port value", b.port); b.port = 0;}
if (b.cell < cells.i.length) return;
- console.error("Data Integrity Check. Burg", b.i, "is linked to invalid cell", b.cell);
+ ERROR && console.error("Data Integrity Check. Burg", b.i, "is linked to invalid cell", b.cell);
b.cell = findCell(b.x, b.y);
cells.i.filter(i => cells.burg[i] === b.i).forEach(i => cells.burg[i] = 0);
cells.burg[b.cell] = b.i;
@@ -1047,17 +1142,25 @@ function parseLoadedData(data) {
}()
changeMapSize();
+
+ // remove href from emblems, to trigger rendering on load
+ emblems.selectAll("use").attr("href", null);
+
+ // set options
+ yearInput.value = options.year;
+ eraInput.value = options.era;
+
if (window.restoreDefaultEvents) restoreDefaultEvents();
focusOn(); // based on searchParams focus on point, cell or burg
invokeActiveZooming();
- console.warn(`TOTAL: ${rn((performance.now()-uploadMap.timeStart)/1000,2)}s`);
- showStatistics();
- console.groupEnd("Loaded Map " + seed);
+ WARN && console.warn(`TOTAL: ${rn((performance.now()-uploadMap.timeStart)/1000,2)}s`);
+ INFO && showStatistics();
+ INFO && console.groupEnd("Loaded Map " + seed);
tip("Map is successfully loaded", true, "success", 7000);
}
catch(error) {
- console.error(error);
+ ERROR && console.error(error);
clearMainTip();
alertMessage.innerHTML = `An error is occured on map loading. Select a different file to load,
diff --git a/modules/ui/3d.js b/modules/ui/3d.js
index bd88decc..c7fa9aa0 100644
--- a/modules/ui/3d.js
+++ b/modules/ui/3d.js
@@ -121,7 +121,6 @@ const saveScreenshot = async function() {
const link = document.createElement("a");
link.download = getFileName() + ".jpeg";
link.href = URL;
- document.body.appendChild(link);
link.click();
tip(`Screenshot is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
diff --git a/modules/ui/burg-editor.js b/modules/ui/burg-editor.js
index b129df88..1ec8f8da 100644
--- a/modules/ui/burg-editor.js
+++ b/modules/ui/burg-editor.js
@@ -31,8 +31,10 @@ function editBurg(id) {
document.getElementById("burgRemoveGroup").addEventListener("click", removeBurgsGroup);
document.getElementById("burgName").addEventListener("input", changeName);
- document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture);
document.getElementById("burgNameReRandom").addEventListener("click", generateNameRandom);
+ document.getElementById("burgType").addEventListener("input", changeType);
+ document.getElementById("burgCulture").addEventListener("input", changeCulture);
+ document.getElementById("burgNameReCulture").addEventListener("click", generateNameCulture);
document.getElementById("burgPopulation").addEventListener("change", changePopulation);
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature));
@@ -43,7 +45,7 @@ function editBurg(id) {
document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle);
document.getElementById("burgSeeInMFCG").addEventListener("click", openInMFCG);
- document.getElementById("burgOpenCOA").addEventListener("click", openInIAHG);
+ document.getElementById("burgEditEmblem").addEventListener("click", openEmblemEdit);
document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg);
document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
@@ -51,10 +53,27 @@ function editBurg(id) {
function updateBurgValues() {
const id = +elSelected.attr("data-id");
const b = pack.burgs[id];
+ const province = pack.cells.province[b.cell];
+ const provinceName = province ? pack.provinces[province].fullName + ", " : "";
+ const stateName = pack.states[b.state].fullName || pack.states[b.state].name;
+ document.getElementById("burgProvinceAndState").innerHTML = provinceName + stateName;
+
document.getElementById("burgName").value = b.name;
+ document.getElementById("burgType").value = b.type || "Generic";
document.getElementById("burgPopulation").value = rn(b.population * populationRate.value * urbanization.value);
document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none";
+ // update list and select culture
+ const cultureSelect = document.getElementById("burgCulture");
+ cultureSelect.options.length = 0;
+ const cultures = pack.cultures.filter(c => !c.removed);
+ cultures.forEach(c => cultureSelect.options.add(new Option(c.name, c.i, false, c.i === b.culture)));
+
+ const temperature = grid.cells.temp[pack.cells.g[b.cell]];
+ document.getElementById("burgTemperature").innerHTML = convertTemperature(temperature);
+ document.getElementById("burgTemperatureLikeIn").innerHTML = getTemperatureLikeness(temperature);
+ document.getElementById("burgElevation").innerHTML = getHeight(pack.cells.h[b.cell]);
+
// toggle features
if (b.capital) document.getElementById("burgCapital").classList.remove("inactive");
else document.getElementById("burgCapital").classList.add("inactive");
@@ -79,6 +98,23 @@ function editBurg(id) {
burgLabels.selectAll("g").each(function() {
select.options.add(new Option(this.id, this.id, false, this.id === group));
});
+
+ // set emlem image
+ const coaID = "burgCOA"+id;
+ COArenderer.trigger(coaID, b.coa);
+ document.getElementById("burgEmblem").setAttribute("href", "#" + coaID);
+ }
+
+ // in °C, array from -1 °C; source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
+ function getTemperatureLikeness(temperature) {
+ if (temperature < -5) return "Yakutsk";
+ const cities = [
+ "Snag (Yukon)", "Yellowknife (Canada)", "Okhotsk (Russia)", "Fairbanks (Alaska)", "Nuuk (Greenland)", "Murmansk", // -5 - 0
+ "Arkhangelsk", "Anchorage", "Tromsø", "Reykjavik", "Riga", "Stockholm", "Halifax", "Prague", "Copenhagen9", "London", // 1 - 10
+ "Antwerp", "Paris", "Milan", "Batumi", "Rome", "Dubrovnik", "Lisbon", "Barcelona", "Marrakesh", "Alexandria", // 11 - 20
+ "Tegucigalpa", "Guangzhou", "Rio de Janeiro", "Dakar", "Miami", "Jakarta", "Mogadishu", "Bangkok", "Aden", "Khartoum"]; // 21 - 30
+ if (temperature > 30) return "Mecca";
+ return cities[temperature+5] || null;
}
function dragBurgLabel() {
@@ -141,7 +177,7 @@ function editBurg(id) {
const label = document.querySelector("#burgLabels [data-id='" + id + "']");
const icon = document.querySelector("#burgIcons [data-id='" + id + "']");
const anchor = document.querySelector("#anchors [data-id='" + id + "']");
- if (!label || !icon) {console.error("Cannot find label or icon elements"); return;}
+ if (!label || !icon) {ERROR && console.error("Cannot find label or icon elements"); return;}
const labelG = document.querySelector("#burgLabels > #"+oldGroup);
const iconG = document.querySelector("#burgIcons > #"+oldGroup);
@@ -220,6 +256,22 @@ function editBurg(id) {
elSelected.text(burgName.value);
}
+ function generateNameRandom() {
+ const base = rand(nameBases.length-1);
+ burgName.value = Names.getBase(base);
+ changeName();
+ }
+
+ function changeType() {
+ const id = +elSelected.attr("data-id");
+ pack.burgs[id].type = this.value;
+ }
+
+ function changeCulture() {
+ const id = +elSelected.attr("data-id");
+ pack.burgs[id].culture = +this.value;
+ }
+
function generateNameCulture() {
const id = +elSelected.attr("data-id");
const culture = pack.burgs[id].culture;
@@ -227,12 +279,6 @@ function editBurg(id) {
changeName();
}
- function generateNameRandom() {
- const base = rand(nameBases.length-1);
- burgName.value = Names.getBase(base);
- changeName();
- }
-
function changePopulation() {
const id = +elSelected.attr("data-id");
pack.burgs[id].population = rn(burgPopulation.value / populationRate.value / urbanization.value, 4);
@@ -296,7 +342,8 @@ function editBurg(id) {
if (!seed && burg.MFCGlink) {openURL(burg.MFCGlink); return;}
const cells = pack.cells;
const name = elSelected.text();
- const size = Math.max(Math.min(rn(burg.population), 65), 6);
+ const size = Math.max(Math.min(rn(burg.population), 100), 6); // to be removed once change on MFDC is done
+ const population = rn(burg.population * populationRate.value * urbanization.value);
const s = burg.MFCG || defSeed;
const cell = burg.cell;
@@ -321,22 +368,14 @@ function editBurg(id) {
}
const site = "http://fantasycities.watabou.ru/?random=0&continuous=0";
- const url = `${site}&name=${name}&size=${size}&seed=${s}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
+ const url = `${site}&name=${name}&population=${population}&size=${size}&seed=${s}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
openURL(url);
}
}
- function openInIAHG(event) {
- const id = elSelected.attr("data-id"), burg = pack.burgs[id], defSeed = `${seed}-b${id}`;
- const openIAHG = () => openURL("https://ironarachne.com/heraldry/" + (burg.IAHG || defSeed));
-
- if (isCtrlClick(event)) {
- prompt(`Please provide an Iron Arachne Heraldry Generator seed.
Default seed is a combination of FMG map seed and burg id (${defSeed})`,
- {default:burg.IAHG || defSeed}, v => {
- if (v && v != defSeed) burg.IAHG = v;
- openIAHG();
- });
- } else openIAHG();
+ function openEmblemEdit() {
+ const id = +elSelected.attr("data-id"), burg = pack.burgs[id];
+ editEmblem("burg", "burgCOA"+id, burg);
}
function toggleRelocateBurg() {
diff --git a/modules/ui/burgs-overview.js b/modules/ui/burgs-overview.js
index 35d668a6..d5e37f79 100644
--- a/modules/ui/burgs-overview.js
+++ b/modules/ui/burgs-overview.js
@@ -456,7 +456,7 @@ function overviewBurgs() {
function triggerAllBurgsRemove() {
alertMessage.innerHTML = `Are you sure you want to remove all burgs except of capitals?
-
To remove a capital you have to remove its state first`;
+
To remove a capital you have to remove a state first`;
$("#alert").dialog({resizable: false, title: "Remove all burgs",
buttons: {
Remove: function() {
@@ -472,5 +472,4 @@ function overviewBurgs() {
pack.burgs.filter(b => b.i && !b.capital).forEach(b => removeBurg(b.i));
burgsOverviewAddLines();
}
-
}
diff --git a/modules/ui/cultures-editor.js b/modules/ui/cultures-editor.js
index 369083af..c7a6c45e 100644
--- a/modules/ui/cultures-editor.js
+++ b/modules/ui/cultures-editor.js
@@ -38,6 +38,7 @@ function editCultures() {
function refreshCulturesEditor() {
culturesCollectStatistics();
culturesEditorAddLines();
+ drawCultureCenters();
}
function culturesCollectStatistics() {
@@ -491,9 +492,9 @@ function editCultures() {
debug.select("#cultureCenters").style("display", "none");
culturesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
- culturesHeader.querySelector("div[data-sortby='type']").style.left = "6.8em";
+ culturesHeader.querySelector("div[data-sortby='type']").style.left = "8.8em";
culturesFooter.style.display = "none";
- culturesHeader.querySelector("div[data-sortby='base']").style.marginLeft = "21px";
+ culturesHeader.querySelector("div[data-sortby='base']").style.marginLeft = "20px";
body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "none");
$("#culturesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
@@ -587,7 +588,7 @@ function editCultures() {
document.getElementById("culturesManuallyButtons").style.display = "none";
culturesEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden"));
- culturesHeader.querySelector("div[data-sortby='type']").style.left = "15.8em";
+ culturesHeader.querySelector("div[data-sortby='type']").style.left = "18.6em";
culturesFooter.style.display = "block";
culturesHeader.querySelector("div[data-sortby='base']").style.marginLeft = "2px";
body.querySelectorAll("div > input, select, span, svg").forEach(e => e.style.pointerEvents = "all");
diff --git a/modules/ui/diplomacy-editor.js b/modules/ui/diplomacy-editor.js
index 67dc6173..c5f5e986 100644
--- a/modules/ui/diplomacy-editor.js
+++ b/modules/ui/diplomacy-editor.js
@@ -54,8 +54,10 @@ function editDiplomacy() {
const selName = states[sel].fullName;
diplomacySelect.style.display = "none";
- let lines = `
- ${selName}
+ COArenderer.trigger("stateCOA"+sel, states[sel].coa);
+ let lines = `
+ ${selName}
+
`;
for (const s of states) {
@@ -66,9 +68,10 @@ function editDiplomacy() {
const tip = s.fullName + description[index] + selName;
const tipSelect = `${tip}. Click to see relations to ${s.name}`;
const tipChange = `${tip}. Click to change relations to ${selName}`;
+ COArenderer.trigger("stateCOA"+s.i, s.coa);
lines += `
-
+
${s.fullName}