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..2677e045 --- /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..88ae177e --- /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..c73a9795 --- /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..49a2c83b --- /dev/null +++ b/charges/cancer.svg @@ -0,0 +1,20 @@ + diff --git a/charges/carreau.svg b/charges/carreau.svg new file mode 100644 index 00000000..24f687c0 --- /dev/null +++ b/charges/carreau.svg @@ -0,0 +1,6 @@ + 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..cfd58106 --- /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..8a6a8139 --- /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..3a7629c8 --- /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..6d912bfd --- /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..23462185 --- /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..a58b68e4 --- /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..88f706b9 --- /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/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 2fac268a..d104ffb3 100644 --- a/index.css +++ b/index.css @@ -5,11 +5,6 @@ src: url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2'); } -body { - border: 0; - height: 100%; -} - t { pointer-events: none; } @@ -76,10 +71,14 @@ input, button, select, a, textarea { outline: none; } -button, select, a, .pointer { +button, select, a { cursor: pointer; } +.pointer { + cursor: pointer !important; +} + #prec text { font-size: 32px; stroke: none; @@ -101,7 +100,7 @@ button, select, a, .pointer { fill-rule: evenodd; } -#lakes, #coastline, #armies, #ice { +#lakes, #coastline, #armies, #ice, #emblems { cursor: pointer; } @@ -130,9 +129,26 @@ button, select, a, .pointer { #armies text { pointer-events: none; user-select: none; + stroke: none; + fill: #fff; + text-shadow: 0 0 4px #000; + dominant-baseline: central; + text-anchor: middle; + font-family: Helvetica; + fill-opacity: 1; } -#statesBody, #provincesBody, #relig, #biomes, #cults { +#armies text.regimentIcon { + font-size: .8em; +} + +#statesBody, #provincesBody { + stroke-width: 2; + fill-rule: evenodd; + mask: url(#land); +} + +#relig, #biomes, #cults { stroke-width: .6; fill-rule: evenodd; mask: url(#land); @@ -141,7 +157,6 @@ button, select, a, .pointer { #statesHalo { fill: none; filter: url(#blur5); - /*animation: hideshow 3s infinite;*/ } #borders { @@ -156,11 +171,6 @@ button, select, a, .pointer { font-size: 9px; } -@keyframes hideshow { - 0% {stroke-width: 1;} - 50% {stroke-width: 10;} -} - #rivers { stroke: none; mask: url(#land); @@ -707,6 +717,15 @@ fieldset { width: 6%; } +.emblemShapePreview { + width: 1.5em; + height: 1.5em; + margin: -.4em .1em; + fill: #fff; + stroke: #000; + stroke-width: 5px; +} + #styleContent table { border-spacing: 0; margin-left: .2em; @@ -1334,7 +1353,7 @@ div.states>.small { } div.states>.cultureName { - width: 5em; + width: 7em; } div.states>.culturePopulation { @@ -1342,10 +1361,7 @@ div.states>.culturePopulation { cursor: pointer; } -div.states > .cultureBase, -div.states > .cultureType, -div.states > .stateCulture, -div.states > .diplomacyRelations { +div.states > select { width: 4.6em; cursor: pointer; border: 0; @@ -1355,10 +1371,7 @@ div.states > .diplomacyRelations { appearance: none; } -div.states > .cultureBase { - width: 6em; -} - +div.states > .cultureBase, div.states > .burgName, div.states > .burgState, div.states > .burgCulture { @@ -1391,6 +1404,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; } @@ -1422,9 +1447,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; @@ -1439,9 +1468,7 @@ div.states>input.riverType { } .burgFeature { - font-size: 1.2em; - padding: 1px 2px; - color: #555; + padding: 1px; cursor: pointer; } @@ -1492,12 +1519,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 { @@ -1533,6 +1558,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; } @@ -1934,8 +1997,8 @@ svg.button { } #alertMessage ul { - padding-left: 15px; - margin: 10px 0; + padding-left: 1.2em; + margin: 1em 0; } .pseudoLink { @@ -2079,6 +2142,11 @@ svg.button { border: dashed 1px #5d4651; } +.speaker { + font-size: .9em; + cursor: pointer; +} + #prompt { position: absolute; left: 50%; diff --git a/index.html b/index.html index 7f787126..2218f7a5 100644 --- a/index.html +++ b/index.html @@ -32,8 +32,8 @@ #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;}} - - + +
@@ -142,67 +142,56 @@")},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/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
@@ -336,37 +338,28 @@ 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 = "Main changes:" //link("https://www.reddit.com/r/FantasyMapGenerator/comments/ft5b41/update_v15/", "Main changes:");
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
+ - State, province and burg Emblems generation
+ - Emblem editor integrated with ${link("https://azgaar.github.io/Armoria", "Armoria")} — our new dedicated Heraldry generator and editor
+ - 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(
{resizable: false, title: "Fantasy Map Generator update", width: "28em",
buttons: {OK: function() {$(this).dialog("close")}},
- position: {my: "center", at: "center", of: "svg"},
+ position: {my: "center center-80", at: "center", of: "svg"},
close: () => localStorage.setItem("version", version)}
);
}
@@ -436,7 +429,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);
});
}
@@ -468,6 +472,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) {
@@ -586,7 +602,7 @@ 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
@@ -620,7 +636,8 @@ function calculateVoronoi(graph, points) {
// Mark features (ocean, lakes, islands)
function markFeatures() {
TIME && console.time("markFeatures");
- Math.seedrandom(seed); // restart Math.random() to get the same result on heightmap edit in Erase mode
+ 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;
@@ -1718,7 +1735,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 924280e3..d3d75e97 100644
--- a/modules/burgs-and-states.js
+++ b/modules/burgs-and-states.js
@@ -88,9 +88,11 @@
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;
});
@@ -137,7 +139,7 @@
}
}
- // define burg coordinates, port status and define details
+ // define burg coordinates, coa, port status and define details
const specifyBurgs = function() {
TIME && console.time("specifyBurgs");
const cells = pack.cells, vertices = pack.vertices, features = pack.features, temp = grid.cells.temp;
@@ -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
@@ -188,14 +202,30 @@
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 (!cells.burg[i] || pack.burgs[cells.burg[i]].population < 6) {
+ if (population < 5 && [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;
});
@@ -348,7 +378,6 @@
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");
}
TIME && console.timeEnd("normalizeStates");
}
@@ -399,11 +428,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();
@@ -438,7 +462,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});
@@ -540,7 +563,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;
@@ -571,6 +594,7 @@
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();
});
@@ -593,7 +617,10 @@
}
// 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);
+ });
TIME && console.timeEnd("collectStatistics");
}
@@ -794,9 +821,6 @@
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)];
@@ -809,21 +833,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);
}
@@ -841,13 +863,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;
}
@@ -865,34 +887,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);
}
}
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) {
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];
@@ -903,15 +925,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;
@@ -928,12 +949,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});
}
});
@@ -945,7 +971,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) {
@@ -1017,7 +1042,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);
@@ -1026,7 +1052,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
@@ -1054,7 +1085,7 @@
}
return {generate, expandStates, normalizeStates, assignColors,
- drawBurgs, specifyBurgs, defineBurgFeatures, drawStateLabels, collectStatistics,
+ 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..22265084
--- /dev/null
+++ b/modules/coa-generator.js
@@ -0,0 +1,501 @@
+(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: 8, ermine: 6,
+ vair: 4, counterVair: 1, vairInPale: 1, vairEnPointe: 2, vairAncien: 2,
+ potent: 2, counterPotent: 1, potentInPale: 1, potentEnPointe: 1,
+ chequy: 8, lozengy: 5, fusily: 2, pally: 8, barry: 10, gemelles: 1,
+ bendy: 8, bendySinister: 4, palyBendy: 2, barryBendy: 1,
+ pappellony: 2, pappellony2: 3, scaly: 1, plumetty: 1,
+ masoned: 6, fretty: 3, grillage: 1, chainy: 1, maily: 2, honeycombed: 1 }
+ }
+
+ 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, carreau: 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 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 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(.1)) size = "-small";
+ else if (P(.1)) size = "-smaller";
+ else if (P(.01)) size = "-big";
+ else if (P(.005)) 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");
+ const shapeGroup = emblemShape.selectedOptions[0].parentNode.label;
+ if (shapeGroup !== "Diversiform") return emblemShape.value;
+
+ if (emblemShape.value === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield;
+ if (pack.cultures[culture].shield) return pack.cultures[culture].shield;
+ console.error("Shield 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, shields};
+
+})));
\ No newline at end of file
diff --git a/modules/coa-renderer.js b/modules/coa-renderer.js
new file mode 100644
index 00000000..3a451782
--- /dev/null
+++ b/modules/coa-renderer.js
@@ -0,0 +1,1094 @@
+(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 = {
+ // straight divisions
+ perFess: ` `,
+ perPale: ` `,
+ perBend: ` `,
+ perBendSinister: ` `,
+ perChevron: ` `,
+ perChevronReversed: ` `,
+ perCross: ` `,
+ perPile: ` `,
+ perSaltire: ` `,
+ gyronny: ` `,
+ chevronny: ` `,
+ // lined divisions
+ perFessLined: line => ` `,
+ perPaleLined: line => ` `,
+ perBendLined: line => ` `,
+ perBendSinisterLined: line => ` `,
+ perChevronLined: line => ` `,
+ perChevronReversedLined: line => ` `,
+ perCrossLined: line => ` `,
+ perPileLined: line => ` `,
+ // straight ordinaries
+ fess: ` `,
+ pale: ` `,
+ bend: ` `,
+ bendSinister: ` `,
+ chief: ` `,
+ bar: ` `,
+ gemelle: ` `,
+ fessCotissed: ` `,
+ fessDoubleCotissed: ` `,
+ bendlet: ` `,
+ bendletSinister: ` `,
+ terrace: ` `,
+ cross: ` `,
+ crossParted: ` `,
+ saltire: ` `,
+ saltireParted: ` `,
+ mount: ` `,
+ point: ` `,
+ flaunches: ` `,
+ gore: ` `,
+ pall: ` `,
+ pallReversed: ` `,
+ chevron: ` `,
+ chevronReversed: ` `,
+ gyron: ` `,
+ quarter: ` `,
+ canton: ` `,
+ pile: ` `,
+ pileInBend: ` `,
+ pileInBendSinister: ` `,
+ piles: ` `,
+ pilesInPoint: ` `,
+ label: ` `,
+ // lined ordinaries
+ fessLined: line => ` `,
+ paleLined: line => ` `,
+ bendLined: line => ` `,
+ bendSinisterLined: line => ` `,
+ chiefLined: line => ` `,
+ barLined: line => ` `,
+ gemelleLined: line => ` `,
+ fessCotissedLined: line => ` `,
+ fessDoubleCotissedLined: line => ` `,
+ bendletLined: line => ` `,
+ bendletSinisterLined: line => ` `,
+ terraceLined: line => ` `,
+ crossLined: line => ` `,
+ crossPartedLined: line => ` `,
+ saltireLined: line => ` `,
+ saltirePartedLined: line => ` `
+ }
+
+ const patterns = {
+ semy: (p, c1, c2, size, chargeId) => ` `,
+ vair: (p, c1, c2, size) => ` `,
+ counterVair: (p, c1, c2, size) => ` `,
+ vairInPale: (p, c1, c2, size) => ` `,
+ vairEnPointe: (p, c1, c2, size) => ` `,
+ vairAncien: (p, c1, c2, size) => ` `,
+ potent: (p, c1, c2, size) => ` `,
+ counterPotent: (p, c1, c2, size) => ` `,
+ potentInPale: (p, c1, c2, size) => ` `,
+ potentEnPointe: (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) => ` `,
+ barryBendy: (p, c1, c2, size) => ` `,
+ pappellony: (p, c1, c2, size) => ` `,
+ pappellony2: (p, c1, c2, size) => ` `,
+ scaly: (p, c1, c2, size) => ` `,
+ plumetty: (p, c1, c2, size) => ` `,
+ masoned: (p, c1, c2, size) => ` `,
+ fretty: (p, c1, c2, size) => ` `,
+ grillage: (p, c1, c2, size) => ` `,
+ chainy: (p, c1, c2, size) => ` `,
+ maily: (p, c1, c2, size) => ` `,
+ honeycombed: (p, c1, c2, size) => ` `
+ }
+
+ const draw = async function(id, coa) {
+ const {shield, division, ordinaries = [], charges = []} = coa;
+
+ const ordinariesRegular = ordinaries.filter(o => !o.above);
+ const ordinariesAboveCharges = ordinaries.filter(o => o.above);
+ const shieldPath = shieldPaths[shield];
+ const tDiv = division ? (division.t.includes("-") ? division.t.split("-")[1] : division.t) : null;
+ const positions = shieldPositions[shield];
+ const sizeModifier = shieldSize[shield] || 1;
+ const viewBox = shieldBox[shield] || "0 0 200 200";
+
+ const shieldClip = ` `;
+ const divisionClip = division ? `${getTemplate(division.division, division.line)} ` : "";
+ const loadedCharges = await getCharges(coa, id, shieldPath);
+ const loadedPatterns = getPatterns(coa, id);
+ const blacklight = ` `;
+ const field = ` `;
+ const divisionGroup = division ? templateDivision() : "";
+ const overlay = ` `;
+
+ const svg= ``;
+
+ // insert coa svg to defs
+ document.getElementById("coas").insertAdjacentHTML("beforeend", svg);
+ return true;
+
+ 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 .8;
+ if (size === "smaller") return .5;
+ if (size === "smallest") return .25;
+ if (size === "big") return 1.6;
+ return 1;
+ }
+
+ function getTemplate(id, line) {
+ const linedId = id+"Lined";
+ if (!line || line === "straight" || !templates[linedId]) return templates[id];
+ const linePath = lines[line];
+ return templates[linedId](linePath);
+ }
+
+ // 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 = async 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)) return 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, shieldPaths};
+
+})));
diff --git a/modules/cultures-generator.js b/modules/cultures-generator.js
index 8e05018f..bf9d2620 100644
--- a/modules/cultures-generator.js
+++ b/modules/cultures-generator.js
@@ -17,7 +17,7 @@
count = Math.floor(populated.length / 50);
if (!count) {
WARN && console.warn(`There are no populated cells. Cannot generate cultures`);
- pack.cultures = [{name:"Wildlands", i:0, base:1}];
+ 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.
@@ -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,6 +55,7 @@
c.origin = 0;
c.code = getCode(c.name);
cells.culture[cell] = i+1;
+ if (emblemShape === "random") c.shield = getRandomShield();
});
function placeCenter(v) {
@@ -65,10 +67,14 @@
}
// 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) {ERROR && 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) {
@@ -113,6 +119,7 @@
// 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++) {
@@ -138,7 +145,13 @@
const code = getCode(name);
const i = pack.cultures.length;
const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex();
- pack.cultures.push({name, color, base, center, i, expansionism:1, type:"Generic", cells:0, area:0, rural:0, urban:0, origin:0, code});
+
+ // define emblem shape
+ let shield = culture.shield;
+ const emblemShape = document.getElementById("emblemShape").value;
+ if (emblemShape === "random") shield = getRandomShield();
+
+ pack.cultures.push({name, color, base, center, i, expansionism:1, type:"Generic", cells:0, area:0, rural:0, urban:0, origin:0, code, shield});
}
const getDefault = function(count) {
@@ -148,164 +161,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,38 +312,38 @@
// 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"}
];
}
@@ -384,14 +378,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);
+
TIME && console.timeEnd('expandCultures');
}
@@ -429,6 +419,11 @@
return 0;
}
- return {generate, add, expand, getDefault};
+ const getRandomShield = function() {
+ const type = rw(COA.shields.types);
+ return rw(COA.shields[type]);
+ }
+
+ return {generate, add, expand, getDefault, getRandomShield};
})));
diff --git a/modules/military-generator.js b/modules/military-generator.js
index 4dd19d3f..2102e2b9 100644
--- a/modules/military-generator.js
+++ b/modules/military-generator.js
@@ -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;
}
diff --git a/modules/relief-icons.js b/modules/relief-icons.js
index ab1916ce..ab6d5490 100644
--- a/modules/relief-icons.js
+++ b/modules/relief-icons.js
@@ -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,7 +64,7 @@
void function renderRelief() {
let reliefHTML = "";
for (const r of relief) {
- reliefHTML += ``;
+ reliefHTML += ``;
}
terrain.html(reliefHTML);
}()
diff --git a/modules/river-generator.js b/modules/river-generator.js
index 54fc1b3a..2e6c5022 100644
--- a/modules/river-generator.js
+++ b/modules/river-generator.js
@@ -1,376 +1,376 @@
(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global.Rivers = factory());
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.Rivers = factory());
}(this, (function () {'use strict';
- const generate = function(changeHeights = true) {
- TIME && console.time('generateRivers');
- Math.seedrandom(seed);
- const cells = pack.cells, p = cells.p, features = pack.features;
+const generate = function(changeHeights = true) {
+ 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)
- void function markupLand() {
- const q = t => cells.i.filter(i => cells.t[i] === t);
- for (let t = 2, queue = q(t); queue.length; t++, queue = q(t)) {
- queue.forEach(i => cells.c[i].forEach(c => {
- if (!cells.t[c]) cells.t[c] = t+1;
- }));
+ // build distance field in cells from water (cells.t)
+ void function markupLand() {
+ const q = t => cells.i.filter(i => cells.t[i] === t);
+ for (let t = 2, queue = q(t); queue.length; t++, queue = q(t)) {
+ queue.forEach(i => cells.c[i].forEach(c => {
+ if (!cells.t[c]) cells.t[c] = t+1;
+ }));
+ }
+ }()
+
+ // height with added t value to make map less depressed
+ const h = Array.from(cells.h)
+ .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100)
+ .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000);
+
+ resolveDepressions(h);
+ features.forEach(f => {delete f.river; delete f.flux; delete f.inlets});
+
+ const riversData = []; // rivers data
+ cells.fl = new Uint16Array(cells.i.length); // water flux array
+ cells.r = new Uint16Array(cells.i.length); // rivers array
+ cells.conf = new Uint8Array(cells.i.length); // confluences array
+ let riverNext = 1; // first river id is 1, not 0
+
+ void function drainWater() {
+ const land = cells.i.filter(i => h[i] >= 20).sort((a,b) => h[b] - h[a]);
+ const outlets = new Uint32Array(features.length);
+ // enumerate lake outlet positions
+ features.filter(f => f.type === "lake" && (f.group === "freshwater" || f.group === "frozen")).forEach(l => {
+ let outlet = 0;
+ if (l.shoreline) {
+ outlet = l.shoreline[d3.scan(l.shoreline, (a,b) => h[a] - h[b])];
+ } else { // in case it got missed or deleted
+ WARN && console.warn('Re-scanning shoreline of a lake');
+ const shallows = cells.i.filter(j => cells.t[j] === -1 && cells.f[j] === l.i);
+ let shoreline = [];
+ shallows.map(w => cells.c[w]).forEach(cList => cList.forEach(s => shoreline.push(s)));
+ outlet = shoreline[d3.scan(shoreline, (a,b) => h[a] - h[b])];
}
- }()
-
- // height with added t value to make map less depressed
- const h = Array.from(cells.h)
- .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + cells.t[i] / 100)
- .map((h, i) => h < 20 || cells.t[i] < 1 ? h : h + d3.mean(cells.c[i].map(c => cells.t[c])) / 10000);
-
- resolveDepressions(h);
- features.forEach(f => {delete f.river; delete f.flux; delete f.inlets});
-
- const riversData = []; // rivers data
- cells.fl = new Uint16Array(cells.i.length); // water flux array
- cells.r = new Uint16Array(cells.i.length); // rivers array
- cells.conf = new Uint8Array(cells.i.length); // confluences array
- let riverNext = 1; // first river id is 1, not 0
-
- void function drainWater() {
- const land = cells.i.filter(i => h[i] >= 20).sort((a,b) => h[b] - h[a]);
- const outlets = new Uint32Array(features.length);
- // enumerate lake outlet positions
- features.filter(f => f.type === "lake" && (f.group === "freshwater" || f.group === "frozen")).forEach(l => {
- let outlet = 0;
- if (l.shoreline) {
- outlet = l.shoreline[d3.scan(l.shoreline, (a,b) => h[a] - h[b])];
- } else { // in case it got missed or deleted
- WARN && console.warn('Re-scanning shoreline of a lake');
- const shallows = cells.i.filter(j => cells.t[j] === -1 && cells.f[j] === l.i);
- let shoreline = [];
- shallows.map(w => cells.c[w]).forEach(cList => cList.forEach(s => shoreline.push(s)));
- outlet = shoreline[d3.scan(shoreline, (a,b) => h[a] - h[b])];
- }
- outlets[l.i] = outlet;
- delete l.shoreline // cleanup temp data once used
- });
-
- const flowDown = function(min, mFlux, iFlux, ri, i = 0){
- if (cells.r[min]) { // downhill cell already has river assigned
- if (mFlux < iFlux) {
- cells.conf[min] = cells.fl[min]; // mark confluence
- if (h[min] >= 20) riversData.find(r => r.river === cells.r[min]).parent = ri; // min river is a tributary of current river
- cells.r[min] = ri; // re-assign river if downhill part has less flux
- } else {
- cells.conf[min] += iFlux; // mark confluence
- if (h[min] >= 20) riversData.find(r => r.river === ri).parent = cells.r[min]; // current river is a tributary of min river
- }
- } else cells.r[min] = ri; // assign the river to the downhill cell
-
- if (h[min] < 20) {
- // pour water to the sea haven
- const oh = i ? cells.haven[i] : min;
- riversData.push({river: ri, cell: oh, x: p[min][0], y: p[min][1]});
- const mf = features[cells.f[min]]; // feature of min cell
- if (mf.type === "lake") {
- if (!mf.river || iFlux > mf.flux) {
- mf.river = ri; // pour water to temporaly elevated lake
- mf.flux = iFlux; // entering flux
- }
- mf.totalFlux += iFlux;
- if (mf.inlets) {
- mf.inlets.push(ri);
- } else {
- mf.inlets = [ri];
- }
- }
- } else {
- cells.fl[min] += iFlux; // propagate flux
- riversData.push({river: ri, cell: min, x: p[min][0], y: p[min][1]}); // add next River segment
- }
- }
-
- land.forEach(function(i) {
- cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation
- const x = p[i][0], y = p[i][1];
-
- // lake outlets draw from lake
- let n = -1, out2 = 0;
- while (outlets.includes(i, n+1)) {
- n = outlets.indexOf(i, n+1);
- const l = features[n];
- if ( ! l ) {continue;}
- const j = cells.haven[i];
- // allow chain lakes to retain identity
- if(cells.r[j] !== l.river) {
- let touch = false;
- for (const c of cells.c[j]){
- if (cells.r[c] === l.river) {
- touch = true;
- break;
- }
- }
- if (touch) {
- cells.r[j] = l.river;
- riversData.push({river: l.river, cell: j, x: p[j][0], y: p[j][1]});
- } else {
- cells.r[j] = riverNext;
- riversData.push({river: riverNext, cell: j, x: p[j][0], y: p[j][1]});
- riverNext++;
- }
- }
- cells.fl[j] = l.totalFlux; // signpost river size
- flowDown(i, cells.fl[i], l.totalFlux, cells.r[j]);
- // prevent dropping imediately back into the lake
- out2 = cells.c[i].filter(c => (h[c] >= 20 || cells.f[c] !== cells.f[j])).sort((a,b) => h[a] - h[b])[0]; // downhill cell not in the source lake
- // assign all to outlet basin
- if (l.inlets) l.inlets.forEach(fork => riversData.find(r => r.river === fork).parent = cells.r[j]);
- }
-
- // near-border cell: pour out of the screen
- if (cells.b[i]) {
- if (cells.r[i]) {
- const to = [];
- const min = Math.min(y, graphHeight - y, x, graphWidth - x);
- if (min === y) {to[0] = x; to[1] = 0;} else
- if (min === graphHeight - y) {to[0] = x; to[1] = graphHeight;} else
- if (min === x) {to[0] = 0; to[1] = y;} else
- if (min === graphWidth - x) {to[0] = graphWidth; to[1] = y;}
- riversData.push({river: cells.r[i], cell: i, x: to[0], y: to[1]});
- }
- return;
- }
-
- const min = out2 ? out2 : cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell
-
- if (cells.fl[i] < 30) {
- if (h[min] >= 20) cells.fl[min] += cells.fl[i];
- return; // flux is too small to operate as river
- }
-
- // Proclaim a new river
- if (!cells.r[i]) {
- cells.r[i] = riverNext;
- riversData.push({river: riverNext, cell: i, x, y});
- riverNext++;
- }
-
- flowDown(min, cells.fl[min], cells.fl[i], cells.r[i], i);
-
- });
- }()
-
- void function defineRivers() {
- pack.rivers = []; // rivers data
- const riverPaths = []; // temporary data for all rivers
-
- for (let r = 1; r <= riverNext; r++) {
- const riverSegments = riversData.filter(d => d.river === r);
-
- if (riverSegments.length > 2) {
- const source = riverSegments[0], mouth = riverSegments[riverSegments.length-2];
- const riverEnhanced = addMeandring(riverSegments);
- let width = rn(.8 + Math.random() * .4, 1); // river width modifier [.2, 10]
- let increment = rn(.8 + Math.random() * .6, 1); // river bed widening modifier [.01, 3]
- const [path, length] = getPath(riverEnhanced, width, increment, cells.h[source.cell] >= 20 ? .1 : .6);
- riverPaths.push([r, path, width, increment]);
- const parent = source.parent || 0;
- pack.rivers.push({i:r, parent, length, source:source.cell, mouth:mouth.cell});
- } else {
- // remove too short rivers
- riverSegments.filter(s => cells.r[s.cell] === r).forEach(s => cells.r[s.cell] = 0);
- }
- }
-
- // 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]);
- }()
-
- // apply change heights as basic one
- if (changeHeights) cells.h = Uint8Array.from(h);
-
- TIME && console.timeEnd('generateRivers');
- }
-
- // depression filling algorithm (for a correct water flux modeling)
- const resolveDepressions = function(h) {
- const cells = pack.cells;
- const land = cells.i.filter(i => h[i] >= 20 && h[i] < 100 && !cells.b[i]); // exclude near-border cells
- const lakes = pack.features.filter(f => f.type === "lake" && (f.group === "freshwater" || f.group === "frozen")); // to keep lakes flat
- lakes.forEach(l => {
- l.shoreline = [];
- l.height = 21;
- l.totalFlux = grid.cells.prec[cells.g[l.firstCell]];
+ outlets[l.i] = outlet;
+ delete l.shoreline // cleanup temp data once used
});
- for (let i of land.filter(i => cells.t[i] === 1)) { // select shoreline cells
- cells.c[i].map(c => pack.features[cells.f[c]]).forEach(cf => {
- if (lakes.includes(cf) && !cf.shoreline.includes(i)) {
- cf.shoreline.push(i);
- }
- })
- }
- land.sort((a,b) => h[b] - h[a]); // highest cells go first
- let depressed = false;
- for (let l = 0, depression = Infinity; depression && l < 100; l++) {
- depression = 0;
- for (const l of lakes) {
- const minHeight = d3.min(l.shoreline.map(s => h[s]));
- if (minHeight === 100) continue; // already max height
- if (l.height <= minHeight) {
- l.height = Math.min(minHeight + 1, 100);
- depression++;
- depressed = true;
+ const flowDown = function(min, mFlux, iFlux, ri, i = 0){
+ if (cells.r[min]) { // downhill cell already has river assigned
+ if (mFlux < iFlux) {
+ cells.conf[min] = cells.fl[min]; // mark confluence
+ if (h[min] >= 20) riversData.find(r => r.river === cells.r[min]).parent = ri; // min river is a tributary of current river
+ cells.r[min] = ri; // re-assign river if downhill part has less flux
+ } else {
+ cells.conf[min] += iFlux; // mark confluence
+ if (h[min] >= 20) riversData.find(r => r.river === ri).parent = cells.r[min]; // current river is a tributary of min river
}
- }
- for (const i of land) {
- const minHeight = d3.min(cells.c[i].map(c => cells.t[c] > 0 ? h[c] :
- pack.features[cells.f[c]].height || h[c] // NB undefined is falsy (a || b is short for a ? a : b)
- ));
- if (minHeight === 100) continue; // already max height
- if (h[i] <= minHeight) {
- h[i] = Math.min(minHeight + 1, 100);
- depression++;
- depressed = true;
+ } else cells.r[min] = ri; // assign the river to the downhill cell
+
+ if (h[min] < 20) {
+ // pour water to the sea haven
+ const oh = i ? cells.haven[i] : min;
+ riversData.push({river: ri, cell: oh, x: p[min][0], y: p[min][1]});
+ const mf = features[cells.f[min]]; // feature of min cell
+ if (mf.type === "lake") {
+ if (!mf.river || iFlux > mf.flux) {
+ mf.river = ri; // pour water to temporaly elevated lake
+ mf.flux = iFlux; // entering flux
+ }
+ mf.totalFlux += iFlux;
+ if (mf.inlets) {
+ mf.inlets.push(ri);
+ } else {
+ mf.inlets = [ri];
+ }
}
+ } else {
+ cells.fl[min] += iFlux; // propagate flux
+ riversData.push({river: ri, cell: min, x: p[min][0], y: p[min][1]}); // add next River segment
}
}
- return depressed;
+ land.forEach(function(i) {
+ cells.fl[i] += grid.cells.prec[cells.g[i]]; // flux from precipitation
+ const x = p[i][0], y = p[i][1];
+
+ // lake outlets draw from lake
+ let n = -1, out2 = 0;
+ while (outlets.includes(i, n+1)) {
+ n = outlets.indexOf(i, n+1);
+ const l = features[n];
+ if ( ! l ) {continue;}
+ const j = cells.haven[i];
+ // allow chain lakes to retain identity
+ if(cells.r[j] !== l.river) {
+ let touch = false;
+ for (const c of cells.c[j]){
+ if (cells.r[c] === l.river) {
+ touch = true;
+ break;
+ }
+ }
+ if (touch) {
+ cells.r[j] = l.river;
+ riversData.push({river: l.river, cell: j, x: p[j][0], y: p[j][1]});
+ } else {
+ cells.r[j] = riverNext;
+ riversData.push({river: riverNext, cell: j, x: p[j][0], y: p[j][1]});
+ riverNext++;
+ }
+ }
+ cells.fl[j] = l.totalFlux; // signpost river size
+ flowDown(i, cells.fl[i], l.totalFlux, cells.r[j]);
+ // prevent dropping imediately back into the lake
+ out2 = cells.c[i].filter(c => (h[c] >= 20 || cells.f[c] !== cells.f[j])).sort((a,b) => h[a] - h[b])[0]; // downhill cell not in the source lake
+ // assign all to outlet basin
+ if (l.inlets) l.inlets.forEach(fork => riversData.find(r => r.river === fork).parent = cells.r[j]);
+ }
+
+ // near-border cell: pour out of the screen
+ if (cells.b[i]) {
+ if (cells.r[i]) {
+ const to = [];
+ const min = Math.min(y, graphHeight - y, x, graphWidth - x);
+ if (min === y) {to[0] = x; to[1] = 0;} else
+ if (min === graphHeight - y) {to[0] = x; to[1] = graphHeight;} else
+ if (min === x) {to[0] = 0; to[1] = y;} else
+ if (min === graphWidth - x) {to[0] = graphWidth; to[1] = y;}
+ riversData.push({river: cells.r[i], cell: i, x: to[0], y: to[1]});
+ }
+ return;
+ }
+
+ const min = out2 ? out2 : cells.c[i][d3.scan(cells.c[i], (a, b) => h[a] - h[b])]; // downhill cell
+
+ if (cells.fl[i] < 30) {
+ if (h[min] >= 20) cells.fl[min] += cells.fl[i];
+ return; // flux is too small to operate as river
+ }
+
+ // Proclaim a new river
+ if (!cells.r[i]) {
+ cells.r[i] = riverNext;
+ riversData.push({river: riverNext, cell: i, x, y});
+ riverNext++;
+ }
+
+ flowDown(min, cells.fl[min], cells.fl[i], cells.r[i], i);
+
+ });
+ }()
+
+ void function defineRivers() {
+ pack.rivers = []; // rivers data
+ const riverPaths = []; // temporary data for all rivers
+
+ for (let r = 1; r <= riverNext; r++) {
+ const riverSegments = riversData.filter(d => d.river === r);
+
+ if (riverSegments.length > 2) {
+ const source = riverSegments[0], mouth = riverSegments[riverSegments.length-2];
+ const riverEnhanced = addMeandring(riverSegments);
+ let width = rn(.8 + Math.random() * .4, 1); // river width modifier [.2, 10]
+ let increment = rn(.8 + Math.random() * .6, 1); // river bed widening modifier [.01, 3]
+ const [path, length] = getPath(riverEnhanced, width, increment, cells.h[source.cell] >= 20 ? .1 : .6);
+ riverPaths.push([r, path, width, increment]);
+ const parent = source.parent || 0;
+ pack.rivers.push({i:r, parent, length, source:source.cell, mouth:mouth.cell});
+ } else {
+ // remove too short rivers
+ riverSegments.filter(s => cells.r[s.cell] === r).forEach(s => cells.r[s.cell] = 0);
+ }
+ }
+
+ // 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]);
+ }()
+
+ // apply change heights as basic one
+ if (changeHeights) cells.h = Uint8Array.from(h);
+
+ TIME && console.timeEnd('generateRivers');
+}
+
+// depression filling algorithm (for a correct water flux modeling)
+const resolveDepressions = function(h) {
+ const cells = pack.cells;
+ const land = cells.i.filter(i => h[i] >= 20 && h[i] < 100 && !cells.b[i]); // exclude near-border cells
+ const lakes = pack.features.filter(f => f.type === "lake" && (f.group === "freshwater" || f.group === "frozen")); // to keep lakes flat
+ lakes.forEach(l => {
+ l.shoreline = [];
+ l.height = 21;
+ l.totalFlux = grid.cells.prec[cells.g[l.firstCell]];
+ });
+ for (let i of land.filter(i => cells.t[i] === 1)) { // select shoreline cells
+ cells.c[i].map(c => pack.features[cells.f[c]]).forEach(cf => {
+ if (lakes.includes(cf) && !cf.shoreline.includes(i)) {
+ cf.shoreline.push(i);
+ }
+ })
+ }
+ land.sort((a,b) => h[b] - h[a]); // highest cells go first
+ let depressed = false;
+
+ for (let l = 0, depression = Infinity; depression && l < 100; l++) {
+ depression = 0;
+ for (const l of lakes) {
+ const minHeight = d3.min(l.shoreline.map(s => h[s]));
+ if (minHeight === 100) continue; // already max height
+ if (l.height <= minHeight) {
+ l.height = Math.min(minHeight + 1, 100);
+ depression++;
+ depressed = true;
+ }
+ }
+ for (const i of land) {
+ const minHeight = d3.min(cells.c[i].map(c => cells.t[c] > 0 ? h[c] :
+ pack.features[cells.f[c]].height || h[c] // NB undefined is falsy (a || b is short for a ? a : b)
+ ));
+ if (minHeight === 100) continue; // already max height
+ if (h[i] <= minHeight) {
+ h[i] = Math.min(minHeight + 1, 100);
+ depression++;
+ depressed = true;
+ }
+ }
}
- // add more river points on 1/3 and 2/3 of length
- const addMeandring = function(segments, rndFactor = 0.3) {
- const riverEnhanced = []; // to store enhanced segments
- let side = 1; // to control meandring direction
+ return depressed;
+}
- for (let s = 0; s < segments.length; s++) {
- const sX = segments[s].x, sY = segments[s].y; // segment start coordinates
- const c = pack.cells.conf[segments[s].cell] || 0; // if segment is river confluence
- riverEnhanced.push([sX, sY, c]);
+// add more river points on 1/3 and 2/3 of length
+const addMeandring = function(segments, rndFactor = 0.3) {
+ const riverEnhanced = []; // to store enhanced segments
+ let side = 1; // to control meandring direction
- if (s+1 === segments.length) break; // do not enhance last segment
+ for (let s = 0; s < segments.length; s++) {
+ const sX = segments[s].x, sY = segments[s].y; // segment start coordinates
+ const c = pack.cells.conf[segments[s].cell] || 0; // if segment is river confluence
+ riverEnhanced.push([sX, sY, c]);
- const eX = segments[s+1].x, eY = segments[s+1].y; // segment end coordinates
- const angle = Math.atan2(eY - sY, eX - sX);
- const sin = Math.sin(angle), cos = Math.cos(angle);
- const serpentine = 1 / (s + 1) + 0.3;
- const meandr = serpentine + Math.random() * rndFactor;
- if (P(.5)) side *= -1; // change meandring direction in 50%
- const dist2 = (eX - sX) ** 2 + (eY - sY) ** 2;
- // if dist2 is big or river is small add extra points at 1/3 and 2/3 of segment
- if (dist2 > 64 || (dist2 > 16 && segments.length < 6)) {
- const p1x = (sX * 2 + eX) / 3 + side * -sin * meandr;
- const p1y = (sY * 2 + eY) / 3 + side * cos * meandr;
- if (P(.2)) side *= -1; // change 2nd extra point meandring direction in 20%
- const p2x = (sX + eX * 2) / 3 + side * sin * meandr;
- const p2y = (sY + eY * 2) / 3 + side * cos * meandr;
- riverEnhanced.push([p1x, p1y], [p2x, p2y]);
- // if dist is medium or river is small add 1 extra middlepoint
- } else if (dist2 > 16 || segments.length < 6) {
- const p1x = (sX + eX) / 2 + side * -sin * meandr;
- const p1y = (sY + eY) / 2 + side * cos * meandr;
- riverEnhanced.push([p1x, p1y]);
- }
+ if (s+1 === segments.length) break; // do not enhance last segment
+ const eX = segments[s+1].x, eY = segments[s+1].y; // segment end coordinates
+ const angle = Math.atan2(eY - sY, eX - sX);
+ const sin = Math.sin(angle), cos = Math.cos(angle);
+ const serpentine = 1 / (s + 1) + 0.3;
+ const meandr = serpentine + Math.random() * rndFactor;
+ if (P(.5)) side *= -1; // change meandring direction in 50%
+ const dist2 = (eX - sX) ** 2 + (eY - sY) ** 2;
+ // if dist2 is big or river is small add extra points at 1/3 and 2/3 of segment
+ if (dist2 > 64 || (dist2 > 16 && segments.length < 6)) {
+ const p1x = (sX * 2 + eX) / 3 + side * -sin * meandr;
+ const p1y = (sY * 2 + eY) / 3 + side * cos * meandr;
+ if (P(.2)) side *= -1; // change 2nd extra point meandring direction in 20%
+ const p2x = (sX + eX * 2) / 3 + side * sin * meandr;
+ const p2y = (sY + eY * 2) / 3 + side * cos * meandr;
+ riverEnhanced.push([p1x, p1y], [p2x, p2y]);
+ // if dist is medium or river is small add 1 extra middlepoint
+ } else if (dist2 > 16 || segments.length < 6) {
+ const p1x = (sX + eX) / 2 + side * -sin * meandr;
+ const p1y = (sY + eY) / 2 + side * cos * meandr;
+ riverEnhanced.push([p1x, p1y]);
}
- return riverEnhanced;
+
}
+ return riverEnhanced;
+}
- const getPath = function(points, width = 1, increment = 1, starting = .1) {
- let offset, extraOffset = starting; // starting river width (to make river source visible)
- const riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i-1][0], v[1] - p[i-1][1]) : 0), 0); // summ of segments length
- const widening = rn((1000 + (riverLength * 30)) * increment);
- const riverPointsLeft = [], riverPointsRight = []; // store points on both sides to build a valid polygon
- const last = points.length - 1;
- const factor = riverLength / points.length;
+const getPath = function(points, width = 1, increment = 1, starting = .1) {
+ let offset, extraOffset = starting; // starting river width (to make river source visible)
+ const riverLength = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i-1][0], v[1] - p[i-1][1]) : 0), 0); // summ of segments length
+ const widening = rn((1000 + (riverLength * 30)) * increment);
+ const riverPointsLeft = [], riverPointsRight = []; // store points on both sides to build a valid polygon
+ const last = points.length - 1;
+ const factor = riverLength / points.length;
- // first point
- let x = points[0][0], y = points[0][1], c;
- let angle = Math.atan2(y - points[1][1], x - points[1][0]);
- let sin = Math.sin(angle), cos = Math.cos(angle);
- let xLeft = x + -sin * extraOffset, yLeft = y + cos * extraOffset;
- riverPointsLeft.push([xLeft, yLeft]);
- let xRight = x + sin * extraOffset, yRight = y + -cos * extraOffset;
- riverPointsRight.unshift([xRight, yRight]);
+ // first point
+ let x = points[0][0], y = points[0][1], c;
+ let angle = Math.atan2(y - points[1][1], x - points[1][0]);
+ let sin = Math.sin(angle), cos = Math.cos(angle);
+ let xLeft = x + -sin * extraOffset, yLeft = y + cos * extraOffset;
+ riverPointsLeft.push([xLeft, yLeft]);
+ let xRight = x + sin * extraOffset, yRight = y + -cos * extraOffset;
+ riverPointsRight.unshift([xRight, yRight]);
- // middle points
- for (let p = 1; p < last; p++) {
- x = points[p][0], y = points[p][1], c = points[p][2] || 0;
- const xPrev = points[p-1][0], yPrev = points[p - 1][1];
- const xNext = points[p+1][0], yNext = points[p + 1][1];
- angle = Math.atan2(yPrev - yNext, xPrev - xNext);
- sin = Math.sin(angle), cos = Math.cos(angle);
- offset = (Math.atan(Math.pow(p * factor, 2) / widening) / 2 * width) + extraOffset;
- const confOffset = Math.atan(c * 5 / widening);
- extraOffset += confOffset;
- xLeft = x + -sin * offset, yLeft = y + cos * (offset + confOffset);
- riverPointsLeft.push([xLeft, yLeft]);
- xRight = x + sin * offset, yRight = y + -cos * offset;
- riverPointsRight.unshift([xRight, yRight]);
- }
-
- // end point
- x = points[last][0], y = points[last][1], c = points[last][2];
- if (c) extraOffset += Math.atan(c * 10 / widening); // add extra width on river confluence
- angle = Math.atan2(points[last-1][1] - y, points[last-1][0] - x);
+ // middle points
+ for (let p = 1; p < last; p++) {
+ x = points[p][0], y = points[p][1], c = points[p][2] || 0;
+ const xPrev = points[p-1][0], yPrev = points[p - 1][1];
+ const xNext = points[p+1][0], yNext = points[p + 1][1];
+ angle = Math.atan2(yPrev - yNext, xPrev - xNext);
sin = Math.sin(angle), cos = Math.cos(angle);
- xLeft = x + -sin * offset, yLeft = y + cos * offset;
+ offset = (Math.atan(Math.pow(p * factor, 2) / widening) / 2 * width) + extraOffset;
+ const confOffset = Math.atan(c * 5 / widening);
+ extraOffset += confOffset;
+ xLeft = x + -sin * offset, yLeft = y + cos * (offset + confOffset);
riverPointsLeft.push([xLeft, yLeft]);
xRight = x + sin * offset, yRight = y + -cos * offset;
riverPointsRight.unshift([xRight, yRight]);
-
- // generate polygon path and return
- lineGen.curve(d3.curveCatmullRom.alpha(0.1));
- const right = lineGen(riverPointsRight);
- let left = lineGen(riverPointsLeft);
- left = left.substring(left.indexOf("C"));
- return [round(right + left, 2), rn(riverLength, 2)];
}
- const specify = function() {
- if (!pack.rivers.length) return;
- Math.seedrandom(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
+ // end point
+ x = points[last][0], y = points[last][1], c = points[last][2];
+ if (c) extraOffset += Math.atan(c * 10 / widening); // add extra width on river confluence
+ angle = Math.atan2(points[last-1][1] - y, points[last-1][0] - x);
+ sin = Math.sin(angle), cos = Math.cos(angle);
+ xLeft = x + -sin * offset, yLeft = y + cos * offset;
+ riverPointsLeft.push([xLeft, yLeft]);
+ xRight = x + sin * offset, yRight = y + -cos * offset;
+ riverPointsRight.unshift([xRight, yRight]);
- for (const r of pack.rivers) {
- r.basin = getBasin(r.i, r.parent);
- r.name = getName(r.mouth);
- //debug.append("circle").attr("cx", pack.cells.p[r.mouth][0]).attr("cy", pack.cells.p[r.mouth][1]).attr("r", 2);
- const small = r.length < smallLength;
- r.type = r.parent && !(r.i%6) ? small ? "Branch" : "Fork" : small ? rw(smallType) : "River";
- }
+ // generate polygon path and return
+ lineGen.curve(d3.curveCatmullRom.alpha(0.1));
+ const right = lineGen(riverPointsRight);
+ let left = lineGen(riverPointsLeft);
+ left = left.substring(left.indexOf("C"));
+ return [round(right + left, 2), rn(riverLength, 2)];
+}
+
+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
+
+ for (const r of pack.rivers) {
+ r.basin = getBasin(r.i, r.parent);
+ r.name = getName(r.mouth);
+ //debug.append("circle").attr("cx", pack.cells.p[r.mouth][0]).attr("cy", pack.cells.p[r.mouth][1]).attr("r", 2);
+ const small = r.length < smallLength;
+ r.type = r.parent && !(r.i%6) ? small ? "Branch" : "Fork" : small ? rw(smallType) : "River";
}
+}
- const getName = function(cell) {
- return Names.getCulture(pack.cells.culture[cell]);
+const getName = function(cell) {
+ return Names.getCulture(pack.cells.culture[cell]);
+}
+
+// remove river and all its tributaries
+const remove = function(id) {
+ const cells = pack.cells;
+ const riversToRemove = pack.rivers.filter(r => r.i === id || getBasin(r.i, r.parent, id) === id).map(r => r.i);
+ riversToRemove.forEach(r => rivers.select("#river"+r).remove());
+ cells.r.forEach((r, i) => {
+ if (!r || !riversToRemove.includes(r)) return;
+ cells.r[i] = 0;
+ cells.fl[i] = grid.cells.prec[cells.g[i]];
+ cells.conf[i] = 0;
+ });
+ pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i));
+}
+
+const getBasin = function(r, p, e) {
+ while (p && r !== p && r !== e) {
+ const parent = pack.rivers.find(r => r.i === p);
+ if (!parent) return r;
+ r = parent.i;
+ p = parent.parent;
}
+ return r;
+}
- // remove river and all its tributaries
- const remove = function(id) {
- const cells = pack.cells;
- const riversToRemove = pack.rivers.filter(r => r.i === id || getBasin(r.i, r.parent, id) === id).map(r => r.i);
- riversToRemove.forEach(r => rivers.select("#river"+r).remove());
- cells.r.forEach((r, i) => {
- if (!r || !riversToRemove.includes(r)) return;
- cells.r[i] = 0;
- cells.fl[i] = grid.cells.prec[cells.g[i]];
- cells.conf[i] = 0;
- });
- pack.rivers = pack.rivers.filter(r => !riversToRemove.includes(r.i));
- }
+return {generate, resolveDepressions, addMeandring, getPath, specify, getName, getBasin, remove};
- const getBasin = function(r, p, e) {
- while (p && r !== p && r !== e) {
- const parent = pack.rivers.find(r => r.i === p);
- if (!parent) return r;
- r = parent.i;
- p = parent.parent;
- }
- return r;
- }
-
- return {generate, resolveDepressions, addMeandring, getPath, specify, getName, getBasin, remove};
-
-})));
+})));
\ No newline at end of file
diff --git a/modules/save-and-load.js b/modules/save-and-load.js
index 86d7e6db..f725a6e8 100644
--- a/modules/save-and-load.js
+++ b/modules/save-and-load.js
@@ -8,7 +8,6 @@ async function saveSVG() {
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);
@@ -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();
@@ -64,7 +62,6 @@ 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);
@@ -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");
});
}
@@ -170,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;
@@ -246,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});
@@ -298,7 +375,6 @@ 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);
@@ -423,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() {
@@ -501,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");
@@ -519,7 +597,7 @@ function uploadMap(file, callback) {
} else {
load = true;
message = `The map version (${mapVersion}) does not match the Generator version (${version}).
-
Click OK to get map 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: {
@@ -638,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");
@@ -698,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();
}()
@@ -741,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());
}()
@@ -917,13 +1003,6 @@ 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();
}
@@ -941,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");
@@ -981,6 +1059,35 @@ function parseLoadedData(data) {
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => r.state = s.i));
}
+ if (version < 1.5) {
+ // 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 cultures has shield attribute
+ pack.cultures.forEach(culture => {
+ if (culture.removed) return;
+ culture.shield = Cultures.getRandomShield();
+ });
+
+ // 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);
+ });
+
+ // v 1.5 added emblems
+ defs.append("g").attr("id", "defs-emblems");
+ emblems = viewbox.insert("g", "#population").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();
+ }
+
}()
void function checkDataIntegrity() {
@@ -1041,6 +1148,9 @@ 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;
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 9a3af95c..2b384ee5 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", "Copenhagen", "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() {
@@ -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 8c4e7965..5f25fb38 100644
--- a/modules/ui/cultures-editor.js
+++ b/modules/ui/cultures-editor.js
@@ -60,6 +60,9 @@ function editCultures() {
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
let lines = "", totalArea = 0, totalPopulation = 0;
+ const emblemShapeGroup = document.getElementById("emblemShape").selectedOptions[0].parentNode.label;
+ const selectShape = emblemShapeGroup === "Diversiform"
+
for (const c of pack.cultures) {
if (c.removed) continue;
const area = c.area * (distanceScaleInput.value ** 2);
@@ -73,7 +76,7 @@ function editCultures() {
if (!c.i) {
// Uncultured (neutral) line
lines += `
+ data-area=${area} data-population=${population} data-base=${c.base} data-type="" data-expansionism="" data-emblems="${c.shield}">
@@ -86,26 +89,28 @@ function editCultures() {
-
+
+ ${selectShape ? `` : ""}
`;
continue;
}
lines += `
+ data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}">
-
+
-
+
+ ${selectShape ? `` : ""}
`;
}
@@ -128,6 +133,7 @@ function editCultures() {
body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism));
body.querySelectorAll("div > select.cultureType").forEach(el => el.addEventListener("change", cultureChangeType));
body.querySelectorAll("div > select.cultureBase").forEach(el => el.addEventListener("change", cultureChangeBase));
+ body.querySelectorAll("div > select.cultureShape").forEach(el => el.addEventListener("change", cultureChangeShape));
body.querySelectorAll("div > div.culturePopulation").forEach(el => el.addEventListener("click", changePopulation));
body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.addEventListener("click", cultureRegenerateBurgs));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", cultureRemove));
@@ -150,6 +156,11 @@ function editCultures() {
return options;
}
+ function getShapeOptions(selected) {
+ const shapes = Object.keys(COA.shields.types).map(type => Object.keys(COA.shields[type])).flat();
+ return shapes.map(shape => ``);
+ }
+
function cultureHighlightOn(event) {
const culture = +event.target.dataset.id;
const info = document.getElementById("cultureInfo");
@@ -225,6 +236,40 @@ function editCultures() {
this.parentNode.dataset.base = pack.cultures[culture].base = v;
}
+ function cultureChangeShape() {
+ const culture = +this.parentNode.dataset.id;
+ const shape = this.value;
+ this.parentNode.dataset.emblems = pack.cultures[culture].shield = shape;
+
+ const rerenderCOA = (id, coa) => {
+ const coaEl = document.getElementById(id);
+ if (!coaEl) return; // not rendered
+ coaEl.remove();
+ COArenderer.trigger(id, coa);
+ }
+
+ pack.states.forEach(state => {
+ if (state.culture !== culture || !state.i || state.removed || !state.coa || state.coa === "custom") return;
+ if (shape === state.coa.shield) return;
+ state.coa.shield = shape;
+ rerenderCOA("stateCOA" + state.i, state.coa);
+ });
+
+ pack.provinces.forEach(province => {
+ if (pack.cells.culture[province.center] !== culture || !province.i || province.removed || !province.coa || province.coa === "custom") return;
+ if (shape === province.coa.shield) return;
+ province.coa.shield = shape;
+ rerenderCOA("provinceCOA" + province.i, province.coa);
+ });
+
+ pack.burgs.forEach(burg => {
+ if (burg.culture !== culture || !burg.i || burg.removed || !burg.coa || burg.coa === "custom") return;
+ if (shape === burg.coa.shield) return;
+ burg.coa.shield = shape
+ rerenderCOA("burgCOA" + burg.i, burg.coa);
+ });
+ }
+
function changePopulation() {
const culture = +this.parentNode.dataset.id;
const c = pack.cultures[culture];
@@ -293,7 +338,7 @@ function editCultures() {
b.name = Names.getCulture(culture);
labels.select("[data-id='" + b.i +"']").text(b.name);
});
- tip(`Names for ${cBurgs.length} burgs are re-generated`);
+ tip(`Names for ${cBurgs.length} burgs are regenerated`, false, "success");
}
function cultureRemove() {
@@ -306,12 +351,12 @@ function editCultures() {
Remove: function() {
cults.select("#culture"+culture).remove();
debug.select("#cultureCenter"+culture).remove();
-
+
pack.burgs.filter(b => b.culture == culture).forEach(b => b.culture = 0);
pack.states.forEach((s, i) => {if(s.culture === culture) s.culture = 0;});
pack.cells.culture.forEach((c, i) => {if(c === culture) pack.cells.culture[i] = 0;});
pack.cultures[culture].removed = true;
-
+
const origin = pack.cultures[culture].origin;
pack.cultures.forEach(c => {if(c.origin === culture) c.origin = origin;});
refreshCulturesEditor();
@@ -492,9 +537,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";
+ culturesHeader.querySelector("div[data-sortby='base']").style.left = "13.6em";
culturesFooter.style.display = "none";
- culturesHeader.querySelector("div[data-sortby='base']").style.marginLeft = "21px";
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"}});
@@ -588,9 +633,9 @@ 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";
+ culturesHeader.querySelector("div[data-sortby='base']").style.left = "35.8em";
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");
if(!close) $("#culturesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
@@ -634,7 +679,7 @@ function editCultures() {
function downloadCulturesData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
- let data = "Id,Culture,Color,Cells,Expansionism,Type,Area "+unit+",Population,Namesbase\n"; // headers
+ let data = "Id,Culture,Color,Cells,Expansionism,Type,Area "+unit+",Population,Namesbase,Emblems Shape\n"; // headers
body.querySelectorAll(":scope > div").forEach(function(el) {
data += el.dataset.id + ",";
@@ -646,7 +691,8 @@ function editCultures() {
data += el.dataset.area + ",";
data += el.dataset.population + ",";
const base = +el.dataset.base;
- data += nameBases[base].name + "\n";
+ data += nameBases[base].name + ",";
+ data += el.dataset.emblems + "\n";
});
const name = getFileName("Cultures") + ".csv";
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}