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). [![preview](https://cdn.discordapp.com/attachments/587406457725779968/594840629213659136/preview1.png)](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,d09GMgABAAAAAC5cAA4AAAAAdigAAC3+AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGh4GYACDKhEICoHQKIGbKAuDJgABNgIkA4ZIBCAFhwQHg0sMBxsLWRNuLDxsHDAWEA8WUTUaS/ZfJfAEJtXwF2CTqKhq1GldrigpJuuZzpbfwBe3GHB3cREO4XjwXN7Hx0/8dYQMuT0E2+yoMBGdGMMADEoUGyVaEAkDCQVRDKza5lxYsdS5vUtdZLnI+KrFp/zz/cXOf7sgURpYIlEzESceYJxgpFHAYXP5f+rg987dPz+xJoo4zKyKKORUdmFudWLp5ri1ToiVgzC5h5J686RIImzq1flJr8Yrh2sqbnTZMpg+Bqe0mSOBV7qkhXheHI7ccDdcL7vk6u7lz42YtAk2ECcHKZBSX4G08na1r5f5t81q/ydZIXtCTn1WyrPXi3hRbnnV8FlmmPkZZIgBq+QEWAVWI+8mk0FDBD8TXREhZ65kT1SaYstc2Z2XWpS1LsPpEijUN6dmtBPj7nzXv7F9nbHeRrRFq5SgfODNzgsAgAwY3J+wAAAArhJUSytL9LXzPy6KB4CvCQoCjtsAAt6gC6d1dC2CsHt+Tx8ABp56gnxw9vfv9PWqwzbUmp+6esAf+W372RYCgGm+f3iYF5yBeGDyBw52hwOLbAAmMOR/NlyPKXievK7u5m47mIbELP1cnCfz7ORDJpGDyaHkZPL2O43JtA7srGukW5RAnkEOlCWdVt975p6+UIer/m8wqu//++7Vvle7X+16tf3VyCsKTamFAYK0qDfVgoj+2jsAMDgCifKqlH+lDrR+7kNCw8IpVFpEZFQ0ncFksWM4sXHc+ITEpOSUVF5aOl8gFIklUplcocxQqTM1WVqd3pCdk5tnzDeZLdaCQhvo6OzuXTzyzbq16yc2TG7asnnrth3bd+7as2/v/oMHjh45dhzUFDlKbzStIasqBw0Fug7UAiUNAACgYsDGb7dXAgCAawGYZ3fh8jNnL05dvXbp8iHpFAC3b94CADT/XwcL+ub39wwODQ8sXQaWrFw1dvrchWrgfMv0OkOa5lFm3IEvcGlK/pRHfsBFgQ3/MfSm9WHgoCDBoE8Y9gahHLShLzy6kdBD8lj6RymFOBcc7EsjWIV0OPg0OdjNjsObHu0PLfxhlQyGnnDXRnBMGIoBQ78ILlhYD3SHXqhWMe6WH0p/+Vn+EiSE+Hi087FHBwBop/hEvvq3VokRVR1tjY911zVpq5XG73dRAK38m3xdPut7DbX+no2pp0PNT8ee7bc6N63vTWxOPenb1N6j1qi+Vbz0BbXa79a0dLZuCAVSDVs2gNVjcHVnq9WdWOfrtVqbQNN3/aBflbRAPwBdk99frFpVrPahAGxXAUpWtbYoHXF/IAL8iF1N5LGaqrWhpeYxqXxdvltQo/eL4RhQ+nw9zaJ+/9UXWqpfCvN7Nf0+S19jJQtX6uqQ3VySHtnvp72jtTXmU5YypD1AQB+lNqC0tPiUYpR7UKvZAFbzqd6AMRo6X7p9ltJh1XTq/lbXt+SVhYXuNlLylCzx+Z4we0HNX9IL543c2R2F01TJtbAhM/PSTWO2CWvDGKedIq69TEEDASoxAXuWXb2Z98LaQC3J3EWn0ttl4zrgprF4ChYMbSpCt0oXbIBDS302b8lJiNk2qOjNxJUvQwUFz/bCc+cuOdi1T9/djhzIMpTTTuM0ljOyC1RCojMtoD5LXPWiwxjxXCE+E5RfDcnDXt80Qzx6+qOmpfFzZENWKq5QqBUj6tI2cmefeDIWijx78msCUqeQiDIb4GHsAsIQp+sL016Ev71bwJ8mCXFH/YBPvGnrQsjYNTKWdtS+26nkJqUZqqF4TlMJIIPXFapoWTWnzUlZEkNFFZx4lrersSk1X2fq8/BWF8jvlaeJjhN53aAKfeoGIEAvGHn9iFygUVwylGPPeB1G9ICRG0VOUpdB0sy3yimNSdIiTWkRiNgavSrCR0KqOp40hVrE1uvVN6ZnQKVVwLSAM5a2YL+zNOMpEH/ipsDek9egjJuAjg3xUkHBmjJqLEcDK6Q/dNoSkFKnVB+dQkHVAwQU1di8EPjLBhAzizR1Op7fK2MxQLnEbK0AUXCO00RJxh7QoZ5ViVuMxA7gOoMrM0b0gs2lBTZIvHXyZTjOUC2dCWtTkroEyD2q1GEZJELA05bN7GUHonsRSVwXOGRNagJqUyho7g8xEFPt8ymOk2ysZBkHHKpcTdDvcb96HTDI2AiBzjOWEJA1uE1WauoU9MzGUovRMWTz9klcqWDoyfMiRflQCe89V7mhnzmKON2OnOq8+LZ4dgx7UzxlTMUMaTZOPPnkTCCd2l5ICBn4p98EChP/fnijntt8U8B2dac0okGu3ny//vS4jTK7wxiVf6B3cv5EBl9afpLv13RlMYRtwsY4hcHpJJNQtyaEvNtPbMa4tTyl3dL8Tu/APQ/Z55F+QsbyEjEZWhuokGRoJpVeiTllauGmCjG/YRPO8taApx6nAQFXSFIGqOj9djNkDRNn4CnulMLRXYs0hV19BWQ4XV7WziI2KM2Kg7izgdiaqc3iI8Z/hK6/jD1isgnnOl1/UsagXnAqOjXrBB6yy3kCxFy4T/bLN8gGJiHHEbvHW9ZzZRIIO5dBo9ncgPNsPl+JrOajEuvGTCI3ufIN41QgMopp4j7SRYV2Rouo1tzftESSfAl4WXYRIhNPnY/Sp2Rct7QtXFYoDM4GmL3AAKnI1YQxR43X5AUP2D0V2TnrxO8fQlgzV/YO9IhN5WxRTq3gqX5mcwo996RQaFmaDah5oh7NKIoaN8CVvvAyhcHX0xJyNBN9IwHXgNKSECdxq1GmA6ifXwzztfSmYcz15tAZQLxNnufe28AXYiJ8UZlvx6DnFgM7hIAhzTgt6RilND6iG8sEtymitxpx1yv8tqu/abM2vSTj7arNJHQsanQ0xaJfY6+PcZ6ZnizbWe4tTGKvWyQJcSxSy3Klz0iT3DG+4RNmkQs1S2E3G+swLgFyJoCOa9psIAtIRWVQ/5pSToJDDytUOCB/Uw72PyLGQN51Q6wqUjSKzvh/T5PHU6Hnw/iErlfDBYVKyCYeFpAyoL/OM0fBSdmgVo6QsiEentzxmnpVxm4FEEXfdrRDVw1CMlbb77bMkzPq9oemgjf5xOLUkW7ZANedsw0j5yu0P5679wNOrcsJ7Q2hkSxrJtOqp+Bx+pzYrU4wkNO/7HgrV3mF5lUWAYcGuZc5tbQzI+1Hqirj7ftDynRM9/O3nB2He9Ic2X3c7cXl3pGlIYxyvkO9dgRF+LC5ytEA74L3gxQC3yGD/C1nHMf0T8fmFBZqM8nxVb7dzMeROwLK27115JqqHunL4/MK5Rt5iU7GFzBkesUzREKaeipemf8eOFdoZ4tl2HVdnpI2lnQFeNfj1jfgWNb0Rki/Lv7pGssKVSTW/9iyI5ERsMNzZwyFO8PcyWbogMb+4agrgAD5P/D4iivT8aGYgAPcMR2+J6VFavRMgWrH+NqAW5jPbAzISFzKWwNLJLn3MlDUYtIGYF7qG/bjyO3ZMpCunXPhGlSbvFgWBrDbXQIOTVRlbG76xrMxmHfzQP0ofclzc7gxHm3wc+yaYgfLGTje+JE3FFwHYqe4gtcFV2xX3HRvSN8/ll+veVBySyJnsTewy+OUoZqXYmEh+3PBBvtH6Xq30ueWrc8Fs6p/37HQvUH+LIiPq4tPHgyGBKgYKOiPAtRUPyVhPLIX0gcnnHYUaOUs2Uez6aTLAhtsvSscrhnr4Vo+ncSL+smGzG4vzT7Pa4l1GvGIXbI8h0c50Vim6w/Iyq99FHuuassSHczx6ZVlsKaC05K/BZhM4sXgALlbNQXb6ZTnAnur6VsB/VBJRVFe6WOtUYW8nXMyp/GoyqKwGTDKFgduYq/Dc1UJdS/2cv8FUM52iQ/gIk0I7GUglbwjqJ/9DfxAOFNIDwSjHrNFHp2xghdsD5A1sctPpZEgN6VZ01Jty5Tro4xQiBA0mQS9W8mf8+wVRR3I2y2xRG5JyHmQbMuasTfs/daOnSOq9NqU45cnyWIDwB1hvgcGZxciYP+YhMjKVqz192zi2nhdVgDaQtiGMGM3mwV2FV3OVylL3BZpKvi66dby8LpF7DiJWBcst2ys9NE73FNqDjXwjqJ+TM+6KPDcL5F+zh1nLfdmVUXCrRDC+zyR2sAsRRbnXX3WB4soy71jLbVsJDw4nDd3IFCcMnn/xuj66OyWIU2hVpXKC2Ze9wBpa73cmdYBSU4FYiUcysfClMuxk4zd0EQZppDSpq8EEJembInXIiZNvhJVnWlT/6cb0Zk/4EixTIAoyfmV1GdeC2NPPgBlrw4Lee+rMWOMvhQ9ypLEA2RLbF6kzvJ0gIu1YHcgL2dPEZBvFWnL7bjYIK9Dz4xUsaasQdYw7NiQOxdNn4ovIOjKSb1euWlJp6EOEmfS1RTKyrZohZhMEP2c97Px2OXvl8bgoqbZFyX0PN+oZpPuLZs8a4MjoepLl2T80oNNpYTi3IWUeyQeK1gS8ztUz5uHZt6sG+WdYmCmLQlrUpNspD21O9RYXuhBWQcgYEm9ZMDmQuVNiqZN3Vsg3WMvbqNV7Hnr4hNaLAuJZ435dGuLLG7XSmdpoiBLOHt1JZHQZktNa7mkPBRf77ZZwZkowd956+ULTjdrVzzveG8NTKtTEbBXrhBzge0We/gE+9/biIcYPEm3jvCJUjjKUG1OkU7Z+mEZyIeFAQYvwiHFYOMLacf0XFnAqPNsHfXrzSmNK3LUvnewWOlagnBiuiA9i5JOVKoQVS/gnjwwVrRVLSLtsK8/plwwDuBQHmSGLxKx9Xc8duDofu8Xr5L4LHqFJqpj8TK3M5Iypws0RcFFqbLZlJaujVJ2Q7ak6P3TkmmknaVGCb/aoIgRBkAIKAZtFopNsSR5Uq42b6rFcsc0O3RejG2n/SrS67YuZP1C3fB05KinTF2kMa4zrqYOKFuK5hQqOsmUOTMi7be8xFKERfRq2Mi1FTJ2Oa7qQdSuZkHoFJCWFVkNChMIdGh1afS2VLxlknWlsDGXlnFdIt9oM6hF2zry4P7veGNDfOG4IW0wPxgIXYs7ErPd59H8WqJhrYgktQJQMDeULSXlJO2a0qZ+oWdejtJyVDtq76R2zPkpsxcV6PTdB35YoFsV4hF/1fIzhQUOM4YdZst5lP0OoYvUSl1QJoGkJD6NWI7Ext38AWPG4g6Pjc1/tkNUd5XcPjaaH3ePdh2FO0WxP1+niei9NQRcSVLsDTGkOBLf8MRmtjx8LzlGSXJgOQa3Sqj7OISnjtF2UoWYGK3DZWoFVkc8T0zHexgn3aTJnUUSrgfsiFPsi1Zw9TpZBFq21WsPo1Xq5UKSEryMRZsROzQbTKDFk/g7LYYkpiQOC74D2qHBI+71jdHIhnM8qnjXIonUjiqizzX9YzBAYOdeyAsIrNoLfenl8UYhZrFeGCBp6b1Bb6LHNtoXudYGXBt0iM3uZu2iAx2Q16HlfcLQjs5OkzYoH0IG9PLW4C4c2t8vew3uLCTN7YywS8Tq+xG0sH1IJRBqJyXB1ApLUH8xMaqej3p5E1ioGReSCAUuuTdR9FfcwW+kqITuJcWRMLPoGDUK+0+jC4FTsAMq1AAF6Wc061owqkBzfGDHsaqjJGYP05vzdC2qVQ53zihhDzCGuCKgfs8+hPMbDySr5V0YxDsZEy5t6v6BcDU0B66QCGToXBySnLq0YSLZn1x52pHnNEFA2oOJEZULChz3dDPYy4CEUPOktPrBeZI0vjyDZb9As+QIrtK3njg36mjjLDNhCO9iEUo+kW9JOQbv5iRoCXoa+cOkX5qxnZIuItkeULIYuapkebyBOxTXjFZceid/w2mQmLvxWI1g3jZ/I/HOjQRfFf10qQosRtcwaoX3sKyOEOma17jjHHOowiod1mZmr9IPnEd5jdCmE081DPuYpLFYr82ywFwwe0bNn8v8KaiBya0G9Teaa7F+QWAZZHxYyDaWQOfDuIxJzAaeKxcuF6pGIawcm8sN6WSdjg3zBA+1WXed2lOF8LQpggzAJrkL9qptKxWV59QOtP/EOn3a+km6zrta9/xPF3sL/e0B3aJmbnTnLAZpPR+O43iwxr3vduujZirB7DohYKOL18yFui/4lYVyLGqBkt4iGjy87HhXBW30MVV4NbRpqRyxx2NeBbRxmRxh879F6OD4VOMckAyI/4Pie6QG59y297cMiMeCCigwZ0frCk5AVb9t4OiA6hd3NULoG0G1sBFLipMLVnvG5ZQJycCIgI+pNTJ/NzVC4Esjdt5B7yi5PvD9iQBDDHJ1kXRnDCmDCV+QhQZ7knwPBXUUG7WbQkWFqmb2c8Sqc44e/k4vloyRyW4pbQwL/cFd75abbTZqDW6qwJZrZmbmOlBD//Codl03U3S4f2j6fJr+H9z21WdfnaCX+0/HfDe8UqW3DtTMFK+JDLs/f4RXVyciVgw6CyX1JiVcBMsXpaY3FYu8ZGLvL11qgca9ipTgvrjCrUipKUntL1AtkFXO8tiCVV0VgFD2bUHWWP/Jr+2yzyLzGBhq7PdAqAAWzRiSjr79a+lHfQxcWbPc6waSH78GRjBAJjwAbHiaAIHDYg63xMHhEMK07IQigccExECAraUg+Te8lp8TCs4v9zqJ4lPW9m8teGzLYTZs8TXxaP7O8w9EPW9HWx+NvDratKAV8erqtxL4QiWE32LPHgRrGByR88WYPESOHX2sP/ceaGbm6TENxkfunRagWecEuIQog9GLQvJqo2hOSsOeSc7KxBWA0bZgbGHzQECzf++C0QD0lSwoWMvv7+jg9ze/EcC9pwnOaJWXG0Xt1bzXM8ZNuyehy3HxWSjF0exNP4BWgDU5HoVF+OXnpBv5rkPbyL1w+O+rE5n5NTq9oJxpsIb9VBGxArkmd/ctHLPK3J6v0Bdkte1f6vsROJfRiDWZfvKE6wyhUiridq0mfNibnIfs3Ch1piza3ExaO7zVvah3aHTGOlF5kS7SSWS75cPFIIeAV32EUn8+/vsAKyaUzZqlfBDbYXkaFnyllP0bL9tWPc4pHvs5q4xTbWj8nAGgP4E6KvIb9LekkPbqMG5nSoJIkNgOcxA37xWrDR1dEqS+LA67oNjPCikpMBP7VZo4J+ToShPeU3hiXprHG9Tn/vxivUHXWJppMFoMjauv1saeyU1zTYIkxPJipegvElKRNiAsSy8Ku3+uLa6rw6AW7d1c7HMU0dcfJXJEm7RbQcvXf8GifTyXys5RLw+QVpj2SYql7Q0aa7LeUaNbmZzFyRw/xLiGHbZsRN/QSDjWeU28opjLW4P5OXRpJuCKudmwIl66iPJrI1vfiZqNeNtZfxh/t/Qm72XNfwW2mDZFSR+8pFcBeFe2oJ/aswe3NqGvvRSC357LkHKa1fWgt7BP0dYnmx9HDfiZRflTeULtz1a5uVqrVGvTjFYrMg+Wtdinj9DcHLKC2q05+vdc7KlcbFKfFFti3GLUCcKEk9aZjBIlQy7JeV4+MrxuUVqn64jCc/eWzbsvvyadJllYyxfdbfC0/1SBdxNbJzwlWWFsx0K/pMfclo3yJtUf9y3LN9eG0heDF1bMUjm+QDxIU6B2yBKvU1EC6BJu26SfrWkz7+3STbmb0TiryaMydSVyqG/3a3xMY3fIiOdK3O1+Cbf54uyuPfDq0Dg1iFtiQE1BV/0zq1qd3wSXG4WCrOVLc3zT9BSMTuzn+s6IOlQF+QnD1y9maRvkXyU8YbYwtEKHcojhuSnNtELeypNicA7Op+bmce4/l9fOicypzKX3XuHsb1tkIRWXeiNIeX0orf9QtKq9k1Oy/gBTCYo9/Vxj6ELEZ/6LAbXJC9O/+PWcMHkeBaObrGW+Q1tU8FxTn5Zj9jYs9o/F3dZuE+M1JWkDvo4e7LGbvMiAq0PzIjJKjXT69wlHM40olGJsU2SmMnbYmF/0XijMagBXcSqfE3N6grDa3SlFuGHoPN46JNCjXoAtYEXr/FIiW8PtxDw5e77tjPcOMHK+w5bgke5+IbUwK3myq61c6NvSMoJb8m52SGmpN8K/dCdC7d8T913PfE5OlxcQ9DuO4RXiQdqXXVOJ16kYBziuQC5RIMF9XGaJkIPNW4ithLydoNvxiveJVQFIJWRpBmZEhAZ7UWb6kuEEPBO/+dvCLakP/gSPnVjlSnwBHs36jI16PVG2g9cX2iC4YU85glILu/Zl7rfyM/uqLo+ijUTk8k/DxVWAG5aEHJO1XZIAeG/rdez9GLShZ8GORqr+nneSWbsOQyFCg9O3SuDEQu5wfFXdPugoRiLEfYY2gcdOfG47yugV/PvosqFF/rf7JT4O6HvOLqyO9TqdAOK8dPXvM4ts8FldWN1eBmFngvx/hGw2ufYZSdn2VAuIas8m8lA/MptP/fMQEde4YJgd/IQwVE/xGOe9XS5ClGoGP29u2afEYYClbQtavZr3dmATVeq5F2/bkHIMr6BcTZRVQEPmv92Kjdn8KVzpsPhNbocGcUX1PLurcPKvxfM8lc7ta77GgL1jcxZ79DxqZTYOrmy7trTzkLRkrNKxPT+lLsY0dzJN5R9LOtHt4/PAN3+HJzKc76q2g0YKTluHqXBZy9k4UFgo2b240uZRmI7P0GQMC7kK6h1U3nxc3py/TQL2osyZzpTmYMq1YUf6Ho/D5rgM1MQpx8XGCI1cxajRRimqv28m98otrWr9fuhmA3nWnMXoWVcbYhd8Oy+ceMY/PuSvw2BBKL7AyYltXUlZao70i8fqp1GvJ27qfcsDEVmWnR0hFLxcLe2WMDLC7Ki8AaxS977eb8tTtUfhNlIvUfQxvpuOVXqz5gpTZg9wHZrFix2aFGlbiyYgR+Xpk44o8ufKNhRLooTJQn2ajp+sDHNUrrJS5+CViSng7MheLxXlKfWfODFWaSdpEUhXEb7AipePdCbB+XC3tvMY8U/XNwbB58OxtdTr3KldX8IHxXjFyRS7TKMOT0ZfShI/N6Q2b0FbktuX4DAM9bVQiUvbx7sENqI2LX7L25eFVXSsPnC8UcPH4x81xmBIZXXpjySelloDeW97De8JLPTLy7k8+z31OmiA/VE+/BmlNj4qxCSeSBQmzaJs0f6sOMo8LUFRxqB77HqCngpvUk6R/gJntpgPo6fgPzNP985YtQ6tNQjY3eBcoW8Sj1C9TnqJ0/fsP8eV1bAS3AV1x+c9CBcP0jSoDd1VR0WKdfjMWrzDWu9zFN+0onIf+O5P0pYK+9+2j5EzL76y6lECmR04l7zJDV04YJadL3SL9EOv8yVyRook3oVhfKS38pM2KEKGuUJ1aWPys+kal+Omv98OmZ0t1trN2uX8Ms8zX3yGgoHbadcNB0b8L0bsuOFycyOqIVaqVQ0hugF/OmIPltEzM+vvv+D+1qh5Lt5MM83+EiTs7v3/BFS0j3zDNJ73XGH9KW/0BoB3bkEP27ObrE1gxv3FfotapXr5uuMuDBpUECzY02Zs0tNPWG04VssxJX4ZaU4iTRihJe+s4QQtOSaM7ZgnS2oZmT91EC1BFRcQjzR6hXgBCvJnpB31ycSXv4anKjj17S1C7xk6wtj2brF3ngSdFTjz/JG03UPs3IKQo0gRKaGzaAmELVRzlRyAT5Jcey40WGbaKmfdnn/IEagwuSMISLrFvzZcVxpAlRaPC2k3QRjyPbJuVsXDriNFgSSd91as029JBJ+iPorkB8X7ln1PkZXZyXzQOZ5StOj4bAXBs0fYeqhwMLmsfoemXuMDrHfI5vrGhVRyyg+fLiKW9aDV+69EoPpoSY/haFkHy4ypjj8aocT8xMeKWuE6/0KAT4peAS8/3BRei5M0LD66yq0blkts+LJkPc2LhyafSq4BJZ60+fERxvPjQ6xFq84bI+Ln05IOU1A1oOaT6/3kwBdErqcOjIxyOKlv362tzfN9gQFlxaMKTH1szEQLu/UBGmatHW1skWE+haFlDSZLO3Q7owmcIkUWV6Raud/zDfpDSc5DaFG5Y2HASEuSrLmB/zxQlDnCfgttLWDiWtkIVmD52JdKTMTR+FWLWHMwGvjjJFofKuLK/kZtc30e+TbTH6ZqB4yENN5nSj17xhGPOnNiKPuebTSJdM88ldgVKcPO9vXc6OnL+0hU2kj50V7xedImxUZuHKVZ/GdOiksj4ttpSRgD9IfAW7ASFtIobXh9CAQgmWn5r/1o9VxWl229ObKvrRXJ73R4pkovKDWuCJnVFdBF8lxWWh45sBejmctasXykJ5KUV++b6uzOWqDuiumaenp+C9iAa51zp/1Qib+zouFRww4nxfMtAx0hKZ8l4nPsj2mSK7I5IAr5EXlgoWKeUYfnszFpFxqQ8SFXkIJUNH/ZE3MYNEYSic6iB4MxeHXrDVT173bpZktE1gZZ9j+79rymSEcqi9hmj1WXwBvkF9d7ZL4xTspOVvxW/Y74MHmk7F76tWCepEsUGIhtcjmKQ7ciBfyB/TK8Tih5Ay4S+CYx30iLqEmelKbN5FZXrFee9B6Dy/0DFawsYYiflKuPlPJmppY1fUmv+uwROAdh9A8jg9o7ZP6GxnisTqKgO4uL/pKQYts2pAatjiXptvNVGF8DVtfvE+hfCK4FsHoxovsPI5DO0NReJlc0Y/t1htuF0ALISHfyqPUem6PFFVxhGuY3wDRPUFpy3mGEoBAxOf8caz5AJZXKni6FVSQ1j3xJNHGQAjGEVilcRh+b18m+ZzGBR8SLcRdvC1Xkb405CaQIbuOICROeSMpFm9SopJOvgzUXQzq4y1KW0U5AzgjmQHl4hWDStC1ik4Rbe3o5UdgTvKHpiE1eR6hjrtrGPqRAPL2qBD+gvow2J31TR5+7qWr5q7PmpOaNXxJT7H5ZHfq+IO+NuljXwyP3m1a2r/lj5HDSqOMeu8GNZRotWOOASv2A5oPnZHupFY2Tfy3AKPcv2gAo6M5KYv137/eSxPr76Pd/hDhT6/K2A/X03k1zmPeoKaSFkcYPJyHyW3jcQ7Xe60W7+dduvqn1csUwdS/rgjhzvxKIXs98wK5LOXozaYIVLsJzZB5UFmnSlKP/j7fjjz4W/VjEbRUdrqLfjqAfY/X9sYMHhi/6l9j3ID8HBfIhg2D24Ry9iTTJoso8OCJ8OIs0YR5X56UQCYbEzRVuCxkThAgppDyrdP/2sggg2FcePPUg2BTd5WVZslqKb9wZ1xuULGFiLFahDAU2xJ9bEKosmMp11Lo6cybM9vocRxDBO+VenehLIxo98S6xP+jElWs7rz0Gzm54KcTeaWG6ZVf36YiSXeBUCpHU8bzHwXzj/BxMg3EpFlk4HvphLxptsfrkI4DgWjHG6H51zV0cZgA7PqiyxtcR7GZMunHIkKUlrBt3lFIFeF1Rfh4iXNGx3D/FkGp7qAhHlDrnZLqpGH0Zho7L/w5lZE/PNeDtecBxVPQX6YpmffamOMI16kX9PmJYxPAniBNeG6tE9EBC3Uvh2vCMdEFA0wG6d7ODEvrsYgyUj5NBHoTpcW0w4PZvAhph2Xvogq//gmV6/xI7OIB4tOgBtKv+g/xhP1ESX5s6bFcJ4ACOvyDpVqEpqfANy9QUpuZm2Vri1JIfWvzht3K+CfJsoFku8DnaLQAVy5Lt2qew7841QIFT9c5/jh9Uh2RkGMcL+2Uz0gcAxPG7xrvS76S4+sA/r0gXp0hToyVfJK288Xn7zs877jy7PgPKuMEyoCYlnj5XAvfK/q/gg7/3a76brkMJm1JlBLHibVILjaQnBWoybzbrRQbRN6CKVDHOM5cbOD7WWJ2NLh6wDfHyC1RMn6J4kYYOTv7U0bRgM/qZPZsPXyj5Fvism6it/XfNVqN6zJaufEtPvqlLaoSG64SZUblxOGHUb76G0KV1KCycRgsNwdRBuxgGrJ1dh9t7JsEVvg8L0OuMiFnnGganoZ1gTr8muV6X/XeyLKU+Q/csa/eDLE+66aOGW0EUVge0Ze6Dq2fKVRTsvgV2/OwPTd6S8dFLViEG4/JHhoPsz0+hfJwuVVGYUBpYnjGzAXnwiM2Xkqe8LnwpzLDX2r0ngPpqgPvf8lVdqnfMMEOT90Qtw2dxLiKDWdyZ7dX4sPeK0Fbn9G4+nyFrk23BV9vBwgeL/BY70ZjjrnVyqR6cfUkQxyKlzcHE36UeNqzYyJ5ZZj/hf6LMxxh66J8uvAVzGqc3cjojDKFDk1maFV5UZyHphJviQB64tDgE7c+ZBpYTU9icljEVPhiX6L4EAeJsBPLSRbRlfcCSlrs5QYUetsWVxgmwJkAn/qmRu8e7YcCboddTys+0cZi9UU9Ul+n7l+hjvoKOv0V0ZqoXjigj5O5xD8FmGUVaS3ciuTG/xLOvAyUGbVBLwcqCHjVBrcldae6WBbrZQjkTC5JtHLJ+9SDXBM7MqRgvHcLrbNZYHwPHXH4k2DZkbceLNEXxPipmfgF4hKxI2HVXTsPE1DIZ8otfQxrtKM9tE6roEyk9a7hlsXTtsGCQ73bE3Pj3f9ZkrbzK8JrzxTm8RsfVQ7N7GFkisx3gQ3aAkcihB3In4JXsq+zXVLjA4ykkrs/POKLkT/JcULVXpIB23xsv9VMm7mkf1D65rsRslcdrlUZtmGMYLHvaDyEEXVfWa8OURm28fCtnCmHHf6+sVQ57WGJKEpt9DHKWfNpclMMg1Et2liWQ5vxJLAkmRnsN4JdbZzEVhTnc5KNvYJ/s01t6gz3nY2PjkKX+fcKJ2v0w1KkPTGNyju7M5ftEFzSuypFnqJ/pKotCqrO7W5BA9N7cGbu1JpvG6txMH7+s6GJDF6ISLmOz7lCg4CfsuQBYOyRHgZPOmf7d3G8XboBHfSk7jhur1aqf6crqKG5ZpzwEF+46wkCmbiUz9nBWMizorlYlU/8rqmA39SmZmk0XsXlukHgyY77gDXi3iHF5d4GVYYR98xSX7WTfYJnLsNMAun4acVkvYIp5BjdfPTliv4rvzt4P0+NKoGAAVVIFufx/5jqN6pnv0DmrqmHQWCViDLoC+Cy1zQaGQeUZA5s0tHoNkGN4sN7XVe/A4GCvZz6xiaKLSF53ZynBz5efhbZP3on1X4rptLdu1o2NSOqKjxzEja8cpoA7OL8esmJEV7PLVjIwAsZzEF9Xs8op9v7qPyUPWds7Tpov+oBuqTWUK5cByrCQDfJsKNkQtMFgkm4o8sMXsRHuyq+hRKJpBAZ74aTAYmDNw7qDt0BvTBhh+ZN6CU8qijBD/GJ9S+LFnIwFf8e7/8ZVm7zcNxnM5Gf8CYPGNGOfKn2D7mIOUzVmbN1sUtKk29m1v7BWrI37lbF5F+ghWLD4VpyoFa7xL2SRCwhnUKPUUF7uMhX6smANXTeveYsoVxOw5RICvV8spvbpbrEdc/F/ihuf+rhJdDucz/VDWqiBewPPAnxS1E0hbbyYKg0o1YXX+lvoSALC5K5wBM4/dLtyls0yU2h4fs35Q5wRmB8LJfS4ewbW25Dto+8Z6DSe1kvjCn3iCvXS0+A5+Zgh5zOknsCnEfQjaAPmJ+TL2ROz5RCOimHrwZp3vfoQpscpoKBjLnxtdS3Qw/ISu6kD87kwdgTCoT3iu6HZkv8MO/6fPXjcO8zv/7n3uIiQ/1K0/gLe4tDj5GuhV8kB9OfuIdfIb2el89rOHWhm6187T/NEgj8c9BnErmBB9QKbvsqJhu9+oUnxAxOsONMTWHhEwJzuxdOzPb6a2gGdDw1c7JW1rs5QYjHNfezBPLGYPsG65Ubs7voER143MetnRlOdsUub+lOvun4TzWEUwOWKOqLds2boqvHA+hY7NtwgrbhfZL9oze/Rss/7YaXbcM/AzqduLN3KwtWwlVbvLLfieu3s7Rda8QQFesSE9oGrSvVOE/6d9pwhjWp1hrRUGMm709ON2QGWLJRG9ASGgw3W7pzzxgCgtNnoT/bsppEmAKc9iRY57tXru6KscWbLMDh8C24lWc0qahN5MZbGmehsID7fSGuwy3k5Rsx0V6WHin3CpKpU938OtECk/Q99DM4WneHAo3yRQMCDPdMC1nbHZraK08ZScgqoprxmSNFEmB5X6FFwWiSmXuCrKW1SEGWn3DUIvm8EbTzv+1GM9BByb6wS8RLu5QGx4Jthq/fzHXi+g/ABzxdwHvxhiZUhbtHAA4B1nwPjs18Ay6B8N08g+mJZZb8Zp7NErxm6VZ1rzir7xnkL2Y1oHQAVZesTHTSFW7TmD8idxvSV+7WsrbHfxYK+zKNGb5d9GOP+6cg8Mk0JLmMC0zCq3bt5iwgVfxamhvScAgEAxMILpLlDGU/9AQ72rd6/fqbzL+Vs3xqRQQMG0hzQ1cc8aoBWXDPeHHfAfjnkhuJpfQPipEiPKsJZCX0rtHyc7QfpIrHHdop60XXjlrz7IFywwN0Dcew8PeUDsAOGQxp0gxyoglaYxpJllbBoeFRxkHtpxNHwOmYJ5XYlft/4XVSuvgkCOL5tSQxRnW8ZLSJto1ljcKmlU9idzhrKRxOYXidu79wq2uU23KCm7zfWonIzn77MdbazkPJd7SkpqwJEUsEhASdn/+0SANBjv4cHVHsxChDuNIbDov2IUTk6LroI+HEDdKOiu9f0VIvKHUS5Jjp3JHHYk4h29W9Cwf7rQx/H54bH9F9u95vD4IuOvp+BEZm8TJy9AQxnqXo2UgnIeDn1wz6xussbBYD+RVmJahwxqlh7EXkDkDtGVBwNyDIFb777gSQw/Y+KJ+UwAJAzsX5k4wJPGjFxBNMAqq4YwLcq9c8GAbEUINWrAcJY4vFDoyhaqLQCqV+ICIqFAMQjQFS1OELoFmvtVwsxnM7mCoZ06N7VtwBBqhcHdLpCAqm/KKlQQGP34P4zhQ6AazjR9Zrt2BIqAM7IIVfLs39OD6b7gAuK8N2aVxVudriyVi3i9gXRPdAlQYOck+yOwamBgQKG5Dm+Fs7uAHju4nmvnXO3YgICt7IIKNRyETBBdhNwBJ8JBLehEkje5dvdkdrQoWXIjgEA3FADAeErkYByX2YCJlkDAUd1g0AghSWQwos799zFNT8riVSr0apOGadSDcgiFIlExsEWI4Yx9lpnMoEqNmbSs6G9RDFXh8Vr1KjGNtTVMzufU2VqyWVcmIpUM5GhiUyHTP3YVKcJ7Q5kUsRVK+6cyabScycLJeBK2as41GHloSVocxiR5wf+SzlzUST5pwgPRVBlI+ttVSXFrrKammpLY0uz9ZTnO3XSIr2x+ZHMb2OEPqdGdSiXwlCBa7afKkedLTQ6KGS9KBSs3B80wGAHnZ0audhsDnwXvXQHIAYTGxtHimwZcmVKWRhiQRndBxeTbDYnJTsjNzNlOSTlGAuVtEFByQsQbqCOjDjnuLDUKzsW/MmEA5ryVSuuBrCRFa4bOmkNuEGNZCws9YpOckwN61qPqf5EMqpqdZxYNKRUwIeUq3+2uaW+zPVxw1tppCpwjWgG0ws3sKKB9s7HalP6tf+aAhBhQhkXvHgj8EHky88M/kgCBAoSbCayEKHChKOgookQKUo0OgYmFrYYHLHicMVLkChJshSpeNKk4xMQEhGTkJKRU1DKoKKWSSOLlo6eQbYcufIY5TMxs7AqUMhmgw6dThjxrS5D+o3bYkKfJxZa6nd/GDSqxzkv/Ga1rT7502fr7XDZlJ3siizicFWxS6646ZrrbvhOibtuuW0Xp18t9sA995X6wU96lStToZJLlbWq1apRp16jBk2afa/FLK1mazPHYeu0m2ue+X70s6Me2m2PR557bK99DjrkvP0OuKDbSacC+ji4v+thyHTnAnwQAAAA) 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;}} - - - - - + + - @@ -148,67 +142,56 @@ + - - - - - - - - - - - - - + @@ -219,16 +202,3169 @@ + - - - Port - - - - + + + + + + ? + + + + + + + + + + + + + + +
+
Azgaar's
+
Fantasy Map Generator
+
v. 1.5
+

LOADING...

+
+ +
+ +
+ + +
+ + + +
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ +
Сlick the arrow button for options. Zoom in to see the map in details
+ + + + + + + + + + + + + + + + + + Port + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -800,60 +3936,18 @@ - - - - - ? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - + + + + + + + @@ -875,2831 +3969,17 @@ - + - + - + - - - - + - - - - - - - -
-
Azgaar's
-
Fantasy Map Generator
-
v. 1.4
-

LOADING...

-
- -
- -
- - -
- - - -
- -
-
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
- -
Сlick the arrow button for options. Zoom in to see the map in details
- - - - - @@ -3716,10 +3996,11 @@ + - + - + @@ -3727,7 +4008,7 @@ - + @@ -3758,9 +4039,11 @@ + + - + - + diff --git a/lang/lang-en.js b/lang/lang-en.js index 2ef89bbd..b3361d23 100644 --- a/lang/lang-en.js +++ b/lang/lang-en.js @@ -212,7 +212,7 @@ const sourceDataForReference = { burgEditIconStyle: "Edit icon style for burg group in Style Editor", burgEditAnchorStyle: "Edit port icon (anchor) style for burg group in Style Editor", burgSeeInMFCG: "Open burg in the Medieval Fantasy City Generator by Watabou. Ctrl + click to change the seed", - burgOpenCOA: "Open burg's COA in the Iron Arachne Heraldry Generator. Ctrl + click to change the seed", + burgOpenCOA: "Open burg's COA. Ctrl + click to change the seed", burgRelocate: "Relocate burg", burglLegend: "Edit free text notes (legend) for this burg", burgRemove: "Remove non-capital burg. Shortcut: Delete", @@ -355,7 +355,7 @@ const sourceDataForReference = { NeedToAdd!: "Neutral means states relations are neither positive nor negative", NeedToAdd!: "Suspicion means shate has a cautious distrust of another state", NeedToAdd!: "Enemies are states at war with each other", - NeedToAdd!: "Relations are unknown if states do not have enought information about each other", + NeedToAdd!: "Relations are unknown if states do not have enough information about each other", NeedToAdd!: "Rivalry is a state of competing for dominance in the region", NeedToAdd!: "Vassal is a state having obligation to its suzerain", NeedToAdd!: "Suzerain is a state having some control over its vassals", @@ -508,4 +508,4 @@ const sourceDataForReference = { NeedToAdd!: "Load .map file from local disk", NeedToAdd!: "Load .map file from URL (server should allow CORS)", NeedToAdd!: "Load map from browser storage (if saved before)" -}; \ No newline at end of file +}; diff --git a/libs/alea.min.js b/libs/alea.min.js new file mode 100644 index 00000000..f4ecef67 --- /dev/null +++ b/libs/alea.min.js @@ -0,0 +1,3 @@ +/*https://github.com/macmcmeans/aleaPRNG/blob/master/aleaPRNG-1.1.js +©2010 Johannes Baagøe, MIT license; Derivative ©2017-2020 W. Mac" McMeans, BSD license.*/ +const aleaPRNG=function(){return function(n){"use strict";var r,t,e,o,a,u=new Uint32Array(3),i="";function c(n){var a=function(){var n=4022871197,r=function(r){r=r.toString();for(var t=0,e=r.length;t>>0,n=(o*=n)>>>0,n+=4294967296*(o-=n)}return 2.3283064365386963e-10*(n>>>0)};return r.version="Mash 0.9",r}();r=a(" "),t=a(" "),e=a(" "),o=1;for(var u=0;uarguments[1]&&(n=arguments[1],r=arguments[0]),f(n)&&f(r)?Math.floor(l()*(r-n+1))+n:l()*(r-n)+n},l.restart=function(){c(a)},l.seed=function(){c(Array.prototype.slice.call(arguments))},l.version=function(){return"aleaPRNG 1.1.0"},l.versions=function(){return"aleaPRNG 1.1.0, "+i},0===n.length&&(window.crypto.getRandomValues(u),n=[u[0],u[1],u[2]]),a=n,c(n),l}(Array.prototype.slice.call(arguments))}; \ No newline at end of file diff --git a/libs/lineclip.js b/libs/lineclip.js deleted file mode 100644 index b43da33b..00000000 --- a/libs/lineclip.js +++ /dev/null @@ -1,103 +0,0 @@ -'use strict'; -// lineclip by mourner, https://github.com/mapbox/lineclip -// Cohen-Sutherland line clippign algorithm, adapted to efficiently -// handle polylines rather than just segments -function lineclip(points, bbox, result) { - var len = points.length, - codeA = bitCode(points[0], bbox), - part = [], - i, a, b, codeB, lastCode; - if (!result) result = []; - - for (i = 1; i < len; i++) { - a = points[i - 1]; - b = points[i]; - codeB = lastCode = bitCode(b, bbox); - - while (true) { - if (!(codeA | codeB)) { // accept - part.push(a); - - if (codeB !== lastCode) { // segment went outside - part.push(b); - if (i < len - 1) { // start a new line - result.push(part); - part = []; - } - } else if (i === len - 1) { - part.push(b); - } - break; - - } else if (codeA & codeB) { // trivial reject - break; - } else if (codeA) { // a outside, intersect with clip edge - a = intersect(a, b, codeA, bbox); - codeA = bitCode(a, bbox); - } else { // b outside - b = intersect(a, b, codeB, bbox); - codeB = bitCode(b, bbox); - } - } - codeA = lastCode; - } - - if (part.length) result.push(part); - - return result; -} - -// Sutherland-Hodgeman polygon clipping algorithm -function polygonclip(points, bbox, secure = 0) { - var result, edge, prev, prevInside, inter, i, p, inside; - - // clip against each side of the clip rectangle - for (edge = 1; edge <= 8; edge *= 2) { - result = []; - prev = points[points.length-1]; - prevInside = !(bitCode(prev, bbox) & edge); - - for (i = 0; i < points.length; i++) { - p = points[i]; - inside = !(bitCode(p, bbox) & edge); - inter = inside !== prevInside; // segment goes through the clip window - - const pi = intersect(prev, p, edge, bbox); - if (inter) result.push(pi); // add an intersection point - if (secure && inter) result.push(pi, pi); // add additional intersection points to secure correct d3 curve - if (inside) result.push(p); // add a point if it's inside - - prev = p; - prevInside = inside; - } - points = result; - if (!points.length) break; - } - //result.forEach(p => debug.append("circle").attr("cx", p[0]).attr("cy", p[1]).attr("r", .6).attr("fill", "red")); - return result; -} - -// intersect a segment against one of the 4 lines that make up the bbox -function intersect(a, b, edge, bbox) { - return edge & 8 ? [a[0] + (b[0] - a[0]) * (bbox[3] - a[1]) / (b[1] - a[1]), bbox[3]] : // top - edge & 4 ? [a[0] + (b[0] - a[0]) * (bbox[1] - a[1]) / (b[1] - a[1]), bbox[1]] : // bottom - edge & 2 ? [bbox[2], a[1] + (b[1] - a[1]) * (bbox[2] - a[0]) / (b[0] - a[0])] : // right - edge & 1 ? [bbox[0], a[1] + (b[1] - a[1]) * (bbox[0] - a[0]) / (b[0] - a[0])] : null; // left -} - -// bit code reflects the point position relative to the bbox: -// left mid right -// top 1001 1000 1010 -// mid 0001 0000 0010 -// bottom 0101 0100 0110 -function bitCode(p, bbox) { - var code = 0; - - if (p[0] < bbox[0]) code |= 1; // left - else if (p[0] > bbox[2]) code |= 2; // right - - if (p[1] < bbox[1]) code |= 4; // bottom - else if (p[1] > bbox[3]) code |= 8; // top - - return code; -} \ No newline at end of file diff --git a/libs/lineclip.min.js b/libs/lineclip.min.js new file mode 100644 index 00000000..d1796476 --- /dev/null +++ b/libs/lineclip.min.js @@ -0,0 +1,2 @@ +// lineclip by mourner, https://github.com/mapbox/lineclip +"use strict";function lineclip(t,e,n){var r,i,u,o,s,h=t.length,c=bitCode(t[0],e),f=[];for(n=n||[],r=1;re[2]&&(n|=2),t[1]e[3]&&(n|=8),n} \ No newline at end of file diff --git a/libs/pell.min.js b/libs/pell.min.js new file mode 100644 index 00000000..c5be57e5 --- /dev/null +++ b/libs/pell.min.js @@ -0,0 +1,2 @@ +// https://github.com/jaredreich/pell, MIT License +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Pell=t()}(this,function(){"use strict";const e=(e,t,n)=>e.addEventListener(t,n),t=(e,t)=>e.appendChild(t),n=e=>document.createElement(e),i=e=>document.queryCommandState(e),o=(e,t=null)=>document.execCommand(e,!1,t),l={bold:{icon:"B",title:"Bold",state:()=>i("bold"),result:()=>o("bold")},italic:{icon:"I",title:"Italic",state:()=>i("italic"),result:()=>o("italic")},underline:{icon:"U",title:"Underline",state:()=>i("underline"),result:()=>o("underline")},strikethrough:{icon:"S",title:"Strike-through",state:()=>i("strikeThrough"),result:()=>o("strikeThrough")},heading1:{icon:"H1",title:"Heading 1",result:()=>o("formatBlock","

")},heading2:{icon:"H2",title:"Heading 2",result:()=>o("formatBlock","

")},paragraph:{icon:"¶",title:"Paragraph",result:()=>o("formatBlock","

")},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= ` + ${shieldClip}${divisionClip}${loadedCharges}${loadedPatterns}${blacklight} + ${field}${divisionGroup}${templateAboveAll()} + ${overlay}`; + + // 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}
diff --git a/modules/ui/editors.js b/modules/ui/editors.js index e858beaf..5e1b6c1f 100644 --- a/modules/ui/editors.js +++ b/modules/ui/editors.js @@ -21,7 +21,8 @@ function clicked() { const p = d3.mouse(this); const i = findCell(p[0], p[1]); - if (parent.id === "rivers") editRiver(); + if (grand.id === "emblems") editEmblem(); + else if (parent.id === "rivers") editRiver(); else if (grand.id === "routes") editRoute(); else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel(); else if (grand.id === "burgLabels") editBurg(); @@ -123,8 +124,14 @@ function addBurg(point) { const temple = pack.states[state].form === "Theocracy"; const population = Math.max((cells.s[cell] + cells.road[cell]) / 3 + i / 1000 + cell % 100 / 1000, .1); + const type = BurgsAndStates.getType(cell, false); - pack.burgs.push({name, cell, x, y, state, i, culture, feature, capital: 0, port: 0, temple, population}); + // generate emblem + const coa = COA.generate(pack.states[state].coa, .25, null, type); + coa.shield = COA.getShield(culture, state); + COArenderer.add("burg", i, coa, x, y); + + pack.burgs.push({name, cell, x, y, state, i, culture, feature, capital: 0, port: 0, temple, population, coa, type}); cells.burg[cell] = i; const townSize = burgIcons.select("#towns").attr("size") || 0.5; @@ -141,7 +148,7 @@ function moveBurgToGroup(id, g) { 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;} document.querySelector("#burgLabels > #"+g).appendChild(label); document.querySelector("#burgIcons > #"+g).appendChild(icon); @@ -171,6 +178,13 @@ function removeBurg(id) { const cells = pack.cells, burg = pack.burgs[id]; burg.removed = true; cells.burg[burg.cell] = 0; + + if (burg.coa) { + const coaId = "burgCOA" + id; + if (document.getElementById(coaId)) document.getElementById(coaId).remove(); + emblems.select(`#burgEmblems > use[data-i='${id}']`).remove(); + delete burg.coa; // remove to save data + } } function toggleCapital(burg) { @@ -544,14 +558,17 @@ function unfog(id) { } function getFileName(dataType) { + const formatTime = time => time < 10 ? "0" + time : time; const name = mapName.value; const type = dataType ? dataType + " " : ""; const date = new Date(); - const datFormatter = new Intl.DateTimeFormat("en", {month: "short", day: "numeric"}); - const timeFormatter = new Intl.DateTimeFormat("ru", {hour: "numeric", minute: "numeric"}); - const day = datFormatter.format(date).replace(" ", ""); - const time = timeFormatter.format(date).replace(":", "-"); - return name + " " + type + day + " " + time; + const year = date.getFullYear(); + const month = formatTime(date.getMonth() + 1); + const day = formatTime(date.getDate()); + const hour = formatTime(date.getHours()); + const minutes = formatTime(date.getMinutes()); + const dateString = [year, month, day, hour, minutes].join('-'); + return name + " " + type + dateString; } function downloadFile(data, name, type = "text/plain") { @@ -630,4 +647,17 @@ function selectIcon(initial, callback) { Apply: function() {callback(input.value||"⠀"); $(this).dialog("close")}, Close: function() {callback(initial); $(this).dialog("close")}} }); -} \ No newline at end of file +} + +// Calls the refresh functionality on all editors currently open. +function refreshAllEditors() { + TIME && console.time('refreshAllEditors'); + if (document.getElementById('culturesEditorRefresh').offsetParent) culturesEditorRefresh.click(); + if (document.getElementById('biomesEditorRefresh').offsetParent) biomesEditorRefresh.click(); + if (document.getElementById('diplomacyEditorRefresh').offsetParent) diplomacyEditorRefresh.click(); + if (document.getElementById('provincesEditorRefresh').offsetParent) provincesEditorRefresh.click(); + if (document.getElementById('religionsEditorRefresh').offsetParent) religionsEditorRefresh.click(); + if (document.getElementById('statesEditorRefresh').offsetParent) statesEditorRefresh.click(); + if (document.getElementById('zonesEditorRefresh').offsetParent) zonesEditorRefresh.click(); + TIME && console.timeEnd('refreshAllEditors'); +} diff --git a/modules/ui/elevation-profile.js b/modules/ui/elevation-profile.js index c47c833c..f8b40d29 100644 --- a/modules/ui/elevation-profile.js +++ b/modules/ui/elevation-profile.js @@ -83,7 +83,7 @@ function showElevationProfile(data, routeLen, isRiver) { chartData.ma = Math.max(chartData.ma, chartData.height[i]); } - if (lastBurgIndex != 0 && lastBurgCell == chartData.cell[data.length-1] && lastBurgIndex < data.length) { + if (lastBurgIndex != 0 && lastBurgCell == chartData.cell[data.length-1] && lastBurgIndex < data.length-1) { chartData.burg[data.length-1] = chartData.burg[lastBurgIndex]; chartData.burg[lastBurgIndex] = 0; } @@ -196,7 +196,26 @@ function showElevationProfile(data, routeLen, isRiver) { const x = chartData.points[k][0]; const y = yOffset + chartHeight; const c = biomesData.color[chartData.biome[k]]; - const dataTip = biomesData.name[chartData.biome[k]]+" (" + chartData.height[k] + " " + hu + ", cell " + chartData.cell[k] + ")"; + + const cell = chartData.cell[k]; + const culture = pack.cells.culture[cell]; + const religion = pack.cells.religion[cell]; + const province = pack.cells.province[cell]; + const state = pack.cells.state[cell]; + let pop = pack.cells.pop[cell]; + if (chartData.burg[k]) { + pop += pack.burgs[chartData.burg[k]].population * urbanization.value; + } + + const populationDesc = rn(pop * populationRate.value); + + const provinceDesc = province ? ", " + pack.provinces[province].name : ""; + const dataTip = biomesData.name[chartData.biome[k]] + + provinceDesc + + ", " + pack.states[state].name + + ", " + pack.religions[religion].name + + ", " + pack.cultures[culture].name + + " (height: " + chartData.height[k] + " " + hu + ", population " + populationDesc + ", cell " + chartData.cell[k] + ")"; g.append("rect").attr("stroke", c).attr("fill", c).attr("x", x).attr("y", y).attr("width", xscale(1)).attr("height", 15).attr("data-tip", dataTip); } diff --git a/modules/ui/emblems-editor.js b/modules/ui/emblems-editor.js new file mode 100644 index 00000000..6a8a945b --- /dev/null +++ b/modules/ui/emblems-editor.js @@ -0,0 +1,392 @@ +"use strict"; +function editEmblem(type, id, el) { + if (customization) return; + if (!id && d3.event) defineEmblemData(d3.event); + + emblems.selectAll("use").call(d3.drag().on("drag", dragEmblem)).classed("draggable", true); + + const emblemStates = document.getElementById("emblemStates"); + const emblemProvinces = document.getElementById("emblemProvinces"); + const emblemBurgs = document.getElementById("emblemBurgs"); + const emblemShapeSelector = document.getElementById("emblemShapeSelector"); + + updateElementSelectors(type, id, el); + + $("#emblemEditor").dialog({ + title: "Edit Emblem", resizable: true, width: "18em", height: "auto", + position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}, + close: closeEmblemEditor + }); + + // add listeners,then remove on closure + emblemStates.oninput = selectState; + emblemProvinces.oninput = selectProvince; + emblemBurgs.oninput = selectBurg; + emblemShapeSelector.oninput = changeShape; + document.getElementById("emblemsRegenerate").onclick = regenerate; + document.getElementById("emblemsArmoria").onclick = openInArmoria; + document.getElementById("emblemsUpload").onclick = toggleUpload; + document.getElementById("emblemsUploadImage").onclick = () => emblemImageToLoad.click(); + document.getElementById("emblemsUploadSVG").onclick = () => emblemSVGToLoad.click(); + document.getElementById("emblemImageToLoad").onchange = () => upload("image"); + document.getElementById("emblemSVGToLoad").onchange = () => upload("svg"); + document.getElementById("emblemsDownload").onclick = toggleDownload; + document.getElementById("emblemsDownloadSVG").onclick = () => download("svg"); + document.getElementById("emblemsDownloadPNG").onclick = () => download("png"); + document.getElementById("emblemsDownloadJPG").onclick = () => download("jpeg"); + document.getElementById("emblemsGallery").onclick = downloadGallery; + document.getElementById("emblemsFocus").onclick = showArea; + + function defineEmblemData(e) { + const parent = e.target.parentNode; + const [g, t] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] : + parent.id === "provinceEmblems" ? [pack.provinces, "province"] : + [pack.states, "state"]; + const i = +e.target.dataset.i; + type = t; + id = type+"COA"+i; + el = g[i]; + } + + function updateElementSelectors(type, id, el) { + let state = 0, province = 0, burg = 0; + + // set active type + emblemStates.parentElement.className = type === "state" ? "active" : ""; + emblemProvinces.parentElement.className = type === "province" ? "active" : ""; + emblemBurgs.parentElement.className = type === "burg" ? "active" : ""; + + // define selected values + if (type === "state") state = el.i; + else if (type === "province") { + province = el.i + state = pack.states[el.state].i; + } else { + burg = el.i; + province = pack.cells.province[el.cell] ? pack.provinces[pack.cells.province[el.cell]].i : 0; + state = el.state; + } + + const validBurgs = pack.burgs.filter(burg => burg.i && !burg.removed && burg.coa); + + // update option list and select actual values + emblemStates.options.length = 0; + const neutralBurgs = validBurgs.filter(burg => !burg.state); + if (neutralBurgs.length) emblemStates.options.add(new Option(pack.states[0].name, 0, false, !state)); + const stateList = pack.states.filter(state => state.i && !state.removed); + stateList.forEach(s => emblemStates.options.add(new Option(s.name, s.i, false, s.i === state))); + + emblemProvinces.options.length = 0; + emblemProvinces.options.add(new Option("", 0, false, !province)); + const provinceList = pack.provinces.filter(province => !province.removed && province.state === state); + provinceList.forEach(p => emblemProvinces.options.add(new Option(p.name, p.i, false, p.i === province))); + + emblemBurgs.options.length = 0; + emblemBurgs.options.add(new Option("", 0, false, !burg)); + const burgList = validBurgs.filter(burg => province ? pack.cells.province[burg.cell] === province : burg.state === state); + burgList.forEach(b => emblemBurgs.options.add(new Option(b.capital ? "👑 " + b.name : b.name, b.i, false, b.i === burg))); + emblemBurgs.options[0].disabled = true; + + COArenderer.trigger(id, el.coa); + updateEmblemData(type, id, el); + } + + function updateEmblemData(type, id, el) { + if (!el.coa) return; + document.getElementById("emblemImage").setAttribute("href", "#" + id); + let name = el.fullName || el.name; + if (type === "burg") name = "Burg of " + name; + document.getElementById("emblemArmiger").innerText = name; + + if (el.coa === "custom") emblemShapeSelector.disabled = true; + else { + emblemShapeSelector.disabled = false; + emblemShapeSelector.value = el.coa.shield; + } + } + + function selectState() { + const state = +this.value; + if (state) { + type = "state"; + el = pack.states[state]; + id = "stateCOA"+ state; + } else { + // select neutral burg if state is changed to Neutrals + const neutralBurgs = pack.burgs.filter(burg => burg.i && !burg.removed && !burg.state); + if (!neutralBurgs.length) return; + type = "burg"; + el = neutralBurgs[0]; + id = "burgCOA"+ neutralBurgs[0].i; + } + updateElementSelectors(type, id, el); + } + + function selectProvince() { + const province = +this.value; + + if (province) { + type = "province"; + el = pack.provinces[province]; + id = "provinceCOA"+ province; + } else { + // select state if province is changed to null value + const state = +emblemStates.value; + type = "state"; + el = pack.states[state]; + id = "stateCOA"+ state; + } + + updateElementSelectors(type, id, el); + } + + function selectBurg() { + const burg = +this.value; + type = "burg"; + el = pack.burgs[burg]; + id = "burgCOA"+ burg; + updateElementSelectors(type, id, el); + } + + function changeShape() { + el.coa.shield = this.value; + document.getElementById(id).remove(); + COArenderer.trigger(id, el.coa); + } + + function showArea() { + highlightEmblemElement(type, el); + } + + function regenerate() { + let parent = null; + if (type === "province") parent = pack.states[el.state]; + else if (type === "burg") { + const province = pack.cells.province[el.cell]; + parent = province ? pack.provinces[province] : pack.states[el.state]; + } + + const shield = el.coa.shield || COA.getShield(el.culture || parent?.culture || 0, el.state); + el.coa = COA.generate(parent ? parent.coa : null, .3, .1, null); + el.coa.shield = shield; + emblemShapeSelector.disabled = false; + emblemShapeSelector.value = el.coa.shield; + + const coaEl = document.getElementById(id); + if (coaEl) coaEl.remove(); + COArenderer.trigger(id, el.coa); + } + + function openInArmoria() { + const coa = el.coa && el.coa !== "custom" ? el.coa : {t1: "sable"}; + const json = JSON.stringify(coa).replaceAll("#", "%23"); + const url = `http://azgaar.github.io/Armoria/?coa=${json}`; + openURL(url); + } + + function toggleUpload() { + document.getElementById("emblemDownloadControl").classList.add("hidden"); + const buttons = document.getElementById("emblemUploadControl"); + buttons.classList.toggle("hidden"); + } + + function upload(type) { + const input = type === "image" ? document.getElementById("emblemImageToLoad") : document.getElementById("emblemSVGToLoad"); + const file = input.files[0]; + input.value = ""; + + if (file.size > 500000) { + tip(`File is too big, please optimize file size up to 500kB and re-upload. Recommended size is 200x200 px and up to 100kB`, true, "error", 5000); + return; + } + + const reader = new FileReader(); + + reader.onload = function(readerEvent) { + const result = readerEvent.target.result; + const defs = document.getElementById("defs-emblems"); + const coa = document.getElementById(id); // old emblem + + if (type === "image") { + const svg = ``; + defs.insertAdjacentHTML("beforeend", svg); + } else { + defs.insertAdjacentHTML("beforeend", result); + const newEmblem = defs.lastChild; // new coa + newEmblem.id = id; + newEmblem.setAttribute("width", 200); + newEmblem.setAttribute("height", 200); + } + + if (coa) coa.remove(); // remove old emblem + el.coa = "custom"; + emblemShapeSelector.disabled = true; + }; + + if (type === "image") reader.readAsDataURL(file); else reader.readAsText(file); + } + + function toggleDownload() { + document.getElementById("emblemUploadControl").classList.add("hidden"); + const buttons = document.getElementById("emblemDownloadControl"); + buttons.classList.toggle("hidden"); + } + + function download(format) { + const coa = document.getElementById(id); + const size = +emblemsDownloadSize.value; + const url = getURL(coa, el.coa, size); + const link = document.createElement("a"); + link.download = getFileName(`Emblem ${el.fullName || el.name}`) + "." + format; + + if (format === "svg") downloadSVG(url, link); else downloadRaster(format, url, link, size); + document.getElementById("emblemDownloadControl").classList.add("hidden"); + } + + function downloadSVG(url, link) { + link.href = url; + link.click(); + window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000); + } + + function downloadRaster(format, url, link, size) { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + canvas.width = size; + canvas.height = size; + + const img = new Image(); + img.src = url; + img.onload = function() { + if (format === "jpeg") { + ctx.fillStyle = "#fff"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + const URL = canvas.toDataURL("image/" + format, .92); + link.href = URL; + link.click(); + window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000); + } + } + + function getURL(svg, coa, size) { + const serialized = getSVG(svg, coa, size); + const blob = new Blob([serialized], { type: 'image/svg+xml;charset=utf-8' }); + const url = window.URL.createObjectURL(blob); + return url; + } + + function getSVG(svg, size) { + const clone = svg.cloneNode(true); // clone svg + clone.setAttribute("width", size); + clone.setAttribute("height", size); + return (new XMLSerializer()).serializeToString(clone); + } + + function downloadGallery() { + const name = getFileName("Emblems Gallery"); + const validStates = pack.states.filter(s => s.i && !s.removed && s.coa); + const validProvinces = pack.provinces.filter(p => p.i && !p.removed && p.coa); + const validBurgs = pack.burgs.filter(b => b.i && !b.removed && b.coa); + triggerCOALoad(validStates, validProvinces, validBurgs); + const timeout = (validStates.length + validProvinces.length + validBurgs.length) * 8; + tip("Preparing to download...", true, "warn", timeout); + d3.timeout(runDownload, timeout); + + function runDownload() { + const back = `Go Back`; + + const stateSection = `

States

` + validStates.map(state => { + const el = document.getElementById("stateCOA"+state.i); + const svg = getSVG(el, state.coa, 200); + return `
${state.fullName}
${svg}
`; + }).join("") + `
`; + + const provinceSections = validStates.map(state => { + const stateProvinces = validProvinces.filter(p => p.state === state.i); + const figures = stateProvinces.map(province => { + const el = document.getElementById("provinceCOA"+province.i); + const svg = getSVG(el, province.coa, 200); + return `
${province.fullName}
${svg}
`; + }).join(""); + return stateProvinces.length ? `
${back}

${state.fullName} provinces

${figures}
` : ""; + }).join(""); + + const burgSections = validStates.map(state => { + const stateBurgs = validBurgs.filter(b => b.state === state.i); + let stateBurgSections = validProvinces.filter(p => p.state === state.i).map(province => { + const provinceBurgs = stateBurgs.filter(b => pack.cells.province[b.cell] === province.i); + const provinceBurgFigures = provinceBurgs.map(burg => { + const el = document.getElementById("burgCOA"+burg.i); + const svg = getSVG(el, burg.coa, 200); + return `
${burg.name}
${svg}
`; + }).join(""); + return provinceBurgs.length ? `
${back}

${province.fullName} burgs

${provinceBurgFigures}
` : ""; + }).join(""); + + const stateBurgOutOfProvinces = stateBurgs.filter(b => !pack.cells.province[b.cell]); + const stateBurgOutOfProvincesFigures = stateBurgOutOfProvinces.map(burg => { + const el = document.getElementById("burgCOA"+burg.i); + const svg = getSVG(el, burg.coa, 200); + return `
${burg.name}
${svg}
`; + }).join(""); + if (stateBurgOutOfProvincesFigures) stateBurgSections += `

${state.fullName} burgs under direct control

${stateBurgOutOfProvincesFigures}
`; + return stateBurgSections; + }).join(""); + + const neutralBurgs = validBurgs.filter(b => !b.state); + const neutralsSection = neutralBurgs.length ? "

Independent burgs

" + neutralBurgs.map(burg => { + const el = document.getElementById("burgCOA"+burg.i); + const svg = getSVG(el, burg.coa, 200); + return `
${burg.name}
${svg}
`; + }).join("") + "
" : ""; + + const FMG = `Azgaar's Fantasy Map Generator`; + const license = `the license`; + const html = `${mapName.value} Emblems Gallery + + + +

${mapName.value} Emblems Gallery

+ ${stateSection} + ${provinceSections} + ${burgSections} + ${neutralsSection} +
Generated by ${FMG}. The tool is free, but images may be copyrighted, see ${license}
+ `; + downloadFile(html, name + ".html", "text/plain"); + } + } + + function triggerCOALoad(states, provinces, burgs) { + states.forEach(state => COArenderer.trigger("stateCOA"+state.i, state.coa)); + provinces.forEach(province => COArenderer.trigger("provinceCOA"+province.i, province.coa)); + burgs.forEach(burg => COArenderer.trigger("burgCOA"+burg.i, burg.coa)); + } + + function dragEmblem() { + const tr = parseTransform(this.getAttribute("transform")); + const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y; + + d3.event.on("drag", function() { + const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`; + this.setAttribute("transform", transform); + }); + } + + function closeEmblemEditor() { + emblems.selectAll("use").call(d3.drag().on("drag", null)).attr("class", null); + } +} \ No newline at end of file diff --git a/modules/ui/general.js b/modules/ui/general.js index 3546f018..90070057 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -27,7 +27,7 @@ function tip(tip = "Tip is undefined", main, type, time) { if (type === "success") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)"; if (main) tooltip.dataset.main = tip; // set main tip - if (time) setTimeout(tooltip.dataset.main = "", time); // clear main in some time + if (time) setTimeout(() => tooltip.dataset.main = "", time); // clear main in some time } function showMainTip() { @@ -49,7 +49,8 @@ function showDataTip(e) { tip(dataTip); } -function moved() { +const moved = debounce(mouseMove, 100); +function mouseMove() { const point = d3.mouse(this); const i = findCell(point[0], point[1]); // pack cell id if (i === undefined) return; @@ -61,6 +62,7 @@ function moved() { // show note box on hover (if any) function showNotes(e, i) { + if (notesEditor.offsetParent) return; let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id; if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id; else if (e.target.parentNode.parentNode.id === "burgIcons") id = "burg" + e.target.dataset.id; @@ -70,7 +72,7 @@ function showNotes(e, i) { document.getElementById("notes").style.display = "block"; document.getElementById("notesHeader").innerHTML = note.name; document.getElementById("notesBody").innerHTML = note.legend; - } else { + } else if (!options.pinNotes) { document.getElementById("notes").style.display = "none"; document.getElementById("notesHeader").innerHTML = ""; document.getElementById("notesBody").innerHTML = ""; @@ -88,11 +90,43 @@ function showMapTooltip(point, e, i, g) { const land = pack.cells.h[i] >= 20; // specific elements - if (group === "armies") {tip(e.target.parentNode.dataset.name + ". Click to edit"); return;} - if (group === "rivers") {tip(getRiverName(e.target.id) + "Click to edit"); return;} + if (group === "armies") { + tip(e.target.parentNode.dataset.name + ". Click to edit"); + return; + } + if (group === "emblems" && e.target.tagName === "use") { + const parent = e.target.parentNode; + const [g, type] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] : + parent.id === "provinceEmblems" ? [pack.provinces, "province"] : + [pack.states, "state"]; + const i = +e.target.dataset.i; + highlightEmblemElement(type, g[i]); + + d3.select(e.target).raise(); + d3.select(parent).raise(); + + const name = g[i].fullName || g[i].name; + tip(`${name} ${type} emblem. Click to edit`); + return; + } + if (group === "rivers") { + const river = +e.target.id.slice(5); + const r = pack.rivers.find(r => r.i === river); + const name = r ? r.name + " " + r.type : ""; + tip(name + ". Click to edit"); + if (riversOverview.offsetParent) highlightEditorLine(riversOverview, river, 5000); + return; + } if (group === "routes") {tip("Click to edit the Route"); return;} if (group === "terrain") {tip("Click to edit the Relief Icon"); return;} - if (subgroup === "burgLabels" || subgroup === "burgIcons") {tip("Click to open Burg Editor"); return;} + if (subgroup === "burgLabels" || subgroup === "burgIcons") { + const burg = +path[path.length - 10].dataset.id; + const b = pack.burgs[burg]; + const population = si(b.population * populationRate.value * urbanization.value); + tip(`${b.name}. Population: ${population}. Click to edit`); + if (burgsOverview.offsetParent) highlightEditorLine(burgsOverview, burg, 5000); + return; + } if (group === "labels") {tip("Click to edit the Label"); return;} if (group === "markers") {tip("Click to edit the Marker"); return;} if (group === "ruler") { @@ -105,32 +139,54 @@ function showMapTooltip(point, e, i, g) { if (subgroup === "burgLabels") {tip("Click to edit the Burg"); return;} if (group === "lakes" && !land) {tip(`${capitalize(subgroup)} lake. Click to edit`); return;} if (group === "coastline") {tip("Click to edit the coastline"); return;} - if (group === "zones") {tip(path[path.length-8].dataset.description); return;} + if (group === "zones") { + const zone = path[path.length-8]; + tip(zone.dataset.description); + if (zonesEditor.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000); + return; + } if (group === "ice") {tip("Click to edit the Ice"); return;} // covering elements if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: "+ getFriendlyPrecipitation(i)); else if (layerIsOn("togglePopulation")) tip(getPopulationTip(i)); else if (layerIsOn("toggleTemp")) tip("Temperature: " + convertTemperature(grid.cells.temp[g])); else - if (layerIsOn("toggleBiomes") && pack.cells.biome[i]) tip("Biome: " + biomesData.name[pack.cells.biome[i]]); else + if (layerIsOn("toggleBiomes") && pack.cells.biome[i]) { + const biome = pack.cells.biome[i] + tip("Biome: " + biomesData.name[biome]); + if (biomesEditor.offsetParent) highlightEditorLine(biomesEditor, biome); + } else if (layerIsOn("toggleReligions") && pack.cells.religion[i]) { - const religion = pack.religions[pack.cells.religion[i]]; - const type = religion.type === "Cult" || religion.type == "Heresy" ? religion.type : religion.type + " religion"; - tip(type + ": " + religion.name); + const religion = pack.cells.religion[i]; + const r = pack.religions[religion]; + const type = r.type === "Cult" || r.type == "Heresy" ? r.type : r.type + " religion"; + tip(type + ": " + r.name); + if (religionsEditor.offsetParent) highlightEditorLine(religionsEditor, religion); } else if (pack.cells.state[i] && (layerIsOn("toggleProvinces") || layerIsOn("toggleStates"))) { - const state = pack.states[pack.cells.state[i]].fullName; + const state = pack.cells.state[i]; + const stateName = pack.states[state].fullName; const province = pack.cells.province[i]; const prov = province ? pack.provinces[province].fullName + ", " : ""; - tip(prov + state); + tip(prov + stateName); + if (statesEditor.offsetParent) highlightEditorLine(statesEditor, state); + if (diplomacyEditor.offsetParent) highlightEditorLine(diplomacyEditor, state); + if (militaryOverview.offsetParent) highlightEditorLine(militaryOverview, state); + if (provincesEditor.offsetParent) highlightEditorLine(provincesEditor, province); + } else + if (layerIsOn("toggleCultures") && pack.cells.culture[i]) { + const culture = pack.cells.culture[i]; + tip("Culture: " + pack.cultures[culture].name); + if (culturesEditor.offsetParent) highlightEditorLine(culturesEditor, culture); } else - if (layerIsOn("toggleCultures") && pack.cells.culture[i]) tip("Culture: " + pack.cultures[pack.cells.culture[i]].name); else if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point)); } -function getRiverName(id) { - const r = pack.rivers.find(r => r.i == id.slice(5)); - return r ? r.name + " " + r.type + ". " : ""; +function highlightEditorLine(editor, id, timeout = 15000) { + Array.from(editor.getElementsByClassName("states hovered")).forEach(el => el.classList.remove("hovered")); // clear all hovered + const hovered = Array.from(editor.querySelectorAll("div")).find(el => el.dataset.id == id); + if (hovered) hovered.classList.add("hovered"); // add hovered class + if (timeout) setTimeout(() => {hovered && hovered.classList.remove("hovered")}, timeout); } // get cell info on mouse move @@ -240,6 +296,32 @@ function getPopulationTip(i) { return `Cell population: ${si(rural+urban)}; Rural: ${si(rural)}; Urban: ${si(urban)}`; } +function highlightEmblemElement(type, el) { + if (emblems.selectAll("line, circle").size()) return; + const i = el.i, cells = pack.cells; + const animation = d3.transition().duration(1000).ease(d3.easeSinIn); + + if (type === "burg") { + const {x, y} = el; + emblems.append("circle").attr("cx", x).attr("cy", y).attr("r", 0) + .attr("fill", "none").attr("stroke", "#d0240f").attr("stroke-width", 1).attr("opacity", 1) + .transition(animation).attr("r", 20).attr("opacity", .1).attr("stroke-width", 0).remove(); + return; + } + + const [x, y] = el.pole; + const obj = type === "state" ? cells.state : cells.province; + const borderCells = cells.i.filter(id => obj[id] === i && cells.c[id].some(n => obj[n] !== i)); + const data = Array.from(borderCells).filter((c, i) => !(i%2)).map(i => cells.p[i]).map(i => [i[0], i[1], Math.hypot(i[0]-x, i[1]-y)]); + + emblems.selectAll("line").data(data).enter().append("line") + .attr("x1", x).attr("y1", y).attr("x2", d => d[0]).attr("y2", d => d[1]) + .attr("stroke", "#d0240f").attr("stroke-width", .5).attr("opacity", .2) + .attr("stroke-dashoffset", d => d[2]).attr("stroke-dasharray", d => d[2]) + .transition(animation).attr("stroke-dashoffset", 0).attr("opacity", 1) + .transition(animation).delay(1000).attr("stroke-dashoffset", d => d[2]).attr("opacity", 0).remove(); +} + // assign lock behavior document.querySelectorAll("[data-locked]").forEach(function(e) { e.addEventListener("mouseover", function(event) { @@ -248,7 +330,7 @@ document.querySelectorAll("[data-locked]").forEach(function(e) { event.stopPropagation(); }); - e.addEventListener("click", function(event) { + e.addEventListener("click", function() { const id = (this.id).slice(5); if (this.className === "icon-lock") unlock(id); else lock(id); @@ -285,6 +367,22 @@ function stored(option) { return localStorage.getItem(option); } +// assign skeaker behaviour +Array.from(document.getElementsByClassName("speaker")).forEach(el => { + const input = el.previousElementSibling; + el.addEventListener("click", () => speak(input.value)); +}); + +function speak(text) { + const speaker = new SpeechSynthesisUtterance(text); + const voices = speechSynthesis.getVoices(); + if (voices.length) { + const voiceId = +document.getElementById("speakerVoice").value; + speaker.voice = voices[voiceId]; + } + speechSynthesis.speak(speaker); +} + // apply drop-down menu option. If the value is not in options, add it function applyOption(select, id, name = id) { const custom = !Array.from(select.options).some(o => o.value == id); @@ -298,6 +396,7 @@ function showInfo() { const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit") const Patreon = link("https://www.patreon.com/azgaar", "Patreon"); const Trello = link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Trello"); + const Armoria = link("https://azgaar.github.io/Armoria", "Armoria"); const QuickStart = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial", "Quick start tutorial"); const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page"); @@ -312,9 +411,11 @@ function showInfo() {

The best way to get help is to contact the community on ${Discord} and ${Reddit}. Before asking questions, please check out the ${QuickStart} and the ${QAA}.

-

You can track the development process on ${Trello}.

+

Track the development process on ${Trello}.

- Links: +

Check out our new project: ${Armoria}, heraldry generator and editor.

+ + Links:
  • ${link("https://github.com/Azgaar/Fantasy-Map-Generator", "GitHub repository")}
  • ${link("https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE", "License")}
  • @@ -331,6 +432,7 @@ function showInfo() { // prevent default browser behavior for FMG-used hotkeys document.addEventListener("keydown", event => { if (event.altKey && event.keyCode !== 18) event.preventDefault(); // disallow alt key combinations + if (event.ctrlKey && event.code === "KeyS") event.preventDefault(); // disallow CTRL + C if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab }); @@ -359,6 +461,7 @@ document.addEventListener("keyup", event => { else if (key === 79 && canvas3d) toggle3dOptions(); // "O" to toggle 3d options else if (ctrl && key === 81) toggleSaveReminder(); // Ctrl + "Q" to toggle save reminder + else if (ctrl && key === 83) saveMap(); // Ctrl + "S" to save .map file else if (undo.offsetParent && ctrl && key === 90) undo.click(); // Ctrl + "Z" to undo else if (redo.offsetParent && ctrl && key === 89) redo.click(); // Ctrl + "Y" to redo @@ -410,6 +513,7 @@ document.addEventListener("keyup", event => { else if (key === 78) togglePopulation(); // "N" to toggle Population layer else if (key === 74) toggleIce(); // "J" to toggle Ice layer else if (key === 65) togglePrec(); // "A" to toggle Precipitation layer + else if (key === 89) toggleEmblems(); // "Y" to toggle Emblems layer else if (key === 76) toggleLabels(); // "L" to toggle Labels layer else if (key === 73) toggleIcons(); // "I" to toggle Icons layer else if (key === 77) toggleMilitary(); // "M" to toggle Military layer diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index f574f84e..73a8006e 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -164,8 +164,8 @@ function editHeightmap() { } function regenerateErasedData() { - console.group("Edit Heightmap"); - console.time("regenerateErasedData"); + INFO && console.group("Edit Heightmap"); + TIME && console.time("regenerateErasedData"); const change = changeHeights.checked; markFeatures(); @@ -204,8 +204,8 @@ function editHeightmap() { Military.generate(); addMarkers(); addZones(); - console.timeEnd("regenerateErasedData"); - console.groupEnd("Edit Heightmap"); + TIME && console.timeEnd("regenerateErasedData"); + INFO && console.groupEnd("Edit Heightmap"); } function restoreKeptData() { @@ -216,8 +216,8 @@ function editHeightmap() { } function restoreRiskedData() { - console.group("Edit Heightmap"); - console.time("restoreRiskedData"); + INFO && console.group("Edit Heightmap"); + TIME && console.time("restoreRiskedData"); // assign pack data to grid cells const l = grid.cells.i.length; @@ -401,8 +401,8 @@ function editHeightmap() { .attr("points", d => getPackPolygon(d)).attr("id", d => base + d); }); - console.timeEnd("restoreRiskedData"); - console.groupEnd("Edit Heightmap"); + TIME && console.timeEnd("restoreRiskedData"); + INFO && console.groupEnd("Edit Heightmap"); } // trigger heightmap redraw and history update if at least 1 cell is changed @@ -954,7 +954,7 @@ function editHeightmap() { templateBody.innerHTML = ""; for (const s of steps) { const step = s.split(" "); - if (step.length !== 5) {console.error("Cannot parse step, wrong arguments count", s); continue;} + if (step.length !== 5) {ERROR && console.error("Cannot parse step, wrong arguments count", s); continue;} addStep(step[0], step[1], step[2], step[3], step[4]); } @@ -1311,7 +1311,6 @@ function editHeightmap() { const link = document.createElement("a"); link.download = getFileName("Heightmap") + ".png"; link.href = imgBig; - document.body.appendChild(link); link.click(); canvas.remove(); } diff --git a/modules/ui/layers.js b/modules/ui/layers.js index b834c9d2..8cf4a2c4 100644 --- a/modules/ui/layers.js +++ b/modules/ui/layers.js @@ -1,29 +1,6 @@ // UI module stub to control map layers "use strict"; -// on map regeneration restore layers if they was turned on -function restoreLayers() { - if (layerIsOn("toggleHeight")) drawHeightmap(); - if (layerIsOn("toggleCells")) drawCells(); - if (layerIsOn("toggleGrid")) drawGrid(); - if (layerIsOn("toggleCoordinates")) drawCoordinates(); - if (layerIsOn("toggleCompass")) compass.style("display", "block"); - if (layerIsOn("toggleTemp")) drawTemp(); - if (layerIsOn("togglePrec")) drawPrec(); - if (layerIsOn("togglePopulation")) drawPopulation(); - if (layerIsOn("toggleBiomes")) drawBiomes(); - if (layerIsOn("toggleRelief")) ReliefIcons(); - if (layerIsOn("toggleCultures")) drawCultures(); - if (layerIsOn("toggleProvinces")) drawProvinces(); - if (layerIsOn("toggleReligions")) drawReligions(); - if (layerIsOn("toggleIce")) drawIce(); - - // states are getting rendered each time, if it's not required than layers should be hidden - if (!layerIsOn("toggleBorders")) $('#borders').fadeOut(); - if (!layerIsOn("toggleStates")) regions.style("display", "none").selectAll("path").remove(); -} - -restoreLayers(); // run on-load let presets = {}; // global object restoreCustomPresets(); // run on-load @@ -38,6 +15,7 @@ function getDefaultPresets() { "physical": ["toggleCoordinates", "toggleHeight", "toggleIce", "toggleRivers", "toggleScaleBar"], "poi": ["toggleBorders", "toggleHeight", "toggleIce", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"], "military": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleMilitary", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"], + "emblems": ["toggleBorders", "toggleIcons", "toggleIce", "toggleEmblems", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"], "landmass": ["toggleScaleBar"] } } @@ -55,9 +33,10 @@ function restoreCustomPresets() { presets = storedPresets; } +// run on map generation function applyPreset() { - const selected = localStorage.getItem("preset"); - if (selected) changePreset(selected); + const preset = localStorage.getItem("preset") || document.getElementById("layersPreset").value; + changePreset(preset); } // toggle layers on preset change @@ -117,6 +96,29 @@ function getCurrentPreset() { savePresetButton.style.display = "inline-block"; } +// run on map regeneration +function restoreLayers() { + if (layerIsOn("toggleHeight")) drawHeightmap(); + if (layerIsOn("toggleCells")) drawCells(); + if (layerIsOn("toggleGrid")) drawGrid(); + if (layerIsOn("toggleCoordinates")) drawCoordinates(); + if (layerIsOn("toggleCompass")) compass.style("display", "block"); + if (layerIsOn("toggleTemp")) drawTemp(); + if (layerIsOn("togglePrec")) drawPrec(); + if (layerIsOn("togglePopulation")) drawPopulation(); + if (layerIsOn("toggleBiomes")) drawBiomes(); + if (layerIsOn("toggleRelief")) ReliefIcons(); + if (layerIsOn("toggleCultures")) drawCultures(); + if (layerIsOn("toggleProvinces")) drawProvinces(); + if (layerIsOn("toggleReligions")) drawReligions(); + if (layerIsOn("toggleIce")) drawIce(); + if (layerIsOn("toggleEmblems")) drawEmblems(); + + // states are getting rendered each time, if it's not required than layers should be hidden + if (!layerIsOn("toggleBorders")) $('#borders').fadeOut(); + if (!layerIsOn("toggleStates")) regions.style("display", "none").selectAll("path").remove(); +} + function toggleHeight(event) { if (!terrs.selectAll("*").size()) { turnButtonOn("toggleHeight"); @@ -131,7 +133,7 @@ function toggleHeight(event) { } function drawHeightmap() { - console.time("drawHeightmap"); + TIME && console.time("drawHeightmap"); terrs.selectAll("*").remove(); const cells = pack.cells, vertices = pack.vertices, n = cells.i.length; const used = new Uint8Array(cells.i.length); @@ -165,7 +167,7 @@ function drawHeightmap() { paths[h] += round(lineGen(points)); } - terrs.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%").attr("fill", scheme(.8)); // draw base layer + terrs.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight).attr("fill", scheme(.8)); // draw base layer for (const i of d3.range(20, 101)) { if (paths[i].length < 10) continue; const color = getColor(i, scheme); @@ -188,7 +190,7 @@ function drawHeightmap() { 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;} } return chain; } @@ -199,7 +201,7 @@ function drawHeightmap() { return chain.filter((d, i) => i % n === 0); } - console.timeEnd("drawHeightmap"); + TIME && console.timeEnd("drawHeightmap"); } function getColorScheme() { @@ -228,7 +230,7 @@ function toggleTemp(event) { } function drawTemp() { - console.time("drawTemp"); + TIME && console.time("drawTemp"); temperature.selectAll("*").remove(); lineGen.curve(d3.curveBasisClosed); const scheme = d3.scaleSequential(d3.interpolateSpectral); @@ -257,8 +259,8 @@ function drawTemp() { addLabel(points, t); } - // min temp isoline covers all map - temperature.append("path").attr("d", `M0,0 h${svgWidth} v${svgHeight} h${-svgWidth} Z`).attr("fill", scheme(1 - (min - tMin) / delta)).attr("stroke", "none"); + // min temp isoline covers all graph + temperature.append("path").attr("d", `M0,0 h${graphWidth} v${graphHeight} h${-graphWidth} Z`).attr("fill", scheme(1 - (min - tMin) / delta)).attr("stroke", "none"); for (const t of isolines) { const path = chains.filter(c => c[0] === t).map(c => round(lineGen(c[1]))).join(""); @@ -311,12 +313,12 @@ function drawTemp() { 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(start); return chain; } - console.timeEnd("drawTemp"); + TIME && console.timeEnd("drawTemp"); } function toggleBiomes(event) { @@ -370,7 +372,7 @@ function drawBiomes() { 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;} } return chain; } @@ -457,7 +459,7 @@ function drawCells() { cells.append("path").attr("d", path); } -function toggleIce() { +function toggleIce(event) { if (!layerIsOn("toggleIce")) { turnButtonOn("toggleIce"); $('#ice').fadeIn(); @@ -473,7 +475,7 @@ function toggleIce() { function drawIce() { const cells = grid.cells, vertices = grid.vertices, n = cells.i.length, temp = cells.temp, h = cells.h; const used = new Uint8Array(cells.i.length); - Math.seedrandom(seed); + Math.random = aleaPRNG(seed); const shieldMin = -6; // max temp to form ice shield (glacier) const icebergMax = 2; // max temp to form an iceberg @@ -526,7 +528,7 @@ function drawIce() { 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;} } return chain; } @@ -547,7 +549,7 @@ function toggleCultures(event) { } function drawCultures() { - console.time("drawCultures"); + TIME && console.time("drawCultures"); cults.selectAll("path").remove(); const cells = pack.cells, vertices = pack.vertices, cultures = pack.cultures, n = cells.i.length; @@ -586,11 +588,11 @@ function drawCultures() { 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;} } return chain; } - console.timeEnd("drawCultures"); + TIME && console.timeEnd("drawCultures"); } function toggleReligions(event) { @@ -607,7 +609,7 @@ function toggleReligions(event) { } function drawReligions() { - console.time("drawReligions"); + TIME && console.time("drawReligions"); relig.selectAll("path").remove(); const cells = pack.cells, vertices = pack.vertices, religions = pack.religions, features = pack.features, n = cells.i.length; @@ -657,12 +659,12 @@ function drawReligions() { if (v[0] !== prev && c0 !== c1) {current = v[0]; check(c0 ? c[0] : c[1]);} else if (v[1] !== prev && c1 !== c2) {current = v[1]; check(c1 ? c[1] : c[2]);} else if (v[2] !== prev && c0 !== c2) {current = v[2]; check(c2 ? c[2] : c[0]);} - if (current === chain[chain.length - 1][0]) {console.error("Next vertex is not found"); break;} + if (current === chain[chain.length - 1][0]) {ERROR && console.error("Next vertex is not found"); break;} } return chain; } - console.timeEnd("drawReligions"); + TIME && console.timeEnd("drawReligions"); } function toggleStates(event) { @@ -680,7 +682,7 @@ function toggleStates(event) { // draw states function drawStates() { - console.time("drawStates"); + TIME && console.time("drawStates"); regions.selectAll("path").remove(); const cells = pack.cells, vertices = pack.vertices, states = pack.states, n = cells.i.length; @@ -713,15 +715,16 @@ function drawStates() { }); const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, states[i].color]).filter(d => d[0]); - statesBody.selectAll("path").data(bodyData).enter().append("path").attr("d", d => d[0]).attr("fill", d => d[2]).attr("stroke", "none").attr("id", d => "state"+d[1]); const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, states[i].color]).filter(d => d[0]); - statesBody.selectAll(".path").data(gapData).enter().append("path").attr("d", d => d[0]).attr("fill", "none").attr("stroke", d => d[2]).attr("id", d => "state-gap"+d[1]); - defs.select("#statePaths").selectAll("clipPath").remove(); - defs.select("#statePaths").selectAll("clipPath").data(bodyData).enter().append("clipPath").attr("id", d => "state-clip"+d[1]).append("use").attr("href", d => "#state"+d[1]); - statesHalo.selectAll(".path").data(bodyData).enter().append("path") - .attr("d", d => d[0]).attr("stroke", d => d3.color(d[2]) ? d3.color(d[2]).darker().hex() : "#666666") - .attr("id", d => "state-border"+d[1]).attr("clip-path", d => "url(#state-clip"+d[1]+")"); + const bodyString = bodyData.map(d => ``).join(""); + const gapString = gapData.map(d => ``).join(""); + const clipString = bodyData.map(d => ``).join(""); + const haloString = bodyData.map(d => ``).join(""); + + statesBody.html(bodyString + gapString); + defs.select("#statePaths").html(clipString); + statesHalo.html(haloString); // connect vertices to chain function connectVertices(start, t, state) { @@ -741,18 +744,18 @@ function drawStates() { if (v[0] !== prev && c0 !== c1) {current = v[0]; check(c0 ? c[0] : c[1]);} else if (v[1] !== prev && c1 !== c2) {current = v[1]; check(c1 ? c[1] : c[2]);} else if (v[2] !== prev && c0 !== c2) {current = v[2]; check(c2 ? c[2] : c[0]);} - if (current === chain[chain.length - 1][0]) {console.error("Next vertex is not found"); break;} + if (current === chain[chain.length - 1][0]) {ERROR && console.error("Next vertex is not found"); break;} } chain.push([start, state, land]); // add starting vertex to sequence to close the path return chain; } invokeActiveZooming(); - console.timeEnd("drawStates"); + TIME && console.timeEnd("drawStates"); } // draw state and province borders function drawBorders() { - console.time("drawBorders"); + TIME && console.time("drawBorders"); borders.selectAll("path").remove(); const cells = pack.cells, vertices = pack.vertices, n = cells.i.length; @@ -807,7 +810,7 @@ function drawBorders() { // find starting vertex for (let i=0; i < 1000; i++) { - if (i === 999) console.error("Find starting vertex: limit is reached", current, f, t); + if (i === 999) ERROR && console.error("Find starting vertex: limit is reached", current, f, t); const p = chain[chain.length-2] || -1; // previous vertex const v = vertices.v[current], c = vertices.c[current]; @@ -825,7 +828,7 @@ function drawBorders() { chain = [current]; // vertices chain to form a path // find path for (let i=0; i < 1000; i++) { - if (i === 999) console.error("Find path: limit is reached", current, f, t); + if (i === 999) ERROR && console.error("Find path: limit is reached", current, f, t); const p = chain[chain.length-2] || -1; // previous vertex const v = vertices.v[current], c = vertices.c[current]; c.filter(c => array[c] === t).forEach(c => used[f][c] = t); @@ -845,7 +848,7 @@ function drawBorders() { return chain; } - console.timeEnd("drawBorders"); + TIME && console.timeEnd("drawBorders"); } function toggleBorders(event) { @@ -873,10 +876,30 @@ function toggleProvinces(event) { } function drawProvinces() { - console.time("drawProvinces"); + TIME && console.time("drawProvinces"); const labelsOn = provs.attr("data-labels") == 1; provs.selectAll("*").remove(); + const provinces = pack.provinces; + const {body, gap} = getProvincesVertices(); + + const g = provs.append("g").attr("id", "provincesBody"); + const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]); + g.selectAll("path").data(bodyData).enter().append("path").attr("d", d => d[0]).attr("fill", d => d[2]).attr("stroke", "none").attr("id", d => "province"+d[1]); + const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]); + g.selectAll(".path").data(gapData).enter().append("path").attr("d", d => d[0]).attr("fill", "none").attr("stroke", d => d[2]).attr("id", d => "province-gap"+d[1]); + + const labels = provs.append("g").attr("id", "provinceLabels"); + labels.style("display", `${labelsOn ? "block" : "none"}`); + const labelData = provinces.filter(p => p.i && !p.removed); + labels.selectAll(".path").data(labelData).enter().append("text") + .attr("x", d => d.pole[0]).attr("y", d => d.pole[1]) + .attr("id", d => "provinceLabel"+d.i).text(d => d.name); + + TIME && console.timeEnd("drawProvinces"); +} + +function getProvincesVertices() { const cells = pack.cells, vertices = pack.vertices, provinces = pack.provinces, n = cells.i.length; const used = new Uint8Array(cells.i.length); const vArray = new Array(provinces.length); // store vertices array @@ -906,18 +929,7 @@ function drawProvinces() { provinces[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility }); - const g = provs.append("g").attr("id", "provincesBody"); - const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]); - g.selectAll("path").data(bodyData).enter().append("path").attr("d", d => d[0]).attr("fill", d => d[2]).attr("stroke", "none").attr("id", d => "province"+d[1]); - const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, provinces[i].color]).filter(d => d[0]); - g.selectAll(".path").data(gapData).enter().append("path").attr("d", d => d[0]).attr("fill", "none").attr("stroke", d => d[2]).attr("id", d => "province-gap"+d[1]); - - const labels = provs.append("g").attr("id", "provinceLabels"); - labels.style("display", `${labelsOn ? "block" : "none"}`); - const labelData = provinces.filter(p => p.i && !p.removed); - labels.selectAll(".path").data(labelData).enter().append("text") - .attr("x", d => d.pole[0]).attr("y", d => d.pole[1]) - .attr("id", d => "provinceLabel"+d.i).text(d => d.name); + return {body, gap}; // connect vertices to chain function connectVertices(start, t, province) { @@ -937,12 +949,12 @@ function drawProvinces() { if (v[0] !== prev && c0 !== c1) {current = v[0]; check(c0 ? c[0] : c[1]);} else if (v[1] !== prev && c1 !== c2) {current = v[1]; check(c1 ? c[1] : c[2]);} else if (v[2] !== prev && c0 !== c2) {current = v[2]; check(c2 ? c[2] : c[0]);} - if (current === chain[chain.length-1][0]) {console.error("Next vertex is not found"); break;} + if (current === chain[chain.length-1][0]) {ERROR && console.error("Next vertex is not found"); break;} } chain.push([start, province, land]); // add starting vertex to sequence to close the path return chain; } - console.timeEnd("drawProvinces"); + } function toggleGrid(event) { @@ -959,10 +971,10 @@ function toggleGrid(event) { } function drawGrid() { - console.time("drawGrid"); + TIME && console.time("drawGrid"); gridOverlay.selectAll("*").remove(); const type = styleGridType.value; - const size = Math.max(+styleGridSize.value, 2); + const size = Math.max(+styleGridSize.value || +gridOverlay.attr("size"), 2); if (type === "pointyHex" || type === "flatHex") { const points = getHexGridPoints(size, type); const hex = "m" + getHex(size, type).slice(0, 4).join("l"); @@ -1003,7 +1015,7 @@ function drawGrid() { }); } - console.timeEnd("drawGrid"); + TIME && console.timeEnd("drawGrid"); } function toggleCoordinates(event) { @@ -1064,9 +1076,6 @@ function toggleCompass(event) { $('#compass').fadeIn(); if (!compass.selectAll("*").size()) { compass.append("use").attr("xlink:href","#rose"); - // prolongate rose lines - svg.select("g#rose > g#sL > line#sL1").attr("y1", -19000).attr("y2", 19000); - svg.select("g#rose > g#sL > line#sL2").attr("x1", -19000).attr("x2", 19000); shiftCompass(); } if (event && isCtrlClick(event)) editStyle("compass"); @@ -1217,7 +1226,98 @@ function toggleZones(event) { if (event && isCtrlClick(event)) {editStyle("zones"); return;} turnButtonOff("toggleZones"); $('#zones').fadeOut(); - } + } +} + +function toggleEmblems(event) { + if (!layerIsOn("toggleEmblems")) { + turnButtonOn("toggleEmblems"); + if (!emblems.selectAll("use").size()) drawEmblems(); + $('#emblems').fadeIn(); + if (event && isCtrlClick(event)) editStyle("emblems"); + } else { + if (event && isCtrlClick(event)) {editStyle("emblems"); return;} + $('#emblems').fadeOut(); + turnButtonOff("toggleEmblems"); + } +} + +function drawEmblems() { + TIME && console.time("drawEmblems"); + const {states, provinces, burgs} = pack; + + const validStates = states.filter(s => s.i && !s.removed && s.coa); + const validProvinces = provinces.filter(p => p.i && !p.removed && p.coa); + const validBurgs = burgs.filter(b => b.i && !b.removed && b.coa); + + const getStateEmblemsSize = () => { + const startSize = Math.min(Math.max((graphHeight + graphWidth) / 40, 10), 100); + const statesMod = (1 + validStates.length / 100) - (15 - validStates.length) / 200; // states number modifier + const sizeMod = +document.getElementById("styleEmblemsStateSizeInput").value || 1; + return rn(startSize / statesMod * sizeMod); // target size ~50px on 1536x754 map with 15 states + }; + + const getProvinceEmblemsSize = () => { + const startSize = Math.min(Math.max((graphHeight + graphWidth) / 80, 5), 75); + const provincesMod = (1 + validProvinces.length / 1000) - (115 - validProvinces.length) / 1000; // states number modifier + const sizeMod = +document.getElementById("styleEmblemsProvinceSizeInput").value || 1; + return rn(startSize / provincesMod * sizeMod); // target size ~26px on 1536x754 map with 115 provinces + } + + const getBurgEmblemSize = () => { + const startSize = Math.min(Math.max((graphHeight + graphWidth) / 150, 5), 50); + const burgsMod = (1 + validBurgs.length / 1000) - (450 - validBurgs.length) / 1000; // states number modifier + const sizeMod = +document.getElementById("styleEmblemsBurgSizeInput").value || 1; + return rn(startSize / burgsMod * sizeMod); // target size ~10px on 1536x754 map with 450 burgs + } + + const sizeBurgs = getBurgEmblemSize(); + const burgCOAs = validBurgs.map(burg => { + const {x, y} = burg; + return {type: "burg", i: burg.i, x, y, size: sizeBurgs}; + }); + + const sizeProvinces = getProvinceEmblemsSize(); + const provinceCOAs = validProvinces.map(province => { + if (!province.pole) getProvincesVertices(); + const [x, y] = province.pole; + return {type: "province", i: province.i, x, y, size: sizeProvinces}; + }); + + const sizeStates = getStateEmblemsSize(); + const stateCOAs = validStates.map(state => { + const [x, y] = state.pole; + return {type: "state", i: state.i, x, y, size: sizeStates}; + }); + + const nodes = burgCOAs.concat(provinceCOAs).concat(stateCOAs); + const simulation = d3.forceSimulation(nodes) + .alphaMin(.6).alphaDecay(.2).velocityDecay(.6) + .force('collision', d3.forceCollide().radius(d => d.size/2)) + .stop(); + + d3.timeout(function() { + const n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); + for (let i = 0; i < n; ++i) { + simulation.tick(); + } + + const burgNodes = nodes.filter(node => node.type === "burg"); + const burgString = burgNodes.map(d => ``).join(""); + emblems.select("#burgEmblems").attr("font-size", sizeBurgs).html(burgString); + + const provinceNodes = nodes.filter(node => node.type === "province"); + const provinceString = provinceNodes.map(d => ``).join(""); + emblems.select("#provinceEmblems").attr("font-size", sizeProvinces).html(provinceString); + + const stateNodes = nodes.filter(node => node.type === "state"); + const stateString = stateNodes.map(d => ``).join(""); + emblems.select("#stateEmblems").attr("font-size", sizeStates).html(stateString); + + invokeActiveZooming(); + }); + + TIME && console.timeEnd("drawEmblems"); } function layerIsOn(el) { @@ -1265,6 +1365,7 @@ function getLayer(id) { if (id === "togglePopulation") return $("#population"); if (id === "toggleIce") return $("#ice"); if (id === "toggleTexture") return $("#texture"); + if (id === "toggleEmblems") return $("#emblems"); if (id === "toggleLabels") return $("#labels"); if (id === "toggleIcons") return $("#icons"); if (id === "toggleMarkers") return $("#markers"); diff --git a/modules/ui/markers-editor.js b/modules/ui/markers-editor.js index a4686d89..c1ea19f6 100644 --- a/modules/ui/markers-editor.js +++ b/modules/ui/markers-editor.js @@ -203,7 +203,16 @@ function editMarker() { function changeMarkerSize() { const id = elSelected.attr("data-id"); - document.querySelectorAll("use[data-id='"+id+"']").forEach(e => e.dataset.size = markerSize.value); + document.querySelectorAll("use[data-id='"+id+"']").forEach(e => { + const x = +e.dataset.x, y = +e.dataset.y; + const desired = e.dataset.size = +markerSize.value; + const size = Math.max(desired * 5 + 25 / scale, 1); + + e.setAttribute("x", x - size / 2); + e.setAttribute("y", y - size / 2); + e.setAttribute("width", size); + e.setAttribute("height", size); + }); invokeActiveZooming(); } diff --git a/modules/ui/namesbase-editor.js b/modules/ui/namesbase-editor.js index baf0acb5..03149a13 100644 --- a/modules/ui/namesbase-editor.js +++ b/modules/ui/namesbase-editor.js @@ -22,6 +22,7 @@ function editNamesbase() { document.getElementById("namesbaseDownload").addEventListener("click", namesbaseDownload); document.getElementById("namesbaseUpload").addEventListener("click", () => namesbaseToLoad.click()); document.getElementById("namesbaseToLoad").addEventListener("change", function() {uploadFile(this, namesbaseUpload)}); + document.getElementById("namesbaseSpeak").addEventListener("click", () => speak(namesbaseExamples.textContent)); createBasesList(); updateInputs(); diff --git a/modules/ui/notes-editor.js b/modules/ui/notes-editor.js index cc49d536..04facf08 100644 --- a/modules/ui/notes-editor.js +++ b/modules/ui/notes-editor.js @@ -5,6 +5,18 @@ function editNotes(id, name) { select.options.length = 0; for (const note of notes) {select.options.add(new Option(note.id, note.id));} + // initiate pell (html editor) + const editor = Pell.init({ + element: document.getElementById("notesText"), + onChange: html => { + const id = document.getElementById("notesSelect").value; + const note = notes.find(note => note.id === id); + if (!note) return; + note.legend = html; + showNote(note); + } + }); + // select an object if (notes.length || id) { if (!id) id = notes[0].id; @@ -17,17 +29,18 @@ function editNotes(id, name) { } select.value = id; notesName.value = note.name; - notesText.value = note.legend; + editor.content.innerHTML = note.legend; + showNote(note); } else { - const value = "There are no added notes. Click on element (e.g. label) and add a free text note"; - document.getElementById("notesText").value = value; + editor.content.innerHTML = "There are no added notes. Click on element (e.g. label) and add a free text note"; document.getElementById("notesName").value = ""; } // open a dialog $("#notesEditor").dialog({ - title: "Notes Editor", minWidth: "40em", - position: {my: "center", at: "center", of: "svg"} + title: "Notes Editor", minWidth: "40em", width: "50vw", + position: {my: "center", at: "center", of: "svg"}, + close: () => notesText.innerHTML = "" }); if (modules.editNotes) return; @@ -36,18 +49,25 @@ function editNotes(id, name) { // add listeners document.getElementById("notesSelect").addEventListener("change", changeObject); document.getElementById("notesName").addEventListener("input", changeName); - document.getElementById("notesText").addEventListener("input", changeText); + document.getElementById("notesPin").addEventListener("click", () => options.pinNotes = !options.pinNotes); + document.getElementById("notesSpeak").addEventListener("click", () => speak(editor.content.innerHTML)); document.getElementById("notesFocus").addEventListener("click", validateHighlightElement); document.getElementById("notesDownload").addEventListener("click", downloadLegends); document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click()); document.getElementById("legendsToLoad").addEventListener("change", function() {uploadFile(this, uploadLegends)}); document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove); + function showNote(note) { + document.getElementById("notes").style.display = "block"; + document.getElementById("notesHeader").innerHTML = note.name; + document.getElementById("notesBody").innerHTML = note.legend; + } + function changeObject() { const note = notes.find(note => note.id === this.value); if (!note) return; notesName.value = note.name; - notesText.value = note.legend; + editor.content.innerHTML = note.legend; } function changeName() { @@ -55,13 +75,7 @@ function editNotes(id, name) { const note = notes.find(note => note.id === id); if (!note) return; note.name = this.value; - } - - function changeText() { - const id = document.getElementById("notesSelect").value; - const note = notes.find(note => note.id === id); - if (!note) return; - note.legend = this.value; + showNote(note); } function validateHighlightElement() { @@ -112,6 +126,7 @@ function editNotes(id, name) { notes.splice(index, 1); select.options.length = 0; if (!notes.length) {$("#notesEditor").dialog("close"); return;} + notesText.innerHTML = ""; editNotes(notes[0].id, notes[0].name); } diff --git a/modules/ui/options.js b/modules/ui/options.js index 4d789173..50473d62 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -71,9 +71,29 @@ document.getElementById("options").querySelector("div.tab").addEventListener("cl // show popup with a list of Patreon supportes (updated manually, to be replaced with API call) function showSupporters() { - const supporters = "Aaron Meyer, Ahmad Amerih, AstralJacks, aymeric, Billy Dean Goehring, Branndon Edwards, Chase Mayers, Curt Flood, cyninge, Dino Princip, E.M. White, es, Fondue, Fritjof Olsson, Gatsu, Johan Fröberg, Jonathan Moore, Joseph Miranda, Kate, KC138, Luke Nelson, Markus Finster, Massimo Vella, Mikey, Nathan Mitchell, Paavi1, Pat, Ryan Westcott, Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun, Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan), Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI, Hilton Williams, Katharina Haase, Hisham Bedri, Ian arless, Karnat, Bird, Kevin, Jessica Thomas, Steve Hyatt, Logicspren, Alfred García, Jonathan Killstring, John Ackley, Invad3r233, Norbert Žigmund, Jennifer, PoliticsBuff, _gfx_, Maggie, Connor McMartin, Jared McDaris, BlastWind, Franc Casanova Ferrer, Dead & Devil, Michael Carmody, Valerie Elise, naikibens220, Jordon Phillips, William Pucs, The Dungeon Masters, Brady R Rathbun, J, Shadow, Matthew Tiffany, Huw Williams, Joseph Hamilton, FlippantFeline, Tamashi Toh, kms, Stephen Herron, MidnightMoon, Whakomatic x, Barished, Aaron bateson, Brice Moss, Diklyquill, PatronUser, Michael Greiner, Steven Bennett, Jacob Harrington, Miguel C., Reya C., Giant Monster Games, Noirbard, Brian Drennen, Ben Craigie, Alex Smolin, Endwords, Joshua E Goodwin, SirTobit , Allen S. Rout, Allen Bull Bear, Pippa Mitchell, R K, G0atfather, Ryan Lege, Caner Oleas Pekgönenç, Bradley Edwards, Tertiary , Austin Miller, Jesse Holmes, Jan Dvořák, Marten F, Erin D. Smale, Maxwell Hill, Drunken_Legends, rob bee, Jesse Holmes, YYako, Detocroix"; - alertMessage.innerHTML = "
      " + supporters.split(", ").sort().map(n => `
    • ${n}
    • `).join("") + "
    "; - $("#alert").dialog({resizable: false, title: "Patreon Supporters", width: "30vw", position: {my: "center", at: "center", of: "svg"}}); + const supporters = `Aaron Meyer,Ahmad Amerih,AstralJacks,aymeric,Billy Dean Goehring,Branndon Edwards,Chase Mayers,Curt Flood,cyninge,Dino Princip, + E.M. White,es,Fondue,Fritjof Olsson,Gatsu,Johan Fröberg,Jonathan Moore,Joseph Miranda,Kate,KC138,Luke Nelson,Markus Finster,Massimo Vella,Mikey, + Nathan Mitchell,Paavi1,Pat,Ryan Westcott,Sasquatch,Shawn Spencer,Sizz_TV,Timothée CALLET,UTG community,Vlad Tomash,Wil Sisney,William Merriott, + Xariun,Gun Metal Games,Scott Marner,Spencer Sherman,Valerii Matskevych,Alloyed Clavicle,Stewart Walsh,Ruthlyn Mollett (Javan),Benjamin Mair-Pratt, + Diagonath,Alexander Thomas,Ashley Wilson-Savoury,William Henry,Preston Brooks,JOSHUA QUALTIERI,Hilton Williams,Katharina Haase,Hisham Bedri,Ian arless, + Karnat,Bird,Kevin,Jessica Thomas,Steve Hyatt,Logicspren,Alfred García,Jonathan Killstring,John Ackley,Invad3r233,Norbert Žigmund,Jennifer, + PoliticsBuff,_gfx_,Maggie,Connor McMartin,Jared McDaris,BlastWind,Franc Casanova Ferrer,Dead & Devil,Michael Carmody,Valerie Elise,naikibens220, + Jordon Phillips,William Pucs,The Dungeon Masters,Brady R Rathbun,J,Shadow,Matthew Tiffany,Huw Williams,Joseph Hamilton,FlippantFeline,Tamashi Toh, + kms,Stephen Herron,MidnightMoon,Whakomatic x,Barished,Aaron bateson,Brice Moss,Diklyquill,PatronUser,Michael Greiner,Steven Bennett,Jacob Harrington, + Miguel C.,Reya C.,Giant Monster Games,Noirbard,Brian Drennen,Ben Craigie,Alex Smolin,Endwords,Joshua E Goodwin,SirTobit ,Allen S. Rout,Allen Bull Bear, + Pippa Mitchell,R K,G0atfather,Ryan Lege,Caner Oleas Pekgönenç,Bradley Edwards,Tertiary ,Austin Miller,Jesse Holmes,Jan Dvořák,Marten F,Erin D. Smale, + Maxwell Hill,Drunken_Legends,rob bee,Jesse Holmes,YYako,Detocroix,Anoplexian,Hannah,Paul,Sandra Krohn,Lucid,Richard Keating,Allen Varney,Rick Falkvinge, + Seth Fusion,Adam Butler,Gus,StroboWolf,Sadie Blackthorne,Zewen Senpai,Dell McKnight,Oneiris,Darinius Dragonclaw Studios,Christopher Whitney,Rhodes HvZ, + Jeppe Skov Jensen,María Martín López,Martin Seeger,Annie Rishor,Aram Sabatés,MadNomadMedia,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta, + Thirty-OneR ,ThatGuyGW ,Dee Chiu,MontyBoosh ,Achillain ,Jaden ,SashaTK,Steve Johnson,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,Thirty-OneR, + ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,Andrew Rostaing,Daniel Gill, + Char,Jack,Barna Csíkos,Ian Rousseau,Nicholas Grabstas,Tom Van Orden jr,Bryan Brake,Akylos,Riley Seaman,MaxOliver,Evan-DiLeo,Alex Debus,Joshua Vaught, + Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,Radovan Zapletal,Jmmat6, + Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,Guilherme Aguiar,Jarno Hallikainen`; + + const array = supporters.replace(/(?:\r\n|\r|\n)/g, "").split(",").map(v => capitalize(v.trim())).sort(); + alertMessage.innerHTML = "
      " + array.map(n => `
    • ${n}
    • `).join("") + "
    "; + $("#alert").dialog({resizable: false,title: "Patreon Supporters",width: "54vw",position: {my: "center",at: "center",of: "svg"}}); } // Option listeners @@ -95,12 +115,11 @@ optionsContent.addEventListener("input", function(event) { else if (id === "neutralOutput") neutralInput.value = value; else if (id === "manorsInput") changeBurgsNumberSlider(value); else if (id === "religionsInput") religionsOutput.value = value; + else if (id === "emblemShape") changeEmblemShape(value); else if (id === "uiSizeInput") uiSizeOutput.value = value; else if (id === "uiSizeOutput") changeUIsize(value); else if (id === "tooltipSizeInput" || id === "tooltipSizeOutput") changeTooltipSize(value); else if (id === "transparencyInput") changeDialogsTransparency(value); - else if (id === "pngResolutionInput") pngResolutionOutput.value = value; - else if (id === "pngResolutionOutput") pngResolutionInput.value = value; }); optionsContent.addEventListener("change", function(event) { @@ -109,6 +128,8 @@ optionsContent.addEventListener("change", function(event) { if (id === "zoomExtentMin" || id === "zoomExtentMax") changeZoomExtent(value); else if (id === "optionsSeed") generateMapWithSeed(); else if (id === "uiSizeInput") changeUIsize(value); + else if (id === "yearInput") changeYear(); + else if (id === "eraInput") changeEra(); }); optionsContent.addEventListener("click", function(event) { @@ -120,6 +141,7 @@ optionsContent.addEventListener("click", function(event) { else if (id === "optionsEraRegenerate") regenerateEra(); else if (id === "zoomExtentDefault") restoreDefaultZoomExtent(); else if (id === "translateExtent") toggleTranslateExtent(event.target); + else if (id === "speakerTest") testSpeaker(); }); function mapSizeInputChange() { @@ -140,6 +162,9 @@ function changeMapSize() { landmass.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); + fogging.selectAll("rect").attr("x", 0).attr("y", 0).attr("width", maxWidth).attr("height", maxHeight); + defs.select("mask#fog > rect").attr("width", maxWidth).attr("height", maxHeight); + texture.select("image").attr("width", maxWidth).attr("height", maxHeight); fitScaleBar(); if (window.fitLegendBox) fitLegendBox(); @@ -175,6 +200,30 @@ function toggleTranslateExtent(el) { else zoom.translateExtent([[0, 0], [graphWidth, graphHeight]]); } +// add voice options +const voiceInterval = setInterval(function() { + const voices = speechSynthesis.getVoices(); + if (voices.length) clearInterval(voiceInterval); else return; + + const select = document.getElementById("speakerVoice"); + voices.forEach((voice, i) => { + select.options.add(new Option(voice.name, i, false)); + }); + if (stored("speakerVoice")) select.value = localStorage.getItem("speakerVoice"); // se voice to store + else select.value = voices.findIndex(voice => voice.lang === "en-US"); // or to first found English-US +}, 1000); + +function testSpeaker() { + const text = `${mapName.value}, ${options.year} ${options.era}`; + const speaker = new SpeechSynthesisUtterance(text); + const voices = speechSynthesis.getVoices(); + if (voices.length) { + const voiceId = +document.getElementById("speakerVoice").value; + speaker.voice = voices[voiceId]; + } + speechSynthesis.speak(speaker); +} + function generateMapWithSeed() { if (optionsSeed.value == seed) { tip("The current map already has this seed", false, "error"); @@ -196,7 +245,7 @@ function showSeedHistoryDialog() { }); } -// generate map with historycal seed +// generate map with historical seed function restoreSeed(id) { if (mapHistory[id].seed == seed) { tip("The current map is already generated with this seed", null, "error"); @@ -240,6 +289,17 @@ function changeCultureSet() { if (+culturesOutput.value > +max) culturesInput.value = culturesOutput.value = max; } +function changeEmblemShape(value) { + const image = document.getElementById("emblemShapeImage"); + const shapeEl = document.getElementById(value); + if (shapeEl) { + const shape = shapeEl.querySelector("path").getAttribute("d"); + image.setAttribute("d", shape); + } else { + image.removeAttribute("d"); + } +} + function changeStatesNumber(value) { regionsInput.value = regionsOutput.value = value; regionsOutput.style.color = +value ? null : "#b12117"; @@ -297,6 +357,7 @@ function applyStoredOptions() { for (let i=0; i < localStorage.length; i++) { const stored = localStorage.key(i), value = localStorage.getItem(stored); + if (stored === "speakerVoice") continue; const input = document.getElementById(stored+"Input") || document.getElementById(stored); const output = document.getElementById(stored+"Output"); if (input) input.value = value; @@ -328,7 +389,7 @@ function applyStoredOptions() { // randomize options if randomization is allowed (not locked or options='default') function randomizeOptions() { - Math.seedrandom(seed); // reset seed to initial one + Math.random = aleaPRNG(seed); // reset seed to initial one const randomize = new URL(window.location.href).searchParams.get("options") === "default"; // ignore stored options // 'Options' settings @@ -358,6 +419,7 @@ function randomizeOptions() { // World settings generateEra(); + changeEmblemShape(emblemShape.value); // change emblem shape image } // select heightmap template pseudo-randomly @@ -381,14 +443,14 @@ function randomizeHeightmapTemplate() { // select culture set pseudo-randomly function randomizeCultureSet() { const sets = { - "world": 25, - "european": 20, - "oriental": 10, - "english": 10, - "antique": 5, - "highFantasy": 22, - "darkFantasy": 6, - "random": 2}; + "world": 10, + "european": 10, + "oriental": 2, + "english": 5, + "antique": 3, + "highFantasy": 11, + "darkFantasy": 3, + "random": 1}; culturesSet.value = rw(sets); changeCultureSet(); } @@ -397,7 +459,7 @@ function randomizeCultureSet() { function generateEra() { if (!stored("year")) yearInput.value = rand(100, 2000); // current year if (!stored("era")) eraInput.value = Names.getBaseShort(P(.7) ? 1 : rand(nameBases.length)) + " Era"; - options.year = yearInput.value; + options.year = +yearInput.value; options.era = eraInput.value; options.eraShort = options.era.split(" ").map(w => w[0].toUpperCase()).join(""); // short name for era } @@ -408,6 +470,18 @@ function regenerateEra() { options.eraShort = options.era.split(" ").map(w => w[0].toUpperCase()).join(""); } +function changeYear() { + if (!yearInput.value) return; + if (isNaN(+yearInput.value)) {tip("Current year should be a number", false, "error"); return;} + options.year = +yearInput.value; +} + +function changeEra() { + if (!eraInput.value) return; + lock("era"); + options.era = eraInput.value; +} + // remove all saved data from LocalStorage and reload the page function restoreDefaultOptions() { localStorage.clear(); @@ -453,7 +527,7 @@ function saveGeoJSON() { $("#alert").dialog({title: "GIS data export", resizable: false, width: "35em", position: {my: "center", at: "center", of: "svg"}, buttons: { Cells: saveGeoJSON_Cells, - Routes: saveGeoJSON_Roads, + Routes: saveGeoJSON_Routes, Rivers: saveGeoJSON_Rivers, Markers: saveGeoJSON_Markers, Close: function() {$(this).dialog("close");} @@ -497,7 +571,7 @@ document.getElementById("mapToLoad").addEventListener("change", function() { // View mode viewMode.addEventListener("click", changeViewMode); -function changeViewMode() { +function changeViewMode(event) { const button = event.target; if (button.tagName !== "BUTTON") return; const pressed = button.classList.contains("pressed"); diff --git a/modules/ui/provinces-editor.js b/modules/ui/provinces-editor.js index 3d64fafc..adda7d3e 100644 --- a/modules/ui/provinces-editor.js +++ b/modules/ui/provinces-editor.js @@ -38,7 +38,7 @@ function editProvinces() { const el = ev.target, cl = el.classList, line = el.parentNode, p = +line.dataset.id; if (cl.contains("fillRect")) changeFill(el); else if (cl.contains("name")) editProvinceName(p); else - if (cl.contains("icon-coa")) provinceOpenCOA(ev, p); else + if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA"+p, pack.provinces[p]); else if (cl.contains("icon-star-empty")) capitalZoomIn(p); else if (cl.contains("icon-flag-empty")) triggerIndependencePromps(p); else if (cl.contains("culturePopulation")) changePopulation(p); else @@ -114,10 +114,11 @@ function editProvinces() { const capital = p.burg ? pack.burgs[p.burg].name : ''; const separable = p.burg && p.burg !== pack.states[p.state].capital; const focused = defs.select("#fog #focusProvince"+p.i).size(); + COArenderer.trigger("provinceCOA"+p.i, p.coa); lines += `
    - + @@ -192,19 +193,6 @@ function editProvinces() { openPicker(currentFill, callback); } - function provinceOpenCOA(event, p) { - const defSeed = `${seed}-p${p}`; - const openIAHG = () => openURL("https://ironarachne.com/heraldry/" + (pack.provinces[p].IAHG || defSeed)); - - if (isCtrlClick(event)) { - prompt(`Please provide an Iron Arachne Heraldry Generator seed.
    Default seed is a combination of FMG map seed and province id (${defSeed})`, - {default:pack.provinces[p].IAHG || defSeed}, v => { - if (v && v != defSeed) pack.provinces[p].IAHG = v; - openIAHG(); - }); - } else openIAHG(); - } - function capitalZoomIn(p) { const capital = pack.provinces[p].burg; const l = burgLabels.select("[data-id='" + capital + "']"); @@ -247,10 +235,15 @@ function editProvinces() { const name = provinces[p].name; const color = getRandomColor(); + const coa = provinces[p].coa; + const coaEl = document.getElementById("provinceCOA"+p); + if (coaEl) coaEl.id = "stateCOA"+newState; + emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove(); + // update cells cells.i.filter(i => cells.province[i] === p).forEach(i => { - cells.province[i] = 0; - cells.state[i] = newState; + cells.province[i] = 0; + cells.state[i] = newState; }); // update diplomacy and reverse relations @@ -272,7 +265,7 @@ function editProvinces() { states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldState].name}`]); // create new state - states.push({i:newState, name, diplomacy, provinces:[], color, expansionism:.5, capital:burg, type:"Generic", center, culture, military:[], alert:1}); + states.push({i:newState, name, diplomacy, provinces:[], color, expansionism:.5, capital:burg, type:"Generic", center, culture, military:[], alert:1, coa}); BurgsAndStates.collectStatistics(); BurgsAndStates.defineStateForms([newState]); @@ -284,7 +277,10 @@ function editProvinces() { // remove old province unfog("focusProvince"+p); if (states[oldState].provinces.includes(p)) states[oldState].provinces.splice(states[oldState].provinces.indexOf(p), 1); - provinces[p].removed = true; + provinces[p] = {i:p, removed: true}; + + // draw emblem + COArenderer.add("state", newState, coa, pack.states[newState].pole[0], pack.states[newState].pole[1]); closeDialogs(); editStates(); @@ -344,7 +340,6 @@ function editProvinces() { refreshProvincesEditor(); } - } function toggleFog(p, cl) { @@ -354,17 +349,24 @@ function editProvinces() { } function removeProvince(p) { - alertMessage.innerHTML = `Are you sure you want to remove the province?
    This action cannot be reverted`; $("#alert").dialog({resizable: false, title: "Remove province", buttons: { Remove: function() { - pack.cells.province.forEach((province, i) => {if(province === p) pack.cells.province[i] = 0;}); - const state = pack.provinces[p].state; - if (pack.states[state].provinces.includes(p)) pack.states[state].provinces.splice(pack.states[state].provinces.indexOf(p), 1); - pack.provinces[p].removed = true; + pack.cells.province.forEach((province, i) => { + if(province === p) pack.cells.province[i] = 0; + }); + const s = province.state, state = pack.states[s]; + if (state.provinces.includes(p)) state.provinces.splice(state.provinces.indexOf(p), 1); + unfog("focusProvince"+p); - + + const coaId = "provinceCOA" + p; + if (document.getElementById(coaId)) document.getElementById(coaId).remove(); + emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove(); + + pack.provinces[p] = {i: p, removed: true}; + const g = provs.select("#provincesBody"); g.select("#province"+p).remove(); g.select("#province-gap"+p).remove(); @@ -375,8 +377,6 @@ function editProvinces() { Cancel: function() {$(this).dialog("close");} } }); - - } function editProvinceName(province) { @@ -387,7 +387,7 @@ function editProvinces() { document.getElementById("provinceNameEditorFull").value = p.fullName; $("#provinceNameEditor").dialog({ - resizable: false, title: "Change province name", width: "22em", buttons: { + resizable: false, title: "Change province name", buttons: { Apply: function() {applyNameChange(p); $(this).dialog("close");}, Cancel: function() {$(this).dialog("close");} }, position: {my: "center", at: "center", of: "svg"} @@ -771,7 +771,16 @@ function editProvinces() { const fullName = name + " " + formName; const stateColor = pack.states[state].color, rndColor = getRandomColor(); const color = stateColor[0] === "#" ? d3.color(d3.interpolate(stateColor, rndColor)(.2)).hex() : rndColor; - provinces.push({i:province, state, center, burg, name, formName, fullName, color}); + + // generate emblem + const kinship = burg ? .8 : .4; + const parent = burg ? pack.burgs[burg].coa : pack.states[state].coa; + const type = BurgsAndStates.getType(center, parent.port); + const coa = COA.generate(parent, kinship, P(.1), type); + coa.shield = COA.getShield(c, state); + COArenderer.add("province", province, coa, point[0], point[1]); + + provinces.push({i:province, state, center, burg, name, formName, fullName, color, coa}); cells.province[center] = province; cells.c[center].forEach(c => { @@ -837,13 +846,17 @@ function editProvinces() { buttons: { Remove: function() { $(this).dialog("close"); - pack.provinces.filter(p => p.i).forEach(p => { - p.removed = true; - unfog("focusProvince"+p.i); - }); - pack.cells.i.forEach(i => pack.cells.province[i] = 0); - pack.states.filter(s => s.i && !s.removed).forEach(s => s.provinces = []); + // remove emblems + document.querySelectorAll("[id^='provinceCOA']").forEach(el => el.remove()); + emblems.select("#provinceEmblems").selectAll("*").remove(); + + // remove data + pack.provinces = [0]; + pack.cells.province = new Uint16Array(pack.cells.i.length); + pack.states.forEach(s => s.provinces = []); + + unfog(); if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); provs.select("#provincesBody").remove(); turnButtonOff("toggleProvinces"); diff --git a/modules/ui/relief-editor.js b/modules/ui/relief-editor.js index a3653aa2..4ed05a4e 100644 --- a/modules/ui/relief-editor.js +++ b/modules/ui/relief-editor.js @@ -54,7 +54,7 @@ function editReliefIcon() { } function updateReliefIconSelected() { - const type = elSelected.attr("data-type"); + const type = elSelected.attr("href"); const button = reliefIconsDiv.querySelector("svg[data-type='"+type+"']"); reliefIconsDiv.querySelectorAll("svg.pressed").forEach(b => b.classList.remove("pressed")); @@ -65,7 +65,7 @@ function editReliefIcon() { } function updateReliefSizeInput() { - const size = +elSelected.attr("data-size"); + const size = +elSelected.attr("width"); reliefSize.value = reliefSizeNumber.value = rn(size); } @@ -146,14 +146,15 @@ function editReliefIcon() { const x = rn(cx-h, 2); const y = rn(cy-h, 2); const z = y + h * 2; + const s = rn(h*2, 2); let nth = 1; while (positions[nth] && z > positions[nth]) {nth++;} tree.add([cx, cy]); positions.push(z); - terrain.insert("use", ":nth-child("+nth+")").attr("xlink:href", type).attr("data-type", type) - .attr("x", x).attr("y", y).attr("data-size", h*2).attr("width", h*2).attr("height", h*2); + terrain.insert("use", ":nth-child("+nth+")").attr("href", type) + .attr("x", x).attr("y", y).attr("width", s).attr("height", s); }); }); @@ -178,7 +179,7 @@ function editReliefIcon() { const r = +reliefRadiusNumber.value; const type = pressed.dataset.type; - const icons = type ? terrain.selectAll("use[data-type='"+type+"']") : terrain.selectAll("use"); + const icons = type ? terrain.selectAll("use[href='"+type+"']") : terrain.selectAll("use"); const tree = d3.quadtree(); icons.each(function() { const x = +this.getAttribute("x") + this.getAttribute("width") / 2; @@ -198,7 +199,7 @@ function editReliefIcon() { if (!reliefIndividual.classList.contains("pressed")) return; const shift = (size - +elSelected.attr("width")) / 2; - elSelected.attr("width", size).attr("height", size).attr("data-size", size); + elSelected.attr("width", size).attr("height", size); const x = +elSelected.attr("x"), y = +elSelected.attr("y"); elSelected.attr("x", x-shift).attr("y", y-shift); } @@ -217,7 +218,7 @@ function editReliefIcon() { if (reliefIndividual.classList.contains("pressed")) { const type = this.dataset.type; - elSelected.attr("xlink:href", type).attr("data-type", type); + elSelected.attr("href", type); } } diff --git a/modules/ui/states-editor.js b/modules/ui/states-editor.js index 5740b1ee..bcaea20e 100644 --- a/modules/ui/states-editor.js +++ b/modules/ui/states-editor.js @@ -42,7 +42,7 @@ function editStates() { const el = ev.target, cl = el.classList, line = el.parentNode, state = +line.dataset.id; if (cl.contains("fillRect")) stateChangeFill(el); else if (cl.contains("name")) editStateName(state); else - if (cl.contains("icon-coa")) stateOpenCOA(ev, state); else + if (cl.contains("coaIcon")) editEmblem("state", "stateCOA"+state, pack.states[state]); else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state); else if (cl.contains("culturePopulation")) changePopulation(state); else if (cl.contains("icon-pin")) toggleFog(state, cl); else @@ -90,7 +90,7 @@ function editStates() { data-population=${population} data-burgs=${s.burgs} data-color="" data-form="" data-capital="" data-culture="" data-type="" data-expansionism=""> - + @@ -109,12 +109,14 @@ function editStates() {
    `; continue; } + const capital = pack.burgs[s.capital].name; + COArenderer.trigger("stateCOA"+s.i, s.coa); lines += `
    - + @@ -230,7 +232,7 @@ function editStates() { document.getElementById("stateNameEditorFull").value = s.fullName || ""; $("#stateNameEditor").dialog({ - resizable: false, title: "Change state name", width: "22em", buttons: { + resizable: false, title: "Change state name", buttons: { Apply: function() {applyNameChange(s); $(this).dialog("close");}, Cancel: function() {$(this).dialog("close");} }, position: {my: "center", at: "center", of: "svg"} @@ -293,7 +295,8 @@ function editStates() { const changed = nameChanged || formChanged || fullNameChanged; if (formChanged) { - const form = formSelect.selectedOptions[0].dataset.form || null; + const selected = formSelect.selectedOptions[0]; + const form = selected.parentElement.label || null; if (form) s.form = form; } @@ -313,19 +316,6 @@ function editStates() { document.querySelector("#burgLabel"+capital).textContent = value; } - function stateOpenCOA(event, state) { - const defSeed = `${seed}-s${state}`; - const openIAHG = () => openURL("https://ironarachne.com/heraldry/" + (pack.states[state].IAHG || defSeed)); - - if (isCtrlClick(event)) { - prompt(`Please provide an Iron Arachne Heraldry Generator seed.
    Default seed is a combination of FMG map seed and state id (${defSeed})`, - {default:pack.states[state].IAHG || defSeed}, v => { - if (v && v != defSeed) pack.states[state].IAHG = v; - openIAHG(); - }); - } else openIAHG(); - } - function changePopulation(state) { const s = pack.states[state]; if (!s.cells) {tip("State does not have any cells, cannot change population", false, "error"); return;} @@ -433,17 +423,28 @@ function editStates() { statesBody.select("#state"+state).remove(); statesBody.select("#state-gap"+state).remove(); statesHalo.select("#state-border"+state).remove(); + labels.select("#stateLabel"+state).remove(); + defs.select("#textPath_stateLabel"+state).remove(); + unfog("focusState"+state); - const label = document.querySelector("#stateLabel"+state); - if (label) label.remove(); pack.burgs.forEach(b => {if(b.state === state) b.state = 0;}); pack.cells.state.forEach((s, i) => {if(s === state) pack.cells.state[i] = 0;}); - pack.states[state].removed = true; + + // remove emblem + const coaId = "stateCOA" + state; + document.getElementById(coaId).remove(); + emblems.select(`#stateEmblems > use[data-i='${state}']`).remove(); // remove provinces pack.states[state].provinces.forEach(p => { - pack.provinces[p].removed = true; + pack.provinces[p] = {i: p, removed: true}; pack.cells.province.forEach((pr, i) => {if(pr === p) pack.cells.province[i] = 0;}); + const coaId = "provinceCOA" + p; + if (document.getElementById(coaId)) document.getElementById(coaId).remove(); + emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove(); + const g = provs.select("#provincesBody"); + g.select("#province"+p).remove(); + g.select("#province-gap"+p).remove(); }); // remove military @@ -454,22 +455,12 @@ function editStates() { }); armies.select("g#army"+state).remove(); - const military = pack.states[elSelected.dataset.state].military; - const regIndex = military.indexOf(regiment()); - if (regIndex === -1) return; - military.splice(regIndex, 1); - - const index = notes.findIndex(n => n.id === elSelected.id); - if (index != -1) notes.splice(index, 1); - elSelected.remove(); - const capital = pack.states[state].capital; pack.burgs[capital].capital = 0; pack.burgs[capital].state = 0; moveBurgToGroup(capital, "towns"); - // clean state object - pack.states[state].military = []; + pack.states[state] = {i: state, removed: true}; debug.selectAll(".highlight").remove(); if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); @@ -852,6 +843,11 @@ function editStates() { const name = Names.getState(basename, culture); const color = getRandomColor(); + // generate emblem + const cultureType = pack.cultures[culture].type; + const coa = COA.generate(burgs[burg].coa, .4, null, cultureType); + coa.shield = COA.getShield(culture, null); + // update diplomacy and reverse relations const diplomacy = states.map(s => { if (!s.i) return "x"; @@ -879,7 +875,9 @@ function editStates() { const affectedProvinces = [cells.province[center]]; cells.state[center] = newState; cells.province[center] = 0; - cells.c[center].forEach(c => { + + const cellsToCheck = [...new Set(cells.c[center].map(c => cells.c[c].map(c => cells.c[c])).flat(2))]; + cellsToCheck.forEach(c => { if (cells.h[c] < 20) return; if (cells.burg[c]) return; affectedStates.push(cells.state[c]); @@ -887,7 +885,8 @@ function editStates() { cells.state[c] = newState; cells.province[c] = 0; }); - states.push({i:newState, name, diplomacy, provinces:[], color, expansionism:.5, capital:burg, type:"Generic", center, culture, military:[], alert:1}); + + states.push({i:newState, name, diplomacy, provinces:[], color, expansionism:.5, capital:burg, type:"Generic", center, culture, military:[], alert:1, coa}); BurgsAndStates.collectStatistics(); BurgsAndStates.defineStateForms([newState]); adjustProvinces([...new Set(affectedProvinces)]); @@ -896,6 +895,7 @@ function editStates() { if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); BurgsAndStates.drawStateLabels([...new Set(affectedStates)]); + COArenderer.add("state", newState, coa, states[newState].pole[0], states[newState].pole[1]); statesEditorAddLines(); } diff --git a/modules/ui/style.js b/modules/ui/style.js index f715a884..97b766e9 100644 --- a/modules/ui/style.js +++ b/modules/ui/style.js @@ -1,6 +1,11 @@ // UI module to control the style "use strict"; +// store some style inputs as options +styleElements.addEventListener("change", function(ev) { + if (ev.target.dataset.stored) lock(ev.target.dataset.stored); +}); + // select element to be edited function editStyle(element, group) { showOptions(); @@ -226,6 +231,12 @@ function selectStyleElement() { styleArmiesSize.value = styleArmiesSizeOutput.value = el.attr("box-size"); } + if (sel === "emblems") { + styleEmblems.style.display = "block"; + styleStrokeWidth.style.display = "block"; + styleStrokeWidthInput.value = styleStrokeWidthOutput.value = el.attr("stroke-width") || 1; + } + // update group options styleGroupSelect.options.length = 0; // remove all options if (sel === "routes" || sel === "labels" || sel === "coastline" || sel === "lakes" || sel === "anchors" || sel === "burgIcons" || sel === "borders") { @@ -614,6 +625,21 @@ styleArmiesSize.addEventListener("input", function() { }); }); +styleEmblemsStateSizeInput.addEventListener("input", function() { + styleEmblemsStateSizeOutput.value = this.value; + drawEmblems(); +}); + +styleEmblemsProvinceSizeInput.addEventListener("input", function() { + styleEmblemsProvinceSizeOutput.value = this.value; + drawEmblems(); +}); + +styleEmblemsBurgSizeInput.addEventListener("input", function() { + styleEmblemsBurgSizeOutput.value = this.value; + drawEmblems(); +}); + // request a URL to image to be used as a texture function textureProvideURL() { alertMessage.innerHTML = `Provide an image URL to be used as a texture: @@ -653,7 +679,7 @@ function setBase64Texture(url) { }; function fetchTextureURL(url) { - console.log("Provided URL is", url); + INFO && console.log("Provided URL is", url); const img = new Image(); img.onload = function () { const canvas = document.getElementById("texturePreview"); @@ -664,17 +690,17 @@ function fetchTextureURL(url) { img.src = url; } -function armiesStyle() { - return `#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}`; +const defaultStyles = { + styleAncient: `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#outline)"},"#emblems":{"opacity":0.6,"stroke-width":0.8,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#pattern1)"},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`, + styleGloom: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.5,"filter":""},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#emblems": {"opacity":0.6,"stroke-width":0.5,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#pattern3)"},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`, + styleClean: `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow01)"},"#emblems":{"opacity":1,"stroke-width":1,"filter":null},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#emptyImage)"},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging":{"opacity":1,"fill":"#ffffff","filter":null}}`, + styleMonochrome: `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.9,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#emblems": {"opacity": 0.5,"stroke-width": 0.5,"filter": null},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":0},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#emptyImage)"},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}` } // apply default or custom style settings on load function applyStyleOnLoad() { - addDefaulsStyles(); // add FMG system styles to localStorage - svg.select("defs").append("style").text(armiesStyle()); // add armies style - const preset = localStorage.getItem("presetStyle"); - const style = preset ? localStorage.getItem(preset) : null; + const style = preset && (defaultStyles[preset] || localStorage.getItem(preset)); if (preset && style && JSON.isValid(style)) { applyStyle(JSON.parse(style)); @@ -683,45 +709,24 @@ function applyStyleOnLoad() { stylePreset.value = preset; stylePreset.dataset.old = preset; } else { + if (preset && preset !== "styleDefault" && ERROR) console.error(`Style preset ${preset} is not available in localStorage, applying default style`); stylePreset.value = "styleDefault"; stylePreset.dataset.old = preset; applyDefaultStyle(); } } -function addDefaulsStyles() { - if (!localStorage.getItem("styleClean")) { - const clean = `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":0.5,"filter":"url(#blur7)","mask":"url(#land)"},"#stateBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":0.8,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.09,"filter":null,"mask":"url(#land)"},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#414141","stroke-width":0.45,"stroke-dasharray":3,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eeedeb","filter":null},"#markers":{"opacity":null,"rescale":null,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0,"fill":"#0080ff","filter":null},"#population":{"opacity":null,"stroke-width":2.58,"stroke-dasharray":0,"stroke-linecap":"butt","filter":"url(#blur3)"},"#rural":{"stroke":"#ff0000"},"#urban":{"stroke":"#800000"},"#freshwater":{"opacity":0.5,"fill":"#aadaff","stroke":"#5f799d","stroke-width":0,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#595959","stroke-width":0.4,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#aadaff"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#f6d068","stroke-width":0.7,"stroke-dasharray":0,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":1,"stroke":"#ffffff","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#4f82c6","stroke-width":0.45,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":"url(#water)"},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":0,"data-width":null,"stroke-width":0},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow01)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.7,"stroke":"#ff6262","stroke-width":0,"stroke-dasharray":"","stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":null},"#oceanLayers":{"filter":"","layers":"none"},"#oceanBase":{"fill":"#aadaff"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#emptyImage)"},"#terrs":{"opacity":0.5,"scheme":"bright","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"","mask":"url(#land)"},"#legend":{"data-size":12.74,"font-size":12.74,"data-font":"Arial","font-family":"Arial","stroke":"#909090","stroke-width":1.13,"stroke-dasharray":0,"stroke-linecap":"round","data-x":98.39,"data-y":12.67,"data-columns":null},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#414141","data-size":7,"font-size":7,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#303030","stroke-width":1.7},"#burgLabels > #towns":{"opacity":1,"fill":"#414141","data-size":3,"font-size":3,"data-font":"Arial","font-family":"Arial"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.06},"#labels > #states":{"opacity":1,"fill":"#292929","stroke":"#303030","stroke-width":0,"data-size":10,"font-size":10,"data-font":"Arial","font-family":"Arial","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#414141","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Arial","font-family":"Arial","filter":null},"#fogging":{"opacity":1,"fill":"#ffffff","filter":null}}`; - localStorage.setItem("styleClean", clean); - } - - if (!localStorage.getItem("styleGloom")) { - const gloom = `{"#map":{"background-color":"#000000","filter":null,"data-filter":null},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":"url(#land)"},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":""},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.3,"stroke-dasharray":".7 1","stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":14,"font-size":14,"stroke":"#4a4a4a","stroke-width":1,"stroke-dasharray":6,"stroke-linecap":null,"filter":"","mask":""},"#compass":{"opacity":0.6,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(100 100) scale(0.3)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":1,"filter":null},"#cults":{"opacity":0.7,"stroke":"#777777","stroke-width":1.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#e0e0e0","filter":null},"#markers":{"opacity":0.8,"rescale":1,"filter":"url(#dropShadow05)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000aa"},"#urban":{"stroke":"#9d0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.6,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":0.9,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":"","fill":"#779582"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":1,"stroke":"#8b4418","stroke-width":0.9,"stroke-dasharray":"2 3","stroke-linecap":"round","filter":"","mask":null},"#trails":{"opacity":1,"stroke":"#844017","stroke-width":0.2,"stroke-dasharray":".5 1","stroke-linecap":"round","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#5e1865","stroke-width":0.6,"stroke-dasharray":"1.2 2.4","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":"url(#dropShadow)"},"#statesHalo":{"opacity":1,"data-width":10.2,"stroke-width":10.2},"#provs":{"opacity":0.5,"filter":""},"#temperature":{"opacity":1,"font-size":"11px","fill":"#62001b","fill-opacity":0.3,"stroke":null,"stroke-width":2,"stroke-dasharray":2,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{"x":0,"y":0},"#zones":{"opacity":0.5,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":"url(#dropShadow01)","mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":null,"layers":"-6,-4,-2"},"#oceanBase":{"fill":"#4e6964"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#pattern3)"},"#terrs":{"opacity":1,"scheme":"bright","terracing":0,"skip":0,"relax":1,"curve":1,"filter":"url(#filter-grayscale)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":2,"stroke":"#444444","stroke-width":0.25,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":0.8,"fill":"#ffffff","size":4,"stroke":"#3e3e4b","stroke-width":1},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":3,"font-size":3,"data-font":"Bitter","font-family":"Bitter"},"#burgIcons > #towns":{"opacity":0.95,"fill":"#ffffff","fill-opacity":0.7,"size":0.8,"stroke":"#3e3e4b","stroke-width":0.2,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1.6,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#4e4e4e","stroke":"#b5b5b5","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":""},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#1b1423","filter":null}}`; - localStorage.setItem("styleGloom", gloom); - } - - if (!localStorage.getItem("styleAncient")) { - const ancient = `{"#map":{"background-color":"#000000","filter":"url(#filter-sepia)","data-filter":"sepia"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.05,"opacity":0.8,"fill-opacity":0.8,"filter":null},"#biomes":{"opacity":null,"filter":null,"mask":null},"#stateBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":0.8,"stroke":"#56566d","stroke-width":0.2,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":"translate(80 80) scale(.25)"},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#eee9d7","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":0.5,"fill":"#a6c1fd","stroke":"#5f799d","stroke-width":0.7,"filter":null},"#salt":{"opacity":0.5,"fill":"#409b8a","stroke":"#388985","stroke-width":0.7,"filter":null},"#sinkhole":{"opacity":1,"fill":"#5bc9fd","stroke":"#53a3b0","stroke-width":0.7,"filter":null},"#frozen":{"opacity":0.95,"fill":"#cdd4e7","stroke":"#cfe0eb","stroke-width":0,"filter":null},"#lava":{"opacity":0.7,"fill":"#90270d","stroke":"#f93e0c","stroke-width":2,"filter":"url(#crumpled)"},"#dry":{"opacity":0.7,"fill":"#c9bfa7","stroke":"#8e816f","stroke-width":0.7,"filter":null},"#sea_island":{"opacity":0.5,"stroke":"#1f3846","stroke-width":0.7,"filter":"url(#dropShadow)","auto-filter":1},"#lake_island":{"opacity":1,"stroke":"#7c8eaf","stroke-width":0.35,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":null,"filter":null,"fill":"#5d97bb"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.8,"stroke":"#2e1607","stroke-width":1.23,"stroke-dasharray":3,"stroke-linecap":"inherit","filter":null,"mask":null},"#trails":{"opacity":0.8,"stroke":"#331809","stroke-width":0.5,"stroke-dasharray":"1 2","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.8,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":""},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.7,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#outline)"},"#texture":{"opacity":null,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":1},"#oceanLayers":{"filter":"url(#blur5)","layers":"-6,-4,-2"},"#oceanBase":{"fill":"#a7a01f"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#pattern1)"},"#terrs":{"opacity":null,"scheme":"light","terracing":0,"skip":0,"relax":0,"curve":0,"filter":null,"mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":8,"font-size":8,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#fdfab9","fill-opacity":0.7,"size":1,"stroke":"#54251d","stroke-width":0.3,"stroke-dasharray":".3 .4","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#fef4d8","fill-opacity":0.7,"size":0.5,"stroke":"#463124","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`; - localStorage.setItem("styleAncient", ancient); - } - - if (!localStorage.getItem("styleMonochrome")) { - const monochrome = `{"#map":{"background-color":"#000000","filter":"url(#filter-grayscale)","data-filter":"grayscale"},"#armies":{"font-size":6,"box-size":3,"stroke":"#000","stroke-width":0.3,"opacity":1,"fill-opacity":1,"filter":null},"#biomes":{"opacity":null,"filter":"url(#blur5)","mask":null},"#stateBorders":{"opacity":1,"stroke":"#56566d","stroke-width":1,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null},"#provinceBorders":{"opacity":1,"stroke":"#56566d","stroke-width":0.4,"stroke-dasharray":1,"stroke-linecap":"butt","filter":null},"#cells":{"opacity":null,"stroke":"#808080","stroke-width":0.1,"filter":null,"mask":null},"#gridOverlay":{"opacity":0.8,"size":10,"type":"pointyHex","stroke":"#808080","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"transform":null,"filter":null,"mask":null},"#coordinates":{"opacity":1,"data-size":12,"font-size":12,"stroke":"#d4d4d4","stroke-width":1,"stroke-dasharray":5,"stroke-linecap":null,"filter":null,"mask":null},"#compass":{"opacity":0.8,"transform":null,"filter":null,"mask":"url(#water)","shape-rendering":"optimizespeed"},"#rose":{"transform":null},"#relig":{"opacity":0.7,"stroke":"#404040","stroke-width":0.7,"filter":null},"#cults":{"opacity":0.6,"stroke":"#777777","stroke-width":0.5,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#landmass":{"opacity":1,"fill":"#000000","filter":null},"#markers":{"opacity":null,"rescale":1,"filter":"url(#dropShadow01)"},"#prec":{"opacity":null,"stroke":"#000000","stroke-width":0.1,"fill":"#003dff","filter":null},"#population":{"opacity":null,"stroke-width":1.6,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null},"#rural":{"stroke":"#0000ff"},"#urban":{"stroke":"#ff0000"},"#freshwater":{"opacity":1,"fill":"#000000","stroke":"#515151","stroke-width":0,"filter":null},"#salt":{"opacity":1,"fill":"#000000","stroke":"#484848","stroke-width":0,"filter":null},"#sinkhole":{"opacity":1,"fill":"#000000","stroke":"#5f5f5f","stroke-width":0.5,"filter":null},"#frozen":{"opacity":1,"fill":"#000000","stroke":"#6f6f6f","stroke-width":0,"filter":null},"#lava":{"opacity":1,"fill":"#000000","stroke":"#5d5d5d","stroke-width":0,"filter":""},"#sea_island":{"opacity":1,"stroke":"#1f3846","stroke-width":0,"filter":"","auto-filter":0},"#lake_island":{"opacity":0,"stroke":"#7c8eaf","stroke-width":0,"filter":null},"#terrain":{"opacity":null,"set":"simple","size":1,"density":0.4,"filter":null,"mask":null},"#rivers":{"opacity":0.2,"filter":"url(#blur1)","fill":"#000000"},"#ruler":{"opacity":null,"filter":null},"#roads":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.7,"stroke-dasharray":2,"stroke-linecap":"butt","filter":null,"mask":null},"#trails":{"opacity":0.9,"stroke":"#d06324","stroke-width":0.25,"stroke-dasharray":".8 1.6","stroke-linecap":"butt","filter":null,"mask":null},"#searoutes":{"opacity":0.8,"stroke":"#ffffff","stroke-width":0.45,"stroke-dasharray":"1 2","stroke-linecap":"round","filter":null,"mask":null},"#regions":{"opacity":0.4,"filter":null},"#statesHalo":{"opacity":1,"data-width":10,"stroke-width":10},"#provs":{"opacity":0.6,"filter":null},"#temperature":{"opacity":null,"font-size":"8px","fill":"#000000","fill-opacity":0.3,"stroke":null,"stroke-width":1.8,"stroke-dasharray":null,"stroke-linecap":null,"filter":null},"#ice":{"opacity":0.8,"fill":"#e8f0f6","stroke":"#e8f0f6","stroke-width":1,"filter":"url(#dropShadow05)"},"#texture":{"opacity":1,"filter":null,"mask":"url(#land)"},"#textureImage":{},"#zones":{"opacity":0.6,"stroke":"#333333","stroke-width":0,"stroke-dasharray":null,"stroke-linecap":"butt","filter":null,"mask":null},"#ocean":{"opacity":0},"#oceanLayers":{"filter":null,"layers":"none"},"#oceanBase":{"fill":"#000000"},"#oceanPattern":{"opacity":null},"#oceanicPattern":{"filter":"url(#emptyImage)"},"#terrs":{"opacity":1,"scheme":"monochrome","terracing":0,"skip":5,"relax":0,"curve":0,"filter":"url(#blur3)","mask":"url(#land)"},"#legend":{"data-size":13,"font-size":13,"data-font":"Almendra+SC","font-family":"Almendra SC","stroke":"#812929","stroke-width":2.5,"stroke-dasharray":"0 4 10 4","stroke-linecap":"round","data-x":99,"data-y":93,"data-columns":8},"#legendBox":{},"#burgLabels > #cities":{"opacity":1,"fill":"#3e3e4b","data-size":7,"font-size":7,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #cities":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":1,"stroke":"#3e3e4b","stroke-width":0.24,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #cities":{"opacity":1,"fill":"#ffffff","size":2,"stroke":"#3e3e4b","stroke-width":1.2},"#burgLabels > #towns":{"opacity":1,"fill":"#3e3e4b","data-size":4,"font-size":4,"data-font":"Almendra+SC","font-family":"Almendra SC"},"#burgIcons > #towns":{"opacity":1,"fill":"#ffffff","fill-opacity":0.7,"size":0.5,"stroke":"#3e3e4b","stroke-width":0.12,"stroke-dasharray":"","stroke-linecap":"butt"},"#anchors > #towns":{"opacity":1,"fill":"#ffffff","size":1,"stroke":"#3e3e4b","stroke-width":1.2},"#labels > #states":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":22,"font-size":22,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#labels > #addedLabels":{"opacity":1,"fill":"#3e3e4b","stroke":"#3a3a3a","stroke-width":0,"data-size":18,"font-size":18,"data-font":"Almendra+SC","font-family":"Almendra SC","filter":null},"#fogging":{"opacity":0.98,"fill":"#30426f","filter":null}}`; - localStorage.setItem("styleMonochrome", monochrome); - } -} - // set default style function applyDefaultStyle() { armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", .3); biomes.attr("opacity", null).attr("filter", null).attr("mask", null); - ice.attr("opacity", .8).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)"); + ice.attr("opacity", .9).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)"); stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt").attr("filter", null); - provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .2).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt").attr("filter", null); + provinceBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .5).attr("stroke-dasharray", "0 2").attr("stroke-linecap", "round").attr("filter", null); cells.attr("opacity", null).attr("stroke", "#808080").attr("stroke-width", .1).attr("filter", null).attr("mask", null); - gridOverlay.attr("opacity", .8).attr("type", "pointyHex").attr("size", 10).attr("stroke", "#808080").attr("stroke-width", .5).attr("stroke-dasharray", null).attr("transform", null).attr("filter", null).attr("mask", null); + gridOverlay.attr("opacity", .8).attr("type", "pointyHex").attr("size", 20).attr("stroke", "#808080").attr("stroke-width", .5).attr("stroke-dasharray", null).attr("transform", null).attr("filter", null).attr("mask", null); coordinates.attr("opacity", 1).attr("data-size", 12).attr("font-size", 12).attr("stroke", "#d4d4d4").attr("stroke-width", 1).attr("stroke-dasharray", 5).attr("filter", null).attr("mask", null); compass.attr("opacity", .8).attr("transform", null).attr("filter", null).attr("mask", "url(#water)").attr("shape-rendering", "optimizespeed"); if (!d3.select("#initial").size()) d3.select("#rose").attr("transform", "translate(80 80) scale(.25)"); @@ -795,6 +800,7 @@ function applyDefaultStyle() { labels.select("#addedLabels").attr("fill", "#3e3e4b").attr("opacity", 1).attr("stroke", "#3a3a3a").attr("stroke-width", 0).attr("font-family", "Almendra SC").attr("data-font", "Almendra+SC").attr("font-size", 18).attr("data-size", 18).attr("filter", null); fogging.attr("opacity", .98).attr("fill", "#30426f"); + emblems.attr("opacity", .9).attr("stroke-width", 1).attr("filter", null); } // apply style settings in JSON @@ -817,10 +823,16 @@ function changeStylePreset(preset) { $("#alert").dialog({resizable: false, title: "Change style preset", width: "23em", buttons: { Change: function() { - const stored = localStorage.getItem(preset); - const style = JSON.isValid(stored) ? JSON.parse(stored) : null; - if (preset === "styleDefault" || !style) applyDefaultStyle(); else applyStyle(style); - if (preset !== "styleDefault" && !style) tip("Cannot parse stored style JSON. Default style is applied", false, "error", 5000); + const customPreset = localStorage.getItem(preset); + if (customPreset) { + if (JSON.isValid(customPreset)) applyStyle(JSON.parse(customPreset)); + else { + tip("Cannot parse stored style JSON. Default style applied", false, "error", 5000); + applyDefaultStyle(); + } + } else if (defaultStyles[preset]) applyStyle(JSON.parse(defaultStyles[preset])); + else applyDefaultStyle(); + removeStyleButton.style.display = stylePreset.selectedOptions[0].dataset.system ? "none" : "inline-block"; updateElements(); // change elements selectStyleElement(); // re-select element to trigger values update @@ -924,6 +936,7 @@ function addStylePreset() { "#provs":["opacity", "filter"], "#temperature":["opacity", "font-size", "fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"], "#ice":["opacity", "fill", "stroke", "stroke-width", "filter"], + "#emblems":["opacity", "stroke-width", "filter"], "#texture":["opacity", "filter", "mask"], "#textureImage":["x", "y"], "#zones":["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"], @@ -1094,7 +1107,7 @@ function addFonts(url) { let family = rule.style.getPropertyValue('font-family'); let font = family.replace(/['"]+/g, '').replace(/ /g, "+"); let weight = rule.style.getPropertyValue('font-weight'); - if (weight !== "400") font += ":" + weight; + if (weight && weight !== "400") font += ":" + weight; if (fonts.indexOf(font) == -1) { fonts.push(font); fetched++ diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 357163b3..d41fbc91 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -55,15 +55,17 @@ toolsContent.addEventListener("click", function(event) { }); function processFeatureRegeneration(event, button) { - if (button === "regenerateStateLabels") {BurgsAndStates.drawStateLabels(); if (!layerIsOn("toggleLabels")) toggleLabels();} else - if (button === "regenerateReliefIcons") {ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief();} else - if (button === "regenerateRoutes") {Routes.regenerate(); if (!layerIsOn("toggleRoutes")) toggleRoutes();} else + if (button === "regenerateStateLabels") {BurgsAndStates.drawStateLabels(); if (!layerIsOn("toggleLabels")) toggleLabels();} else + if (button === "regenerateReliefIcons") {ReliefIcons(); if (!layerIsOn("toggleRelief")) toggleRelief();} else + if (button === "regenerateRoutes") {Routes.regenerate(); if (!layerIsOn("toggleRoutes")) toggleRoutes();} else if (button === "regenerateRivers") regenerateRivers(); else if (button === "regeneratePopulation") recalculatePopulation(); else - if (button === "regenerateBurgs") regenerateBurgs(); else if (button === "regenerateStates") regenerateStates(); else if (button === "regenerateProvinces") regenerateProvinces(); else + if (button === "regenerateBurgs") regenerateBurgs(); else + if (button === "regenerateEmblems") regenerateEmblems(); else if (button === "regenerateReligions") regenerateReligions(); else + if (button === "regenerateCultures") regenerateCultures(); else if (button === "regenerateMilitary") regenerateMilitary(); else if (button === "regenerateIce") regenerateIce(); else if (button === "regenerateMarkers") regenerateMarkers(event); else @@ -94,6 +96,116 @@ function recalculatePopulation() { }); } +function regenerateStates() { + const localSeed = Math.floor(Math.random() * 1e9); // new random seed + Math.random = aleaPRNG(localSeed); + const burgs = pack.burgs.filter(b => b.i && !b.removed); + if (!burgs.length) { + tip("No burgs to generate states. Please create burgs first", false, "error"); + return; + } + if (burgs.length < +regionsInput.value) { + tip(`Not enough burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, "warn"); + } + + // burg local ids sorted by a bit randomized population: + const sorted = burgs.map((b, i) => [i, b.population * Math.random()]).sort((a, b) => b[1] - a[1]).map(b => b[0]); + const capitalsTree = d3.quadtree(); + + // turn all old capitals into towns + burgs.filter(b => b.capital).forEach(b => { + moveBurgToGroup(b.i, "towns"); + b.capital = 0; + }); + + // remove emblems + document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove()); + document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove()); + emblems.selectAll("use").remove(); + + unfog(); + + // if desired states number is 0 + if (regionsInput.value == 0) { + tip(`Cannot generate zero states. Please check the States Number option`, false, "warn"); + pack.states = pack.states.slice(0,1); // remove all except of neutrals + pack.states[0].diplomacy = []; // clear diplomacy + pack.provinces = [0]; // remove all provinces + pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data + borders.selectAll("path").remove(); // remove borders + regions.selectAll("path").remove(); // remove states fill + labels.select("#states").selectAll("text"); // remove state labels + defs.select("#textPaths").selectAll("path[id*='stateLabel']").remove(); // remove state labels paths + + if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click(); + if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click(); + return; + } + + const neutral = pack.states[0].name; + const count = Math.min(+regionsInput.value, burgs.length); + let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals + pack.states = d3.range(count).map(i => { + if (!i) return {i, name: neutral}; + + let capital = null, x = 0, y = 0; + for (const i of sorted) { + capital = burgs[i]; + x = capital.x, y = capital.y; + if (capitalsTree.find(x, y, spacing) === undefined) break; + spacing = Math.max(spacing - 1, 1); + } + + capitalsTree.add([x, y]); + capital.capital = 1; + moveBurgToGroup(capital.i, "cities"); + + const culture = capital.culture; + const basename = capital.name.length < 9 && capital.cell%5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0); + const name = Names.getState(basename, culture); + const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]); + const type = nomadic ? "Nomadic" : pack.cultures[culture].type === "Nomadic" ? "Generic" : pack.cultures[culture].type; + const expansionism = rn(Math.random() * powerInput.value + 1, 1); + + const cultureType = pack.cultures[culture].type; + const coa = COA.generate(capital.coa, .3, null, cultureType); + coa.shield = capital.coa.shield; + + return {i, name, type, capital:capital.i, center:capital.cell, culture, expansionism, coa}; + }); + + BurgsAndStates.expandStates(); + BurgsAndStates.normalizeStates(); + BurgsAndStates.collectStatistics(); + BurgsAndStates.assignColors(); + BurgsAndStates.generateCampaigns(); + BurgsAndStates.generateDiplomacy(); + BurgsAndStates.defineStateForms(); + BurgsAndStates.generateProvinces(true); + if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); + if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); + BurgsAndStates.drawStateLabels(); + Military.generate(); + if (layerIsOn("toggleEmblems")) drawEmblems(); // redrawEmblems + + if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click(); + if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click(); + if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click(); +} + +function regenerateProvinces() { + unfog(); + + BurgsAndStates.generateProvinces(true); + drawBorders(); + if (layerIsOn("toggleProvinces")) drawProvinces(); + + // remove emblems + document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove()); + emblems.selectAll("use").remove(); + if (layerIsOn("toggleEmblems")) drawEmblems(); +} + function regenerateBurgs() { const cells = pack.cells, states = pack.states; rankCells(); @@ -143,100 +255,65 @@ function regenerateBurgs() { BurgsAndStates.drawBurgs(); Routes.regenerate(); + // remove emblems + document.querySelectorAll("[id^=burgCOA]").forEach(el => el.remove()); + emblems.selectAll("use").remove(); + if (layerIsOn("toggleEmblems")) drawEmblems(); + if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click(); if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click(); } -function regenerateStates() { - Math.seedrandom(Math.floor(Math.random() * 1e9)); // new random seed - const burgs = pack.burgs.filter(b => b.i && !b.removed); - if (!burgs.length) { - tip("No burgs to generate states. Please create burgs first", false, "error"); - return; - } - if (burgs.length < +regionsInput.value) { - tip(`Not enought burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, "warn"); - } +function regenerateEmblems() { + // remove old emblems + document.querySelectorAll("[id^=stateCOA]").forEach(el => el.remove()); + document.querySelectorAll("[id^=provinceCOA]").forEach(el => el.remove()); + document.querySelectorAll("[id^=burgCOA]").forEach(el => el.remove()); + emblems.selectAll("use").remove(); - // burg local ids sorted by a bit randomized population: - const sorted = burgs.map((b, i) => [i, b.population * Math.random()]).sort((a, b) => b[1] - a[1]).map(b => b[0]); - const capitalsTree = d3.quadtree(); - - // turn all old capitals into towns - burgs.filter(b => b.capital).forEach(b => { - moveBurgToGroup(b.i, "towns"); - b.capital = 0; + // generate new emblems + pack.states.forEach(state => { + if (!state.i || state.removed) return; + const cultureType = pack.cultures[state.culture].type; + state.coa = COA.generate(null, null, null, cultureType); + state.coa.shield = COA.getShield(state.culture, null); }); - unfog(); + pack.burgs.forEach(burg => { + if (!burg.i || burg.removed) return; + const state = pack.states[burg.state]; - // if desired states number is 0 - if (regionsInput.value == 0) { - tip(`Cannot generate zero states. Please check the States Number option`, false, "warn"); - pack.states = pack.states.slice(0,1); // remove all except of neutrals - pack.states[0].diplomacy = []; // clear diplomacy - pack.provinces = [0]; // remove all provinces - pack.cells.state = new Uint16Array(pack.cells.i.length); // reset cells data - borders.selectAll("path").remove(); // remove borders - regions.selectAll("path").remove(); // remove states fill - labels.select("#states").selectAll("text"); // remove state labels - defs.select("#textPaths").selectAll("path[id*='stateLabel']").remove(); // remove state labels paths + let kinship = .25; + if (burg.capital) kinship += .1; + else if (burg.port) kinship -= .1; + if (burg.culture !== state.culture) kinship -= .25; + burg.coa = COA.generate(state.coa, kinship, null, burg.type); + burg.coa.shield = COA.getShield(burg.culture, burg.state); + }); - if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click(); - if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click(); - return; - } + pack.provinces.forEach(province => { + if (!province.i || province.removed) return; + const parent = province.burg ? pack.burgs[province.burg] : pack.states[province.state]; - const neutral = pack.states[0].name; - const count = Math.min(+regionsInput.value, burgs.length); - let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals - pack.states = d3.range(count).map(i => { - if (!i) return {i, name: neutral}; - - let capital = null, x = 0, y = 0; - for (const i of sorted) { - capital = burgs[i]; - x = capital.x, y = capital.y; - if (capitalsTree.find(x, y, spacing) === undefined) break; - spacing = Math.max(spacing - 1, 1); + let dominion = false; + if (!province.burg) { + dominion = P(.2); + if (province.formName === "Colony") dominion = P(.95); else + if (province.formName === "Island") dominion = P(.6); else + if (province.formName === "Islands") dominion = P(.5); else + if (province.formName === "Territory") dominion = P(.4); else + if (province.formName === "Land") dominion = P(.3); } - capitalsTree.add([x, y]); - capital.capital = 1; - moveBurgToGroup(capital.i, "cities"); - - const culture = capital.culture; - const basename = capital.name.length < 9 && capital.cell%5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, "", 0); - const name = Names.getState(basename, culture); - const nomadic = [1, 2, 3, 4].includes(pack.cells.biome[capital.cell]); - const type = nomadic ? "Nomadic" : pack.cultures[culture].type === "Nomadic" ? "Generic" : pack.cultures[culture].type; - const expansionism = rn(Math.random() * powerInput.value + 1, 1); - return {i, name, type, capital:capital.i, center:capital.cell, culture, expansionism}; + const nameByBurg = province.burg && province.name.slice(0, 3) === parent.name.slice(0, 3); + const kinship = dominion ? 0 : nameByBurg ? .8 : .4; + const culture = pack.cells.culture[province.center]; + const type = BurgsAndStates.getType(province.center, parent.port); + province.coa = COA.generate(parent.coa, kinship, dominion, type); + province.coa.shield = COA.getShield(culture, province.state); }); - BurgsAndStates.expandStates(); - BurgsAndStates.normalizeStates(); - BurgsAndStates.collectStatistics(); - BurgsAndStates.assignColors(); - BurgsAndStates.generateCampaigns(); - BurgsAndStates.generateDiplomacy(); - BurgsAndStates.defineStateForms(); - BurgsAndStates.generateProvinces(true); - if (!layerIsOn("toggleStates")) toggleStates(); else drawStates(); - if (!layerIsOn("toggleBorders")) toggleBorders(); else drawBorders(); - BurgsAndStates.drawStateLabels(); - Military.generate(); - - if (document.getElementById("burgsOverviewRefresh").offsetParent) burgsOverviewRefresh.click(); - if (document.getElementById("statesEditorRefresh").offsetParent) statesEditorRefresh.click(); - if (document.getElementById("militaryOverviewRefresh").offsetParent) militaryOverviewRefresh.click(); -} - -function regenerateProvinces() { - unfog(); - BurgsAndStates.generateProvinces(true); - drawBorders(); - if (layerIsOn("toggleProvinces")) drawProvinces(); + if (layerIsOn("toggleEmblems")) drawEmblems(); // redrawEmblems } function regenerateReligions() { @@ -244,6 +321,15 @@ function regenerateReligions() { if (!layerIsOn("toggleReligions")) toggleReligions(); else drawReligions(); } +function regenerateCultures() { + Cultures.generate(); + Cultures.expand(); + BurgsAndStates.updateCultures(); + Religions.updateCultures(); + if (!layerIsOn("toggleCultures")) toggleCultures(); else drawCultures(); + refreshAllEditors(); +} + function regenerateMilitary() { Military.generate(); if (!layerIsOn("toggleMilitary")) toggleMilitary(); diff --git a/modules/ui/world-configurator.js b/modules/ui/world-configurator.js index ffcef39f..11eef897 100644 --- a/modules/ui/world-configurator.js +++ b/modules/ui/world-configurator.js @@ -2,10 +2,10 @@ function editWorld() { if (customization) return; $("#worldConfigurator").dialog({title: "Configure World", resizable: false, width: "42em", buttons: { - "Whole World": () => applyPreset(100, 50), - "Northern": () => applyPreset(33, 25), - "Tropical": () => applyPreset(33, 50), - "Southern": () => applyPreset(33, 75), + "Whole World": () => applyWorldPreset(100, 50), + "Northern": () => applyWorldPreset(33, 25), + "Tropical": () => applyWorldPreset(33, 50), + "Southern": () => applyWorldPreset(33, 75), "Restore Winds": restoreDefaultWinds }, open: function() { const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button"); @@ -125,7 +125,7 @@ function editWorld() { if (update) updateWorld(); } - function applyPreset(size, lat) { + function applyWorldPreset(size, lat) { document.getElementById("mapSizeInput").value = document.getElementById("mapSizeOutput").value = size; document.getElementById("latitudeInput").value = document.getElementById("latitudeOutput").value = lat; lock("mapSize"); diff --git a/modules/utils.js b/modules/utils.js index 6b681018..3e34336c 100644 --- a/modules/utils.js +++ b/modules/utils.js @@ -232,7 +232,7 @@ function gauss(expected = 100, deviation = 30, min = 0, max = 300, round = 0) { return rn(Math.max(Math.min(d3.randomNormal(expected, deviation)(), max), min), round); } -/** This is a description of the foo function. */ +// probability shorthand for floats function Pint(float) { return ~~float + +P(float % 1); } @@ -471,14 +471,14 @@ function lim(v) { // get number from string in format "1-3" or "2" or "0.5" function getNumberInRange(r) { - if (typeof r !== "string") {console.error("The value should be a string", r); return 0;} + if (typeof r !== "string") {ERROR && console.error("The value should be a string", r); return 0;} if (!isNaN(+r)) return ~~r + +P(r - ~~r); const sign = r[0] === "-" ? -1 : 1; if (isNaN(+r[0])) r = r.slice(1); const range = r.includes("-") ? r.split("-") : null; - if (!range) {console.error("Cannot parse the number. Check the format", r); return 0;} + if (!range) {ERROR && console.error("Cannot parse the number. Check the format", r); return 0;} const count = rand(range[0] * sign, +range[1]); - if (isNaN(count) || count < 0) {console.error("Cannot parse number. Check the format", r); return 0;} + if (isNaN(count) || count < 0) {ERROR && console.error("Cannot parse number. Check the format", r); return 0;} return count; } @@ -516,26 +516,15 @@ function getNextId(core, i = 1) { return core + i; } -// from https://davidwalsh.name/javascript-debounce-function -function debounce(func, wait, immediate) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) func.apply(context, args); - } - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); - } -} +function debounce(f, ms) { + let isCooldown = false; -// pause/block JS execution for a while -function sleep(delay) { - const start = new Date().getTime(); - while (new Date().getTime() < start + delay); + return function() { + if (isCooldown) return; + f.apply(this, arguments); + isCooldown = true; + setTimeout(() => isCooldown = false, ms); + }; } // parse error to get the readable string in Chrome and Firefox @@ -597,15 +586,21 @@ function generateDate(from = 100, to = 1000) { return new Date(rand(from, to),rand(12),rand(31)).toLocaleDateString("en", {year:'numeric', month:'long', day:'numeric'}); } +function getQGIScoordinates(x, y) { + const cx = mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT; + const cy = mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT; // this is inverted in QGIS otherwise + return [cx, cy]; +} + // prompt replacer (prompt does not work in Electron) void function() { const prompt = document.getElementById("prompt"); const form = prompt.querySelector("#promptForm"); - window.prompt = function(promptTest = "Please provide an input", options = {default:1, step:.01, min:0, max:100}, callback) { - if (options.default === undefined) {console.error("Prompt: options object does not have default value defined"); return;} + window.prompt = function(promptText = "Please provide an input", options = {default:1, step:.01, min:0, max:100}, callback) { + if (options.default === undefined) {ERROR && console.error("Prompt: options object does not have default value defined"); return;} const input = prompt.querySelector("#promptInput"); - prompt.querySelector("#promptTest").innerHTML = promptTest; + prompt.querySelector("#promptText").innerHTML = promptText; const type = typeof(options.default) === "number" ? "number" : "text"; input.type = type; if (options.step !== undefined) input.step = options.step; @@ -628,4 +623,4 @@ void function() { }() // indexedDB; ldb object -!function(){function e(t,o){return n?void(n.transaction("s").objectStore("s").get(t).onsuccess=function(e){var t=e.target.result&&e.target.result.v||null;o(t)}):void setTimeout(function(){e(t,o)},100)}var t=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;if(!t)return void console.error("indexedDB not supported");var n,o={k:"",v:""},r=t.open("d2",1);r.onsuccess=function(e){n=this.result},r.onerror=function(e){console.error("indexedDB request error"),console.log(e)},r.onupgradeneeded=function(e){n=null;var t=e.target.result.createObjectStore("s",{keyPath:"k"});t.transaction.oncomplete=function(e){n=e.target.db}},window.ldb={get:e,set:function(e,t){o.k=e,o.v=t,n.transaction("s","readwrite").objectStore("s").put(o)}}}(); \ No newline at end of file +!function(){function e(t,o){return n?void(n.transaction("s").objectStore("s").get(t).onsuccess=function(e){var t=e.target.result&&e.target.result.v||null;o(t)}):void setTimeout(function(){e(t,o)},100)}var t=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;if(!t)return void ERROR && console.error("indexedDB not supported");var n,o={k:"",v:""},r=t.open("d2",1);r.onsuccess=function(e){n=this.result},r.onerror=function(e){ERROR && console.error("indexedDB request error"),INFO && console.log(e)},r.onupgradeneeded=function(e){n=null;var t=e.target.result.createObjectStore("s",{keyPath:"k"});t.transaction.oncomplete=function(e){n=e.target.db}},window.ldb={get:e,set:function(e,t){o.k=e,o.v=t,n.transaction("s","readwrite").objectStore("s").put(o)}}}(); \ No newline at end of file diff --git a/run_python_server.bat b/run_python_server.bat new file mode 100644 index 00000000..b74d34c1 --- /dev/null +++ b/run_python_server.bat @@ -0,0 +1,3 @@ +start chrome.exe http://localhost:8000/ +@echo off +python -m http.server 8000 \ No newline at end of file