mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-23 12:31:24 +01:00
merge completed... now to fix all the bugs...
This commit is contained in:
commit
87c4d80fbc
3472 changed files with 466748 additions and 6517 deletions
15
.eslintrc.js
Normal file
15
.eslintrc.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
'airbnb-base',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 13,
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
},
|
||||
};
|
||||
9
.idea/Fantasy-Map-Generator.iml
generated
Normal file
9
.idea/Fantasy-Map-Generator.iml
generated
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Fantasy-Map-Generator.iml" filepath="$PROJECT_DIR$/.idea/Fantasy-Map-Generator.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
4
.idea/shelf/For_Other_branch.xml
generated
Normal file
4
.idea/shelf/For_Other_branch.xml
generated
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<changelist name="For_Other_branch" date="1639061594618" recycled="false">
|
||||
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/For_Other_branch/shelved.patch" />
|
||||
<option name="DESCRIPTION" value="For Other branch" />
|
||||
</changelist>
|
||||
70
.idea/shelf/For_Other_branch/shelved.patch
generated
Normal file
70
.idea/shelf/For_Other_branch/shelved.patch
generated
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
Index: _developmentREADME.md
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
--- _developmentREADME.md (date 1639061423175)
|
||||
+++ _developmentREADME.md (date 1639061423175)
|
||||
@@ -0,0 +1,62 @@
|
||||
+# How to setup development for project
|
||||
+
|
||||
+## Useful Commands
|
||||
+
|
||||
+```
|
||||
+# Run using python on port 8000
|
||||
+python -m http.server 8000
|
||||
+
|
||||
+#Check Network Ports if you having issues
|
||||
+netstat -ano | grep LISTEN
|
||||
+
|
||||
+```
|
||||
+
|
||||
+## Useful starting points
|
||||
+
|
||||
+- Tool uses the d3.js library to visualize, navigate and map.
|
||||
+- cells[i] is your friend it has all the data.
|
||||
+
|
||||
+
|
||||
+
|
||||
+
|
||||
+## Open Tasks
|
||||
+
|
||||
+
|
||||
+[ - ] Merge main back to dev-economics branch
|
||||
+
|
||||
+[ ] make cells a typescript class so we get code completion.
|
||||
+
|
||||
+[ ] Finish economics
|
||||
+
|
||||
+## Docker Host
|
||||
+
|
||||
+
|
||||
+ [ ] Add from command history
|
||||
+
|
||||
+
|
||||
+
|
||||
+### Networking
|
||||
+
|
||||
+
|
||||
+
|
||||
+ [ ]
|
||||
+
|
||||
+
|
||||
+##### TLS
|
||||
+
|
||||
+ [ x ] Use Kubernetes
|
||||
+
|
||||
+ [ ]
|
||||
+
|
||||
+
|
||||
+### Securing
|
||||
+
|
||||
+ [ x ] Use Kubernetes
|
||||
+
|
||||
+ [ ]
|
||||
+
|
||||
+
|
||||
+## Tools
|
||||
+
|
||||
+Intellij
|
||||
+
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
350
.idea/workspace.xml
generated
Normal file
350
.idea/workspace.xml
generated
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="BranchesTreeState">
|
||||
<expand>
|
||||
<path>
|
||||
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||
<item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||
</path>
|
||||
<path>
|
||||
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||
<item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||
</path>
|
||||
<path>
|
||||
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||
<item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||
<item name="GROUP_NODE:origin" type="e8cecc67:BranchNodeDescriptor" />
|
||||
</path>
|
||||
</expand>
|
||||
<select>
|
||||
<path>
|
||||
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||
<item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||
<item name="BRANCH:dev-economics-merge" type="e8cecc67:BranchNodeDescriptor" />
|
||||
</path>
|
||||
</select>
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="53f140fa-3e36-41e1-9b6f-3a4b888fc394" name="Default Changelist" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/dropbox.html" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/libs/umami.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/modules/cloud.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/modules/export.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/modules/markers-generator.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/modules/ui/hotkeys.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/modules/ui/markers-overview.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/arrayUtils.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/colorUtils.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/commonUtils.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/graphUtils.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/nodeUtils.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/numberUtils.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/polyfills.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/probabilityUtils.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/stringUtils.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/utils/unitUtils.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Readme.txt" beforeDir="false" afterPath="$PROJECT_DIR$/Readme.txt" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/fonts.css" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/images/preview.png" beforeDir="false" afterPath="$PROJECT_DIR$/images/preview.png" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/index.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/index.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/libs/jquery-ui.css" beforeDir="false" afterPath="$PROJECT_DIR$/libs/jquery-ui.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/libs/pell.js" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/main.js" beforeDir="false" afterPath="$PROJECT_DIR$/main.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/burgs-and-states.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/burgs-and-states.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/coa-renderer.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/coa-renderer.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/cultures-generator.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/cultures-generator.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/fonts.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/fonts.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/heightmap-generator.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/heightmap-generator.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/load.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/load.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/military-generator.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/military-generator.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/names-generator.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/names-generator.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/relief-icons.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/relief-icons.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/religions-generator.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/religions-generator.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/river-generator.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/river-generator.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/save.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/save.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/3d.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/3d.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/battle-screen.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/battle-screen.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/burg-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/burg-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/burgs-overview.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/burgs-overview.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/cultures-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/cultures-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/editors.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/editors.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/general.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/general.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/heightmap-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/heightmap-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/labels-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/labels-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/layers.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/layers.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/markers-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/markers-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/measurers.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/measurers.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/military-overview.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/military-overview.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/notes-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/notes-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/options.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/options.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/provinces-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/provinces-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/rivers-creator.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/rivers-creator.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/rivers-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/rivers-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/rivers-overview.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/rivers-overview.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/states-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/states-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/style.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/style.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/tools.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/tools.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/units-editor.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/units-editor.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/ui/world-configurator.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/ui/world-configurator.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/utils.js" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/modules/voronoi.js" beforeDir="false" afterPath="$PROJECT_DIR$/modules/voronoi.js" afterDir="false" />
|
||||
</list>
|
||||
<list id="1a815da3-372f-4aee-83db-d66764da8a8c" name="cap10bill-stuff" comment="cap10bill-stuff" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="CodeStyleSettingsInfer">
|
||||
<option name="done" value="true" />
|
||||
</component>
|
||||
<component name="ComposerSettings">
|
||||
<execution />
|
||||
</component>
|
||||
<component name="Git.Merge.Settings">
|
||||
<option name="BRANCH" value="master" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<favorite-branches>
|
||||
<branch-storage>
|
||||
<map>
|
||||
<entry type="REMOTE">
|
||||
<value>
|
||||
<list>
|
||||
<branch-info repo="$PROJECT_DIR$" source="origin/dev-economics" />
|
||||
<branch-info repo="$PROJECT_DIR$" source="origin/cap10bill" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</branch-storage>
|
||||
</favorite-branches>
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="dev-economics" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
<option name="UPDATE_TYPE" value="REBASE" />
|
||||
</component>
|
||||
<component name="GitSEFilterConfiguration">
|
||||
<file-type-list>
|
||||
<filtered-out-file-type name="LOCAL_BRANCH" />
|
||||
<filtered-out-file-type name="REMOTE_BRANCH" />
|
||||
<filtered-out-file-type name="TAG" />
|
||||
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
|
||||
</file-type-list>
|
||||
</component>
|
||||
<component name="GitToolBoxStore">
|
||||
<option name="projectConfigVersion" value="2" />
|
||||
<option name="recentBranches">
|
||||
<RecentBranches>
|
||||
<option name="branchesForRepo">
|
||||
<list>
|
||||
<RecentBranchesForRepo>
|
||||
<option name="branches">
|
||||
<list>
|
||||
<RecentBranch>
|
||||
<option name="branchName" value="cap10bill" />
|
||||
<option name="lastUsedInstant" value="1638217190" />
|
||||
</RecentBranch>
|
||||
<RecentBranch>
|
||||
<option name="branchName" value="master" />
|
||||
<option name="lastUsedInstant" value="1638217189" />
|
||||
</RecentBranch>
|
||||
</list>
|
||||
</option>
|
||||
<option name="repositoryRootUrl" value="file://$PROJECT_DIR$" />
|
||||
</RecentBranchesForRepo>
|
||||
</list>
|
||||
</option>
|
||||
</RecentBranches>
|
||||
</option>
|
||||
</component>
|
||||
<component name="KubernetesApiPersistence">
|
||||
<option name="context" value="gke_fhir-poc-330418_us-west2_aidbox-clin-10300" />
|
||||
<option name="namespace" value="default" />
|
||||
</component>
|
||||
<component name="MavenImportPreferences">
|
||||
<option name="generalSettings">
|
||||
<MavenGeneralSettings>
|
||||
<option name="mavenHome" value="$APPLICATION_HOME_DIR$/plugins/maven/lib/maven3" />
|
||||
</MavenGeneralSettings>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectCodeStyleSettingsMigration">
|
||||
<option name="version" value="1" />
|
||||
</component>
|
||||
<component name="ProjectId" id="20KLG9LLdl9Lkg5IYeUuWUNLYgk" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="abbreviatePackageNames" value="true" />
|
||||
<option name="autoscrollFromSource" value="true" />
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="aspect.path.notification.shown" value="true" />
|
||||
<property name="dart.analysis.tool.window.visible" value="false" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$/../../../../Program Files/KDiff3/kdiff3.exe" />
|
||||
<property name="nodejs_package_manager_path" value="npm" />
|
||||
<property name="settings.editor.selected.configurable" value="diff.external" />
|
||||
<property name="show.migrate.to.gradle.popup" value="false" />
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="53f140fa-3e36-41e1-9b6f-3a4b888fc394" name="Default Changelist" comment="" />
|
||||
<changelist id="1a815da3-372f-4aee-83db-d66764da8a8c" name="cap10bill-stuff" comment="" />
|
||||
<created>1635788235045</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1635788235045</updated>
|
||||
<workItem from="1635788236544" duration="16834000" />
|
||||
<workItem from="1636783758094" duration="4249000" />
|
||||
<workItem from="1637180980802" duration="114000" />
|
||||
<workItem from="1638216431637" duration="928000" />
|
||||
<workItem from="1638217433198" duration="16527000" />
|
||||
<workItem from="1638411446516" duration="5354000" />
|
||||
<workItem from="1638493341355" duration="906000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="For Other branch">
|
||||
<created>1639061594876</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1639061594876</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="2" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="dev-economics-merge" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_FILTERS">
|
||||
<map>
|
||||
<entry key="Branch">
|
||||
<value>
|
||||
<list>
|
||||
<RecentGroup>
|
||||
<option name="FILTER_VALUES">
|
||||
<option value="master" />
|
||||
</option>
|
||||
</RecentGroup>
|
||||
<RecentGroup>
|
||||
<option name="FILTER_VALUES">
|
||||
<option value="origin/dev-economics" />
|
||||
</option>
|
||||
</RecentGroup>
|
||||
<RecentGroup>
|
||||
<option name="FILTER_VALUES">
|
||||
<option value="origin/vite-migration" />
|
||||
</option>
|
||||
</RecentGroup>
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="oldMeFiltersMigrated" value="true" />
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="For Other branch" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="For Other branch" />
|
||||
</component>
|
||||
<component name="WindowStateProjectService">
|
||||
<state x="638" y="80" key="#com.intellij.ide.macro.MacrosDialog" timestamp="1639369375598">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="638" y="80" key="#com.intellij.ide.macro.MacrosDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639369375598" />
|
||||
<state x="574" y="185" width="667" height="706" key="#com.intellij.tools.ToolEditorDialog" timestamp="1639369607692">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="574" y="185" width="667" height="706" key="#com.intellij.tools.ToolEditorDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639369607692" />
|
||||
<state x="513" y="0" key="CommitChangelistDialog2" timestamp="1639061594894">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="513" y="0" key="CommitChangelistDialog2/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639061594894" />
|
||||
<state x="92" y="92" width="1736" height="856" key="DiffContextDialog" timestamp="1639368893441">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="92" y="92" width="1736" height="856" key="DiffContextDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639368893441" />
|
||||
<state x="687" y="215" key="FileChooserDialogImpl" timestamp="1639369646456">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="687" y="215" key="FileChooserDialogImpl/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639369646456" />
|
||||
<state x="52" y="100" width="1854" height="868" key="MergeDialog" timestamp="1639367643275">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="52" y="100" width="1854" height="868" key="MergeDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639367643275" />
|
||||
<state x="499" y="220" key="MultipleFileMergeDialog" timestamp="1639377574037">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="499" y="220" key="MultipleFileMergeDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639377574037" />
|
||||
<state x="589" y="149" key="RollbackChangesDialog" timestamp="1638860880630">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="589" y="149" key="RollbackChangesDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1638860880630" />
|
||||
<state x="260" y="0" key="SettingsEditor" timestamp="1639372442707">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="260" y="0" key="SettingsEditor/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639372442707" />
|
||||
<state x="92" y="92" width="1736" height="856" key="ShowDiffWithBranchDialog" timestamp="1639368289613">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="92" y="92" width="1736" height="856" key="ShowDiffWithBranchDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639368289613" />
|
||||
<state x="787" y="376" width="374" height="287" key="VCS.EditChangelistDialog" timestamp="1639061484548">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="787" y="376" width="374" height="287" key="VCS.EditChangelistDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639061484548" />
|
||||
<state x="598" y="189" key="VcsDiffUtil.ChangesDialog" timestamp="1639368226337">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="598" y="189" key="VcsDiffUtil.ChangesDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639368226337" />
|
||||
<state x="623" y="395" key="com.intellij.openapi.vcs.update.UpdateOrStatusOptionsDialogupdate-v2" timestamp="1639369945245">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="623" y="395" key="com.intellij.openapi.vcs.update.UpdateOrStatusOptionsDialogupdate-v2/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1639369945245" />
|
||||
<state x="613" y="160" key="git4idea.branch.GitSmartOperationDialog" timestamp="1638860788027">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="613" y="160" key="git4idea.branch.GitSmartOperationDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1638860788027" />
|
||||
<state x="735" y="414" key="git4idea.rebase.GitUnstructuredEditor" timestamp="1638865361273">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="735" y="414" key="git4idea.rebase.GitUnstructuredEditor/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1638865361273" />
|
||||
<state x="673" y="389" key="git4idea.ui.GitResetDialog" timestamp="1638860917078">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="673" y="389" key="git4idea.ui.GitResetDialog/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1638860917078" />
|
||||
<state x="539" y="5" width="840" height="1034" key="search.everywhere.popup" timestamp="1638860833149">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</state>
|
||||
<state x="539" y="5" width="840" height="1034" key="search.everywhere.popup/0.0.1920.1040/-1920.0.1920.1040@0.0.1920.1040" timestamp="1638860833149" />
|
||||
</component>
|
||||
</project>
|
||||
16
README.md
16
README.md
|
|
@ -1,10 +1,10 @@
|
|||
# Fantasy Map Generator
|
||||
|
||||
Azgaar's _Fantasy Map Generator_ is a free client-side web application generating interactive and highly customizable svg maps based on voronoi diagram.
|
||||
Azgaar's _Fantasy Map Generator_ is a free web application generating interactive and highly customizable svg maps based on voronoi diagram.
|
||||
|
||||
Project is under development, the current version is available on [Github Pages](https://azgaar.github.io/Fantasy-Map-Generator).
|
||||
|
||||
Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for a guidance. Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com), you may also keep an eye on my [Trello devboard](https://trello.com/b/7x832DG4/fantasy-map-generator).
|
||||
Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for guidance. The current progress is tracked in [Trello](https://trello.com/b/7x832DG4/fantasy-map-generator). Some details are covered in my old blog [_Fantasy Maps for fun and glory_](https://azgaar.wordpress.com).
|
||||
|
||||
[](https://i.redd.it/8bf81ir2cy631.png)
|
||||
|
||||
|
|
@ -12,18 +12,20 @@ Refer to the [project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki
|
|||
|
||||
[](https://cdn.discordapp.com/attachments/515359096925454350/593891237984206848/The_Wichin_Island_-_diplomacy.png)
|
||||
|
||||
Join our [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) and [Discord server](https://discordapp.com/invite/X7E84HU) to share the created maps, discuss the Generator, suggest ideas and get a most recent updates. You may also contact me directly via [email](mailto:azgaar.fmg@yandex.by). For bug reports please use the project [issues page](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or Discord "Bugs" channel. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips).
|
||||
Join our [Discord server](https://discordapp.com/invite/X7E84HU) and [Reddit community](https://www.reddit.com/r/FantasyMapGenerator) to share your creations, discuss the Generator, suggest ideas and get the most recent updates.
|
||||
|
||||
Contact me via [email](mailto:azgaar.fmg@yandex.by) if you have non-public suggestions. For bug reports please use [GitHub issues](https://github.com/Azgaar/Fantasy-Map-Generator/issues) or _#bugs_ channel on Discord. If you are facing performance issues, please read [the tips](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Tips#performance-tips).
|
||||
|
||||
Electron desktop application is available in [releases](https://github.com/Azgaar/Fantasy-Map-Generator/releases). Download archive for your architecture, unzip and run.
|
||||
|
||||
Pull requests are welcomed. The Tool codebase is messy and requires re-design, but I will appreciate if you start with minor changes.
|
||||
Pull requests are highly welcomed. The codebase is messy and requires re-design, but I will appreciate if you start with minor changes. Check out the [data model](https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Data-model) before contributing.
|
||||
|
||||
You can support the project on [Patreon](https://www.patreon.com/azgaar).
|
||||
|
||||
_Inspiration:_
|
||||
|
||||
* Martin O'Leary's [_Generating fantasy maps_](https://mewo2.com/notes/terrain)
|
||||
- Martin O'Leary's [_Generating fantasy maps_](https://mewo2.com/notes/terrain)
|
||||
|
||||
* Amit Patel's [_Polygonal Map Generation for Games_](http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation)
|
||||
- Amit Patel's [_Polygonal Map Generation for Games_](http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation)
|
||||
|
||||
* Scott Turner's [_Here Dragons Abound_](https://heredragonsabound.blogspot.com)
|
||||
- Scott Turner's [_Here Dragons Abound_](https://heredragonsabound.blogspot.com)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
Azgaar's Fantasy Map Generator
|
||||
This is an open-source software available under MIT license
|
||||
|
||||
Developed by Azgaar (azgaar.fmg@yandex.com) and contributors
|
||||
|
||||
Minsk, 2017-2021. MIT License
|
||||
|
||||
https://github.com/Azgaar/Fantasy-Map-Generator
|
||||
|
||||
To run the tool unzip ALL files and open index.html in browser
|
||||
BIN
_maps/.DS_Store
vendored
Normal file
BIN
_maps/.DS_Store
vendored
Normal file
Binary file not shown.
49
dropbox.html
Normal file
49
dropbox.html
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script type="text/javascript" src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
|
||||
<title>FMG Dropbox Auth</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
/*
|
||||
open this page in a new window without query parameter to start auth
|
||||
window.opener.setDropBoxToken(token) will be called on the opener
|
||||
window.
|
||||
*/
|
||||
const REDIRECT_URI = window.location.origin + window.location.pathname;
|
||||
const dbxAuth = new Dropbox.DropboxAuth({clientId: "pdr9ae64ip0qno4"});
|
||||
|
||||
const spObj = new URLSearchParams(window.location.search);
|
||||
const searchParams = Object.fromEntries(spObj.entries());
|
||||
|
||||
if (searchParams.code) getToken();
|
||||
else doAuth(); // start authentication
|
||||
|
||||
function doAuth() {
|
||||
dbxAuth
|
||||
.getAuthenticationUrl(REDIRECT_URI, undefined, "code", "offline", undefined, undefined, true)
|
||||
.then(authUrl => {
|
||||
window.sessionStorage.clear();
|
||||
window.sessionStorage.setItem("codeVerifier", dbxAuth.codeVerifier);
|
||||
window.location.href = authUrl;
|
||||
})
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
|
||||
function getToken() {
|
||||
dbxAuth.setCodeVerifier(window.sessionStorage.getItem("codeVerifier"));
|
||||
dbxAuth
|
||||
.getAccessTokenFromCode(REDIRECT_URI, searchParams.code)
|
||||
.then(resp => {
|
||||
const token = resp.result.access_token;
|
||||
window.opener.Cloud.providers.dropbox.setDropBoxToken(token);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
175
fonts.css
175
fonts.css
|
|
@ -1,175 +0,0 @@
|
|||
@font-face {
|
||||
font-family: 'Amatic SC';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Amatic SC Bold'), local('AmaticSC-Bold'), url(https://fonts.gstatic.com/s/amaticsc/v11/TUZ3zwprpvBS1izr_vOMscGKfrUC.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Architects Daughter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Architects Daughter Regular'), local('ArchitectsDaughter-Regular'), url(https://fonts.gstatic.com/s/architectsdaughter/v8/RXTgOOQ9AAtaVOHxx0IUBM3t7GjCYufj5TXV5VnA2p8.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Bitter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Bitter Regular'), local('Bitter-Regular'), url(https://fonts.gstatic.com/s/bitter/v12/zfs6I-5mjWQ3nxqccMoL2A.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Caesar Dressing';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Caesar Dressing'), local('CaesarDressing-Regular'), url(https://fonts.gstatic.com/s/caesardressing/v6/yYLx0hLa3vawqtwdswbotmK4vrRHdrz7.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cinzel';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Cinzel Regular'), local('Cinzel-Regular'), url(https://fonts.gstatic.com/s/cinzel/v7/zOdksD_UUTk1LJF9z4tURA.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Comfortaa';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Comfortaa Bold'), local('Comfortaa-Bold'), url(https://fonts.gstatic.com/s/comfortaa/v12/fND5XPYKrF2tQDwwfWZJI-gdm0LZdjqr5-oayXSOefg.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Dancing Script';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Dancing Script Bold'), local('DancingScript-Bold'), url(https://fonts.gstatic.com/s/dancingscript/v9/KGBfwabt0ZRLA5W1ywjowUHdOuSHeh0r6jGTOGdAKHA.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fredericka the Great';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Fredericka the Great'), local('FrederickatheGreat'), url(https://fonts.gstatic.com/s/frederickathegreat/v6/9Bt33CxNwt7aOctW2xjbCstzwVKsIBVV--Sjxbc.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Gloria Hallelujah';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Gloria Hallelujah'), local('GloriaHallelujah'), url(https://fonts.gstatic.com/s/gloriahallelujah/v9/CA1k7SlXcY5kvI81M_R28cNDay8z-hHR7F16xrcXsJw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Great Vibes';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Great Vibes'), local('GreatVibes-Regular'), url(https://fonts.gstatic.com/s/greatvibes/v5/6q1c0ofG6NKsEhAc2eh-3Y4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'IM Fell English';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('IM FELL English Roman'), local('IM_FELL_English_Roman'), url(https://fonts.gstatic.com/s/imfellenglish/v7/xwIisCqGFi8pff-oa9uSVAkYLEKE0CJQa8tfZYc_plY.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Kaushan Script';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Kaushan Script'), local('KaushanScript-Regular'), url(https://fonts.gstatic.com/s/kaushanscript/v6/qx1LSqts-NtiKcLw4N03IEd0sm1ffa_JvZxsF_BEwQk.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MedievalSharp';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('MedievalSharp'), url(https://fonts.gstatic.com/s/medievalsharp/v9/EvOJzAlL3oU5AQl2mP5KdgptMqhwMg.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Metamorphous';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Metamorphous'), url(https://fonts.gstatic.com/s/metamorphous/v7/Wnz8HA03aAXcC39ZEX5y133EOyqs.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montez';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Montez Regular'), local('Montez-Regular'), url(https://fonts.gstatic.com/s/montez/v8/aq8el3-0osHIcFK6bXAPkw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Nova Script';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Nova Script Regular'), local('NovaScript-Regular'), url(https://fonts.gstatic.com/s/novascript/v10/7Au7p_IpkSWSTWaFWkumvlQKGFw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Orbitron';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Orbitron Regular'), local('Orbitron-Regular'), url(https://fonts.gstatic.com/s/orbitron/v9/HmnHiRzvcnQr8CjBje6GQvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Satisfy';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Satisfy Regular'), local('Satisfy-Regular'), url(https://fonts.gstatic.com/s/satisfy/v8/2OzALGYfHwQjkPYWELy-cw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Shadows Into Light';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Shadows Into Light'), local('ShadowsIntoLight'), url(https://fonts.gstatic.com/s/shadowsintolight/v7/clhLqOv7MXn459PTh0gXYFK2TSYBz0eNcHnp4YqE4Ts.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Uncial Antiqua';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Uncial Antiqua'), local('UncialAntiqua-Regular'), url(https://fonts.gstatic.com/s/uncialantiqua/v5/N0bM2S5WOex4OUbESzoESK-i-MfWQZQ.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Underdog';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Underdog'), local('Underdog-Regular'), url(https://fonts.gstatic.com/s/underdog/v6/CHygV-jCElj7diMroWSlWV8.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Yellowtail';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Yellowtail Regular'), local('Yellowtail-Regular'), url(https://fonts.gstatic.com/s/yellowtail/v8/GcIHC9QEwVkrA19LJU1qlPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 58 KiB |
341
index.css
341
index.css
File diff suppressed because one or more lines are too long
2514
index.css.orig
Normal file
2514
index.css.orig
Normal file
File diff suppressed because one or more lines are too long
567
index.html
567
index.html
File diff suppressed because it is too large
Load diff
4752
index.html.orig
Normal file
4752
index.html.orig
Normal file
File diff suppressed because one or more lines are too long
530
libs/jquery-ui.css
vendored
530
libs/jquery-ui.css
vendored
|
|
@ -3,81 +3,79 @@
|
|||
* Copyright jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
.ui-draggable-handle {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.ui-helper-hidden {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
.ui-helper-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
.ui-helper-reset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
}
|
||||
.ui-helper-clearfix:before,
|
||||
.ui-helper-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.ui-helper-clearfix:after {
|
||||
clear: both;
|
||||
clear: both;
|
||||
}
|
||||
.ui-helper-zfix {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
filter:Alpha(Opacity=0); /* support: IE8 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
filter: Alpha(Opacity=0); /* support: IE8 */
|
||||
}
|
||||
|
||||
.ui-front {
|
||||
z-index: 100;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-disabled {
|
||||
cursor: default !important;
|
||||
pointer-events: none;
|
||||
cursor: default !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
.ui-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: -.25em;
|
||||
position: relative;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: -0.25em;
|
||||
position: relative;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ui-widget-icon-block {
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
display: block;
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Misc visuals
|
||||
|
|
@ -85,102 +83,102 @@
|
|||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
.ui-resizable-handle {
|
||||
position: absolute;
|
||||
font-size: 0.1px;
|
||||
display: block;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
position: absolute;
|
||||
font-size: 0.1px;
|
||||
display: block;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-resizable-disabled .ui-resizable-handle,
|
||||
.ui-resizable-autohide .ui-resizable-handle {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
.ui-resizable-n {
|
||||
cursor: n-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
cursor: n-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
top: -5px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-resizable-s {
|
||||
cursor: s-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
cursor: s-resize;
|
||||
height: 7px;
|
||||
width: 100%;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-resizable-e {
|
||||
cursor: e-resize;
|
||||
width: 7px;
|
||||
right: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
cursor: e-resize;
|
||||
width: 7px;
|
||||
right: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable-w {
|
||||
cursor: w-resize;
|
||||
width: 7px;
|
||||
left: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
cursor: w-resize;
|
||||
width: 7px;
|
||||
left: -5px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-resizable-se {
|
||||
cursor: se-resize;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
right: 1px;
|
||||
bottom: 1px;
|
||||
cursor: se-resize;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
right: 1px;
|
||||
bottom: 1px;
|
||||
}
|
||||
.ui-resizable-sw {
|
||||
cursor: sw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
bottom: -5px;
|
||||
cursor: sw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
bottom: -5px;
|
||||
}
|
||||
.ui-resizable-nw {
|
||||
cursor: nw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
top: -5px;
|
||||
cursor: nw-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
left: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
.ui-resizable-ne {
|
||||
cursor: ne-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
cursor: ne-resize;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
.ui-sortable-handle {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.ui-button {
|
||||
padding: .4em 1em;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
margin-right: .1em;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
padding: 0.4em 1em;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
margin-right: 0.1em;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
/* Support: IE <= 11 */
|
||||
overflow: visible;
|
||||
/* Support: IE <= 11 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ui-button,
|
||||
|
|
@ -188,128 +186,126 @@
|
|||
.ui-button:visited,
|
||||
.ui-button:hover,
|
||||
.ui-button:active {
|
||||
text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* to make room for the icon, a width needs to be set here */
|
||||
.ui-button-icon-only {
|
||||
width: 2em;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
width: 2em;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* button icon element(s) */
|
||||
.ui-button-icon-only .ui-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -8px;
|
||||
margin-left: -8px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -8px;
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.ui-button.ui-icon-notext .ui-icon {
|
||||
padding: 0;
|
||||
width: 2.1em;
|
||||
height: 2.1em;
|
||||
text-indent: -9999px;
|
||||
white-space: nowrap;
|
||||
|
||||
padding: 0;
|
||||
width: 2.1em;
|
||||
height: 2.1em;
|
||||
text-indent: -9999px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
input.ui-button.ui-icon-notext .ui-icon {
|
||||
width: auto;
|
||||
height: auto;
|
||||
text-indent: 0;
|
||||
white-space: normal;
|
||||
padding: .4em 1em;
|
||||
width: auto;
|
||||
height: auto;
|
||||
text-indent: 0;
|
||||
white-space: normal;
|
||||
padding: 0.4em 1em;
|
||||
}
|
||||
|
||||
/* workarounds */
|
||||
/* Support: Firefox 5 - 40 */
|
||||
input.ui-button::-moz-focus-inner,
|
||||
button.ui-button::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.ui-controlgroup {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
.ui-controlgroup > .ui-controlgroup-item {
|
||||
float: left;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
float: left;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
.ui-controlgroup > .ui-controlgroup-item:focus,
|
||||
.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus {
|
||||
z-index: 9999;
|
||||
z-index: 9999;
|
||||
}
|
||||
.ui-controlgroup-vertical > .ui-controlgroup-item {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
display: block;
|
||||
float: none;
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
}
|
||||
.ui-controlgroup-vertical .ui-controlgroup-item {
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ui-controlgroup .ui-controlgroup-label {
|
||||
padding: .4em 1em;
|
||||
padding: 0.4em 1em;
|
||||
}
|
||||
.ui-controlgroup .ui-controlgroup-label span {
|
||||
font-size: 80%;
|
||||
font-size: 80%;
|
||||
}
|
||||
.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item {
|
||||
border-left: none;
|
||||
border-left: none;
|
||||
}
|
||||
.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item {
|
||||
border-top: none;
|
||||
border-top: none;
|
||||
}
|
||||
.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content {
|
||||
border-right: none;
|
||||
border-right: none;
|
||||
}
|
||||
.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content {
|
||||
border-bottom: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Spinner specific style fixes */
|
||||
.ui-controlgroup-vertical .ui-spinner-input {
|
||||
|
||||
/* Support: IE8 only, Android < 4.4 only */
|
||||
width: 75%;
|
||||
width: calc( 100% - 2.4em );
|
||||
/* Support: IE8 only, Android < 4.4 only */
|
||||
width: 75%;
|
||||
width: calc(100% - 2.4em);
|
||||
}
|
||||
.ui-controlgroup-vertical .ui-spinner .ui-spinner-up {
|
||||
border-top-style: solid;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.ui-checkboxradio-label .ui-icon-background {
|
||||
box-shadow: inset 1px 1px 1px #ccc;
|
||||
border-radius: .12em;
|
||||
border: none;
|
||||
box-shadow: inset 1px 1px 1px #ccc;
|
||||
border-radius: 0.12em;
|
||||
border: none;
|
||||
}
|
||||
.ui-checkboxradio-radio-label .ui-icon-background {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 1em;
|
||||
overflow: visible;
|
||||
border: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 1em;
|
||||
overflow: visible;
|
||||
border: none;
|
||||
}
|
||||
.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,
|
||||
.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon {
|
||||
background-image: none;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
background-image: none;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
}
|
||||
.ui-checkboxradio-disabled {
|
||||
pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
body .ui-dialog {
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
outline: 0;
|
||||
|
|
@ -317,30 +313,30 @@ body .ui-dialog {
|
|||
background-color: inherit;
|
||||
}
|
||||
.ui-dialog .ui-dialog-titlebar {
|
||||
padding: .4em 1em;
|
||||
position: relative;
|
||||
font-size: 1.2em;
|
||||
padding: 0.4em 1em;
|
||||
position: relative;
|
||||
font-size: 1.2em;
|
||||
min-width: 150px;
|
||||
}
|
||||
.ui-dialog .ui-dialog-title {
|
||||
float: left;
|
||||
margin: .1em 0;
|
||||
white-space: nowrap;
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
float: left;
|
||||
margin: 0.1em 0;
|
||||
white-space: nowrap;
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-titlebar button {
|
||||
position: absolute;
|
||||
right: .5em;
|
||||
.ui-dialog .ui-dialog-titlebar button {
|
||||
position: absolute;
|
||||
right: 0.5em;
|
||||
top: 53%;
|
||||
padding: 0;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
color: #ffffff;
|
||||
background: none;
|
||||
font-size: .75em;
|
||||
font-size: 0.75em;
|
||||
border: 1px solid #c5c5c5;
|
||||
}
|
||||
|
||||
|
|
@ -349,113 +345,107 @@ body .ui-dialog {
|
|||
}
|
||||
|
||||
.ui-dialog .ui-dialog-titlebar button.ui-dialog-titlebar-close {
|
||||
margin: -1em 0 0;
|
||||
margin: -1em 0 0;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-titlebar button:active {
|
||||
.ui-dialog .ui-dialog-titlebar button:active {
|
||||
border: 1px solid #5d4651;
|
||||
color: #5d4651;
|
||||
}
|
||||
|
||||
.ui-dialog .ui-dialog-content {
|
||||
position: relative;
|
||||
border: 0;
|
||||
padding: .5em 1em;
|
||||
background: none;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
border: 0;
|
||||
padding: 0.5em 1em;
|
||||
background: none;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane {
|
||||
text-align: left;
|
||||
border-width: 1px 0 0 0;
|
||||
background-image: none;
|
||||
margin-top: .5em;
|
||||
padding: .3em 1em .5em .4em;
|
||||
text-align: left;
|
||||
border-width: 1px 0 0 0;
|
||||
background-image: none;
|
||||
margin-top: 0.5em;
|
||||
padding: 0.3em 1em 0.5em 0.4em;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
|
||||
float: right;
|
||||
float: right;
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane button {
|
||||
margin: .5em .4em .5em 0;
|
||||
cursor: pointer;
|
||||
margin: 0.5em 0.4em 0.5em 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ui-dialog .ui-resizable-n {
|
||||
height: 2px;
|
||||
top: 0;
|
||||
height: 2px;
|
||||
top: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-e {
|
||||
width: 2px;
|
||||
right: 0;
|
||||
width: 2px;
|
||||
right: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-s {
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-w {
|
||||
width: 2px;
|
||||
left: 0;
|
||||
width: 2px;
|
||||
left: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-se,
|
||||
.ui-dialog .ui-resizable-sw,
|
||||
.ui-dialog .ui-resizable-ne,
|
||||
.ui-dialog .ui-resizable-nw {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
.ui-dialog .ui-resizable-se {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-sw {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-ne {
|
||||
right: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
.ui-dialog .ui-resizable-nw {
|
||||
left: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.ui-draggable .ui-dialog-titlebar {
|
||||
cursor: move;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
/* Component containers
|
||||
----------------------------------*/
|
||||
.ui-widget {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.ui-widget input,
|
||||
.ui-widget select,
|
||||
.ui-widget textarea,
|
||||
.ui-widget button {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-size: 1em;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget button[class^="icon-"] {
|
||||
padding: 1px 6px;
|
||||
padding: 1px 6px;
|
||||
}
|
||||
.ui-widget.ui-widget-content {
|
||||
border: 1px solid #5e4fa2;
|
||||
border: 1px solid #5e4fa2;
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-content {
|
||||
border: 1px solid #dddddd;
|
||||
color: #333333;
|
||||
border: 1px solid #dddddd;
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-content a {
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-header {
|
||||
border-bottom: 1px solid #5d4651;
|
||||
background: #916e7f;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-header a {
|
||||
color: #333333;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* Interaction states
|
||||
|
|
@ -469,10 +459,10 @@ body .ui-dialog {
|
|||
works properly when clicked or hovered */
|
||||
html .ui-button.ui-state-disabled:hover,
|
||||
html .ui-button.ui-state-disabled:active {
|
||||
border: 1px solid #c5c5c5;
|
||||
background: #f6f6f6;
|
||||
font-weight: normal;
|
||||
color: #454545;
|
||||
border: 1px solid #c5c5c5;
|
||||
background: #f6f6f6;
|
||||
font-weight: normal;
|
||||
color: #454545;
|
||||
}
|
||||
.ui-state-default a,
|
||||
.ui-state-default a:link,
|
||||
|
|
@ -481,10 +471,10 @@ a.ui-button,
|
|||
a:link.ui-button,
|
||||
a:visited.ui-button,
|
||||
.ui-button {
|
||||
color: #454545;
|
||||
color: #454545;
|
||||
}
|
||||
.ui-button:active {
|
||||
color: #5d4651;
|
||||
color: #5d4651;
|
||||
border-color: #5d4651;
|
||||
}
|
||||
.ui-state-hover a,
|
||||
|
|
@ -497,12 +487,12 @@ a:visited.ui-button,
|
|||
.ui-state-focus a:visited,
|
||||
a.ui-button:hover,
|
||||
a.ui-button:focus {
|
||||
color: #2b2b2b;
|
||||
text-decoration: none;
|
||||
color: #2b2b2b;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ui-visual-focus {
|
||||
box-shadow: 0 0 3px 1px rgb(94, 158, 214);
|
||||
box-shadow: 0 0 3px 1px rgb(94, 158, 214);
|
||||
}
|
||||
|
||||
/* Interaction Cues
|
||||
|
|
@ -510,68 +500,68 @@ a.ui-button:focus {
|
|||
.ui-state-highlight,
|
||||
.ui-widget-content .ui-state-highlight,
|
||||
.ui-widget-header .ui-state-highlight {
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
color: #777620;
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-checked {
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
}
|
||||
.ui-state-highlight a,
|
||||
.ui-widget-content .ui-state-highlight a,
|
||||
.ui-widget-header .ui-state-highlight a {
|
||||
color: #777620;
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-error,
|
||||
.ui-widget-content .ui-state-error,
|
||||
.ui-widget-header .ui-state-error {
|
||||
border: 1px solid #f1a899;
|
||||
background: #fddfdf;
|
||||
color: #5f3f3f;
|
||||
border: 1px solid #f1a899;
|
||||
background: #fddfdf;
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error a,
|
||||
.ui-widget-content .ui-state-error a,
|
||||
.ui-widget-header .ui-state-error a {
|
||||
color: #5f3f3f;
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error-text,
|
||||
.ui-widget-content .ui-state-error-text,
|
||||
.ui-widget-header .ui-state-error-text {
|
||||
color: #5f3f3f;
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-priority-primary,
|
||||
.ui-widget-content .ui-priority-primary,
|
||||
.ui-widget-header .ui-priority-primary {
|
||||
font-weight: bold;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-priority-secondary,
|
||||
.ui-widget-content .ui-priority-secondary,
|
||||
.ui-widget-header .ui-priority-secondary {
|
||||
opacity: .7;
|
||||
filter:Alpha(Opacity=70); /* support: IE8 */
|
||||
font-weight: normal;
|
||||
opacity: 0.7;
|
||||
filter: Alpha(Opacity=70); /* support: IE8 */
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-state-disabled,
|
||||
.ui-widget-content .ui-state-disabled,
|
||||
.ui-widget-header .ui-state-disabled {
|
||||
opacity: .35;
|
||||
filter:Alpha(Opacity=35); /* support: IE8 */
|
||||
background-image: none;
|
||||
opacity: 0.35;
|
||||
filter: Alpha(Opacity=35); /* support: IE8 */
|
||||
background-image: none;
|
||||
}
|
||||
.ui-state-disabled .ui-icon {
|
||||
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
|
||||
filter: Alpha(Opacity=35); /* support: IE8 - See #6059 */
|
||||
}
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
background: #aaaaaa;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
background: #aaaaaa;
|
||||
opacity: 0.3;
|
||||
filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
}
|
||||
.ui-widget-shadow {
|
||||
-webkit-box-shadow: 0px 0px 5px #666666;
|
||||
box-shadow: 0px 0px 5px #666666;
|
||||
-webkit-box-shadow: 0px 0px 5px #666666;
|
||||
box-shadow: 0px 0px 5px #666666;
|
||||
}
|
||||
|
|
|
|||
157
libs/pell.js
157
libs/pell.js
|
|
@ -1,157 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
window.Pell = (function () {
|
||||
const defaultParagraphSeparatorString = "defaultParagraphSeparator";
|
||||
const formatBlock = "formatBlock";
|
||||
const addEventListener = (parent, type, listener) => parent.addEventListener(type, listener);
|
||||
const appendChild = (parent, child) => parent.appendChild(child);
|
||||
const createElement = tag => document.createElement(tag);
|
||||
const queryCommandState = command => document.queryCommandState(command);
|
||||
const queryCommandValue = command => document.queryCommandValue(command);
|
||||
const exec = (command, value = null) => document.execCommand(command, false, value);
|
||||
|
||||
const defaultActions = {
|
||||
bold: {
|
||||
icon: "<b>B</b>",
|
||||
title: "Bold",
|
||||
state: () => queryCommandState("bold"),
|
||||
result: () => exec("bold")
|
||||
},
|
||||
italic: {
|
||||
icon: "<i>I</i>",
|
||||
title: "Italic",
|
||||
state: () => queryCommandState("italic"),
|
||||
result: () => exec("italic")
|
||||
},
|
||||
underline: {
|
||||
icon: "<u>U</u>",
|
||||
title: "Underline",
|
||||
state: () => queryCommandState("underline"),
|
||||
result: () => exec("underline")
|
||||
},
|
||||
strikethrough: {
|
||||
icon: "<strike>S</strike>",
|
||||
title: "Strike-through",
|
||||
state: () => queryCommandState("strikeThrough"),
|
||||
result: () => exec("strikeThrough")
|
||||
},
|
||||
heading1: {
|
||||
icon: "<b>H<sub>1</sub></b>",
|
||||
title: "Heading 1",
|
||||
result: () => exec(formatBlock, "<h1>")
|
||||
},
|
||||
heading2: {
|
||||
icon: "<b>H<sub>2</sub></b>",
|
||||
title: "Heading 2",
|
||||
result: () => exec(formatBlock, "<h2>")
|
||||
},
|
||||
paragraph: {
|
||||
icon: "¶",
|
||||
title: "Paragraph",
|
||||
result: () => exec(formatBlock, "<p>")
|
||||
},
|
||||
quote: {
|
||||
icon: "“ ”",
|
||||
title: "Quote",
|
||||
result: () => exec(formatBlock, "<blockquote>")
|
||||
},
|
||||
olist: {
|
||||
icon: "#",
|
||||
title: "Ordered List",
|
||||
result: () => exec("insertOrderedList")
|
||||
},
|
||||
ulist: {
|
||||
icon: "•",
|
||||
title: "Unordered List",
|
||||
result: () => exec("insertUnorderedList")
|
||||
},
|
||||
code: {
|
||||
icon: "</>",
|
||||
title: "Code",
|
||||
result: () => exec(formatBlock, "<pre>")
|
||||
},
|
||||
line: {
|
||||
icon: "―",
|
||||
title: "Horizontal Line",
|
||||
result: () => exec("insertHorizontalRule")
|
||||
},
|
||||
link: {
|
||||
icon: "🔗",
|
||||
title: "Link",
|
||||
result: () => navigator.clipboard.readText().then(url => exec("createLink", url))
|
||||
},
|
||||
image: {
|
||||
icon: "📷",
|
||||
title: "Image",
|
||||
result: () => {
|
||||
navigator.clipboard.readText().then(url => exec("insertImage", url));
|
||||
exec("enableObjectResizing");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const defaultClasses = {
|
||||
actionbar: "pell-actionbar",
|
||||
button: "pell-button",
|
||||
content: "pell-content",
|
||||
selected: "pell-button-selected"
|
||||
};
|
||||
|
||||
const init = settings => {
|
||||
const actions = settings.actions
|
||||
? settings.actions.map(action => {
|
||||
if (typeof action === "string") return defaultActions[action];
|
||||
else if (defaultActions[action.name]) return {...defaultActions[action.name], ...action};
|
||||
return action;
|
||||
})
|
||||
: Object.keys(defaultActions).map(action => defaultActions[action]);
|
||||
|
||||
const classes = {...defaultClasses, ...settings.classes};
|
||||
|
||||
const defaultParagraphSeparator = settings[defaultParagraphSeparatorString] || "div";
|
||||
|
||||
const actionbar = createElement("div");
|
||||
actionbar.className = classes.actionbar;
|
||||
appendChild(settings.element, actionbar);
|
||||
|
||||
const content = (settings.element.content = createElement("div"));
|
||||
content.contentEditable = true;
|
||||
content.className = classes.content;
|
||||
content.oninput = ({target: {firstChild}}) => {
|
||||
if (firstChild && firstChild.nodeType === 3) exec(formatBlock, `<${defaultParagraphSeparator}>`);
|
||||
else if (content.innerHTML === "<br>") content.innerHTML = "";
|
||||
settings.onChange(content.innerHTML);
|
||||
};
|
||||
content.onkeydown = event => {
|
||||
if (event.key === "Enter" && queryCommandValue(formatBlock) === "blockquote") {
|
||||
setTimeout(() => exec(formatBlock, `<${defaultParagraphSeparator}>`), 0);
|
||||
}
|
||||
};
|
||||
appendChild(settings.element, content);
|
||||
|
||||
actions.forEach(action => {
|
||||
const button = createElement("button");
|
||||
button.className = classes.button;
|
||||
button.innerHTML = action.icon;
|
||||
button.title = action.title;
|
||||
button.setAttribute("type", "button");
|
||||
button.onclick = () => action.result() && content.focus();
|
||||
|
||||
if (action.state) {
|
||||
const handler = () => button.classList[action.state() ? "add" : "remove"](classes.selected);
|
||||
addEventListener(content, "keyup", handler);
|
||||
addEventListener(content, "mouseup", handler);
|
||||
addEventListener(button, "click", handler);
|
||||
}
|
||||
|
||||
appendChild(actionbar, button);
|
||||
});
|
||||
|
||||
if (settings.styleWithCSS) exec("styleWithCSS");
|
||||
exec(defaultParagraphSeparatorString, defaultParagraphSeparator);
|
||||
|
||||
return settings.element;
|
||||
};
|
||||
|
||||
return {exec, init};
|
||||
})();
|
||||
36
libs/umami.js
Normal file
36
libs/umami.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
(window => {
|
||||
const noTrack = !location.hostname || window.localStorage.getItem("noTrack");
|
||||
|
||||
const {
|
||||
screen: {width, height},
|
||||
navigator: {language},
|
||||
location: {hostname, pathname, search},
|
||||
document: {referrer}
|
||||
} = window;
|
||||
|
||||
const website = "4f6fd0ae-646a-4946-a9da-7aad63284e48";
|
||||
const root = "https://fmg-stats.herokuapp.com";
|
||||
const screen = `${width}x${height}`;
|
||||
const url = `${pathname}${search}`;
|
||||
|
||||
const post = (url, data) => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.open("POST", url, true);
|
||||
req.setRequestHeader("Content-Type", "application/json");
|
||||
req.send(JSON.stringify(data));
|
||||
};
|
||||
|
||||
const collect = (type, params) => {
|
||||
if (noTrack) return;
|
||||
|
||||
const payload = {website, hostname, screen, language, cache: false};
|
||||
Object.keys(params).forEach(key => {
|
||||
payload[key] = params[key];
|
||||
});
|
||||
|
||||
post(`${root}/api/collect`, {type, payload});
|
||||
};
|
||||
|
||||
collect("pageview", {url, referrer});
|
||||
window.track = (event_type = "reach", event_value = "") => collect("event", {event_type, event_value, url});
|
||||
})(window);
|
||||
602
main.js
602
main.js
|
|
@ -2,11 +2,11 @@
|
|||
// https://github.com/Azgaar/Fantasy-Map-Generator
|
||||
|
||||
'use strict';
|
||||
const version = '1.652'; // generator version
|
||||
const version = '1.71'; // generator version
|
||||
document.title += ' v' + version;
|
||||
|
||||
// Logging constants
|
||||
const PRODUCTION = window.location.host;
|
||||
const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
|
||||
const DEBUG = localStorage.getItem('debug');
|
||||
const INFO = DEBUG || !PRODUCTION;
|
||||
const TIME = DEBUG || !PRODUCTION;
|
||||
|
|
@ -112,18 +112,17 @@ legend.on('mousemove', () => tip('Drag to change the position. Click to hide the
|
|||
// main data variables
|
||||
let grid = {}; // initial grapg based on jittered square grid and data
|
||||
let pack = {}; // packed graph and data
|
||||
let seed,
|
||||
mapId,
|
||||
mapHistory = [],
|
||||
elSelected,
|
||||
modules = {},
|
||||
notes = [];
|
||||
let seed;
|
||||
let mapId;
|
||||
let mapHistory = [];
|
||||
let elSelected;
|
||||
let modules = {};
|
||||
let notes = [];
|
||||
let rulers = new Rulers();
|
||||
let customization = 0; // 0 - no; 1 = heightmap draw; 2 - states draw; 3 - add state/burg; 4 - cultures draw
|
||||
let customization = 0;
|
||||
|
||||
let biomesData = applyDefaultBiomesSystem();
|
||||
let nameBases = Names.getNameBases(); // cultures-related data
|
||||
const fonts = ['Almendra+SC', 'Georgia', 'Arial', 'Times+New+Roman', 'Comic+Sans+MS', 'Lucida+Sans+Unicode', 'Courier+New', 'Verdana', 'Arial', 'Impact']; // default fonts
|
||||
|
||||
let color = d3.scaleSequential(d3.interpolateSpectral); // default color scheme
|
||||
const lineGen = d3.line().curve(d3.curveBasis); // d3 line generator with default curve interpolation
|
||||
|
|
@ -149,28 +148,36 @@ function zoomed() {
|
|||
const zoom = d3.zoom().scaleExtent([1, 20]).on('zoom', zoomed);
|
||||
|
||||
// default options
|
||||
let options = {pinNotes: false}; // options object
|
||||
let options = {
|
||||
pinNotes: false,
|
||||
showMFCGMap: true,
|
||||
winds: [225, 45, 225, 315, 135, 315],
|
||||
stateLabelsMode: "auto"
|
||||
};
|
||||
let mapCoordinates = {}; // map coordinates on globe
|
||||
options.winds = [225, 45, 225, 315, 135, 315]; // default wind directions
|
||||
|
||||
let populationRate = +document.getElementById('populationRateInput').value;
|
||||
let urbanization = +document.getElementById('urbanizationInput').value;
|
||||
let urbanDensity = +document.getElementById("urbanDensityInput").value;
|
||||
|
||||
applyStoredOptions();
|
||||
let graphWidth = +mapWidthInput.value,
|
||||
graphHeight = +mapHeightInput.value; // voronoi graph extention, cannot be changed arter generation
|
||||
let svgWidth = graphWidth,
|
||||
svgHeight = graphHeight; // svg canvas resolution, can be changed
|
||||
|
||||
// voronoi graph extention, cannot be changed arter generation
|
||||
let graphWidth = +mapWidthInput.value;
|
||||
let graphHeight = +mapHeightInput.value;
|
||||
landmass.append('rect').attr('x', 0).attr('y', 0).attr('width', graphWidth).attr('height', graphHeight);
|
||||
oceanPattern.append('rect').attr('fill', 'url(#oceanic)').attr('x', 0).attr('y', 0).attr('width', graphWidth).attr('height', graphHeight);
|
||||
oceanLayers.append('rect').attr('id', 'oceanBase').attr('x', 0).attr('y', 0).attr('width', graphWidth).attr('height', graphHeight);
|
||||
|
||||
void (function removeLoading() {
|
||||
// svg canvas resolution, can be changed
|
||||
let svgWidth = graphWidth;
|
||||
let svgHeight = graphHeight;
|
||||
|
||||
|
||||
// remove loading screen
|
||||
d3.select('#loading').transition().duration(4000).style('opacity', 0).remove();
|
||||
d3.select('#initial').transition().duration(4000).attr('opacity', 0).remove();
|
||||
d3.select('#optionsContainer').transition().duration(3000).style('opacity', 1);
|
||||
d3.select('#tooltip').transition().duration(4000).style('opacity', 1);
|
||||
})();
|
||||
|
||||
// decide which map should be loaded or generated on page load
|
||||
void (function checkLoadParameters() {
|
||||
|
|
@ -219,39 +226,8 @@ void (function checkLoadParameters() {
|
|||
generateMapOnLoad();
|
||||
})();
|
||||
|
||||
function loadMapFromURL(maplink, random) {
|
||||
const URL = decodeURIComponent(maplink);
|
||||
|
||||
fetch(URL, {method: 'GET', mode: 'cors'})
|
||||
.then((response) => {
|
||||
if (response.ok) return response.blob();
|
||||
throw new Error('Cannot load map from URL');
|
||||
})
|
||||
.then((blob) => uploadMap(blob))
|
||||
.catch((error) => {
|
||||
showUploadErrorMessage(error.message, URL, random);
|
||||
if (random) generateMapOnLoad();
|
||||
});
|
||||
}
|
||||
|
||||
function showUploadErrorMessage(error, URL, random) {
|
||||
ERROR && console.error(error);
|
||||
alertMessage.innerHTML = `Cannot load map from the ${link(URL, 'link provided')}.
|
||||
${random ? `A new random map is generated. ` : ''}
|
||||
Please ensure the linked file is reachable and CORS is allowed on server side`;
|
||||
$('#alert').dialog({
|
||||
title: 'Loading error',
|
||||
width: '32em',
|
||||
buttons: {
|
||||
OK: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generateMapOnLoad() {
|
||||
applyStyleOnLoad(); // apply default of previously selected style
|
||||
applyStyleOnLoad(); // apply default or previously selected style
|
||||
generate(); // generate map
|
||||
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
||||
applyPreset(); // apply saved layers preset
|
||||
|
|
@ -262,10 +238,11 @@ function focusOn() {
|
|||
const url = new URL(window.location.href);
|
||||
const params = url.searchParams;
|
||||
|
||||
if (params.get('from') === 'MFCG' && document.referrer) {
|
||||
if (params.get('seed').length === 13) {
|
||||
const fromMGCG = params.get("from") === "MFCG" && document.referrer;
|
||||
if (fromMGCG) {
|
||||
// show back burg from MFCG
|
||||
params.set('burg', params.get('seed').slice(-4));
|
||||
const burgSeed = params.get("seed").slice(-4);
|
||||
params.set("burg", burgSeed);
|
||||
} else {
|
||||
// select burg for MFCG
|
||||
findBurgForMFCG(params);
|
||||
|
|
@ -273,23 +250,33 @@ function focusOn() {
|
|||
}
|
||||
}
|
||||
|
||||
const s = +params.get('scale') || 8;
|
||||
let x = +params.get('x');
|
||||
let y = +params.get('y');
|
||||
const scaleParam = params.get("scale");
|
||||
const cellParam = params.get("cell");
|
||||
const burgParam = params.get("burg");
|
||||
|
||||
const c = +params.get('cell');
|
||||
if (c) {
|
||||
x = pack.cells.p[c][0];
|
||||
y = pack.cells.p[c][1];
|
||||
if (scaleParam || cellParam || burgParam) {
|
||||
const scale = +scaleParam || 8;
|
||||
|
||||
if (cellParam) {
|
||||
const cell = +params.get("cell");
|
||||
const [x, y] = pack.cells.p[cell];
|
||||
zoomTo(x, y, scale, 1600);
|
||||
return;
|
||||
}
|
||||
|
||||
if (burgParam) {
|
||||
const burg = isNaN(+burgParam) ? pack.burgs.find(burg => burg.name === burgParam) : pack.burgs[+burgParam];
|
||||
if (!burg) return;
|
||||
|
||||
const {x, y} = burg;
|
||||
zoomTo(x, y, scale, 1600);
|
||||
return;
|
||||
}
|
||||
|
||||
const x = +params.get("x") || graphWidth / 2;
|
||||
const y = +params.get("y") || graphHeight / 2;
|
||||
zoomTo(x, y, scale, 1600);
|
||||
}
|
||||
|
||||
const b = +params.get('burg');
|
||||
if (b && pack.burgs[b]) {
|
||||
x = pack.burgs[b].x;
|
||||
y = pack.burgs[b].y;
|
||||
}
|
||||
|
||||
if (x && y) zoomTo(x, y, s, 1600);
|
||||
}
|
||||
|
||||
// find burg for MFCG and focus on it
|
||||
|
|
@ -334,7 +321,6 @@ function findBurgForMFCG(params) {
|
|||
else if (p[0] === 'shantytown') b.shanty = +p[1];
|
||||
else b[p[0]] = +p[1]; // other parameters
|
||||
}
|
||||
b.MFCGlink = document.referrer; // set direct link to MFCG
|
||||
if (params.get('name') && params.get('name') != 'null') b.name = params.get('name');
|
||||
|
||||
const label = burgLabels.select("[data-id='" + burgId + "']");
|
||||
|
|
@ -421,13 +407,19 @@ function showWelcomeMessage() {
|
|||
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
|
||||
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
|
||||
<ul>Main changes:
|
||||
<li>Ability to add river selecting its cells</li>
|
||||
<li>Keep river course on edit</li>
|
||||
<li>Refactor river rendering code</li>
|
||||
<li>Ability to limit military units by biome, state, culture and religion</li>
|
||||
<li>New marker types</li>
|
||||
<li>New markers editor</li>
|
||||
<li>Markers overview screen</li>
|
||||
<li>Markers regeneration menu</li>
|
||||
<li>Burg editor update</li>
|
||||
<li>Editable theme color</li>
|
||||
<li>Add font dialog</li>
|
||||
<li>Save to Dropbox</li>
|
||||
</ul>
|
||||
|
||||
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||
<span>Thanks for all supporters on ${patreon}!</i></span>`;
|
||||
<span>Thanks for all supporters on <a href="https://www.patreon.com/azgaar" target="_blank">Patreon</a>!</i></span>`;
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
|
|
@ -479,10 +471,8 @@ function resetZoom(d = 1000) {
|
|||
svg.transition().duration(d).call(zoom.transform, d3.zoomIdentity);
|
||||
}
|
||||
|
||||
// calculate x,y extreme points of viewBox
|
||||
// calculate x y extreme points of viewBox
|
||||
function getViewBoxExtent() {
|
||||
// x = trX / scale * -1 + graphWidth / scale
|
||||
// y = trY / scale * -1 + graphHeight / scale
|
||||
return [
|
||||
[Math.abs(viewX / scale), Math.abs(viewY / scale)],
|
||||
[Math.abs(viewX / scale) + graphWidth / scale, Math.abs(viewY / scale) + graphHeight / scale]
|
||||
|
|
@ -536,19 +526,20 @@ function invokeActiveZooming() {
|
|||
}
|
||||
|
||||
// rescale map markers
|
||||
+markers.attr("rescale") &&
|
||||
|
||||
if (+markers.attr('rescale') && markers.style('display') !== 'none') {
|
||||
markers.selectAll('use').each(function () {
|
||||
const x = +this.dataset.x,
|
||||
y = +this.dataset.y,
|
||||
desired = +this.dataset.size;
|
||||
const size = Math.max(desired * 5 + 25 / scale, 1);
|
||||
d3.select(this)
|
||||
.attr('x', x - size / 2)
|
||||
.attr('y', y - size)
|
||||
.attr('width', size)
|
||||
.attr('height', size);
|
||||
pack.markers?.forEach(marker => {
|
||||
const {i, x, y, size = 30, hidden} = marker;
|
||||
const el = !hidden && document.getElementById(`marker${i}`);
|
||||
if (!el) return;
|
||||
|
||||
const zoomedSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
|
||||
el.setAttribute("width", zoomedSize);
|
||||
el.setAttribute("height", zoomedSize);
|
||||
el.setAttribute("x", rn(x - zoomedSize / 2, 1));
|
||||
el.setAttribute("y", rn(y - zoomedSize, 1));
|
||||
});
|
||||
}
|
||||
|
||||
// rescale rulers to have always the same size
|
||||
if (ruler.style('display') !== 'none') {
|
||||
|
|
@ -678,7 +669,7 @@ function generate() {
|
|||
Lakes.generateName();
|
||||
|
||||
Military.generate();
|
||||
addMarkers();
|
||||
Markers.generate();
|
||||
addZones();
|
||||
Names.getMapName();
|
||||
|
||||
|
|
@ -687,11 +678,12 @@ function generate() {
|
|||
INFO && console.groupEnd('Generated Map ' + seed);
|
||||
} catch (error) {
|
||||
ERROR && console.error(error);
|
||||
const parsedError = parseError(error);
|
||||
clearMainTip();
|
||||
|
||||
alertMessage.innerHTML = `An error is occured on map generation. Please retry.
|
||||
<br>If error is critical, clear the stored data and try again.
|
||||
<p id="errorBox">${parseError(error)}</p>`;
|
||||
<p id="errorBox">${parsedError}</p>`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Generation error',
|
||||
|
|
@ -702,7 +694,7 @@ function generate() {
|
|||
localStorage.setItem('version', version);
|
||||
},
|
||||
Regenerate: function () {
|
||||
regenerateMap();
|
||||
regenerateMap("generation error");
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Ignore: function () {
|
||||
|
|
@ -731,6 +723,7 @@ function generateSeed() {
|
|||
// Place points to calculate Voronoi diagram
|
||||
function placePoints() {
|
||||
TIME && console.time('placePoints');
|
||||
Math.random = aleaPRNG(seed); // reset PRNG
|
||||
|
||||
const cellsDesired = +pointsInput.dataset.cells;
|
||||
const spacing = (grid.spacing = rn(Math.sqrt((graphWidth * graphHeight) / cellsDesired), 2)); // spacing between points before jirrering
|
||||
|
|
@ -955,11 +948,11 @@ function calculateMapCoordinates() {
|
|||
const size = +document.getElementById('mapSizeOutput').value;
|
||||
const latShift = +document.getElementById('latitudeOutput').value;
|
||||
|
||||
const latT = (size / 100) * 180;
|
||||
const latN = 90 - ((180 - latT) * latShift) / 100;
|
||||
const latS = latN - latT;
|
||||
const latT = rn((size / 100) * 180, 1);
|
||||
const latN = rn(90 - ((180 - latT) * latShift) / 100, 1);
|
||||
const latS = rn(latN - latT, 1);
|
||||
|
||||
const lon = Math.min(((graphWidth / graphHeight) * latT) / 2, 180);
|
||||
const lon = rn(Math.min(((graphWidth / graphHeight) * latT) / 2, 180));
|
||||
mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon};
|
||||
}
|
||||
|
||||
|
|
@ -979,7 +972,7 @@ function calculateTemperatures() {
|
|||
const lat = Math.abs(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT); // [0; 90]
|
||||
const initTemp = tEq - int(lat / 90) * tDelta;
|
||||
for (let i = r; i < r + grid.cellsX; i++) {
|
||||
cells.temp[i] = Math.max(Math.min(initTemp - convertToFriendly(cells.h[i]), 127), -128);
|
||||
cells.temp[i] = minmax(initTemp - convertToFriendly(cells.h[i]), -128, 127);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -998,43 +991,45 @@ function calculateTemperatures() {
|
|||
function generatePrecipitation() {
|
||||
TIME && console.time('generatePrecipitation');
|
||||
prec.selectAll('*').remove();
|
||||
const cells = grid.cells;
|
||||
const {cells, cellsX, cellsY} = grid;
|
||||
cells.prec = new Uint8Array(cells.i.length); // precipitation array
|
||||
const modifier = precInput.value / 100; // user's input
|
||||
const cellsX = grid.cellsX,
|
||||
cellsY = grid.cellsY;
|
||||
let westerly = [],
|
||||
easterly = [],
|
||||
southerly = 0,
|
||||
northerly = 0;
|
||||
|
||||
{
|
||||
// latitude bands
|
||||
// x4 = 0-5 latitude: wet through the year (rising zone)
|
||||
// x2 = 5-20 latitude: wet summer (rising zone), dry winter (sinking zone)
|
||||
// x1 = 20-30 latitude: dry all year (sinking zone)
|
||||
// x2 = 30-50 latitude: wet winter (rising zone), dry summer (sinking zone)
|
||||
// x3 = 50-60 latitude: wet all year (rising zone)
|
||||
// x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone)
|
||||
// x1 = 70-90 latitude: dry all year (sinking zone)
|
||||
}
|
||||
const lalitudeModifier = [4, 2, 2, 2, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 0.5]; // by 5d step
|
||||
const westerly = [];
|
||||
const easterly = [];
|
||||
let southerly = 0;
|
||||
let northerly = 0;
|
||||
|
||||
// difine wind directions based on cells latitude and prevailing winds there
|
||||
// precipitation modifier per latitude band
|
||||
// x4 = 0-5 latitude: wet through the year (rising zone)
|
||||
// x2 = 5-20 latitude: wet summer (rising zone), dry winter (sinking zone)
|
||||
// x1 = 20-30 latitude: dry all year (sinking zone)
|
||||
// x2 = 30-50 latitude: wet winter (rising zone), dry summer (sinking zone)
|
||||
// x3 = 50-60 latitude: wet all year (rising zone)
|
||||
// x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone)
|
||||
// x1 = 70-85 latitude: dry all year (sinking zone)
|
||||
// x0.5 = 85-90 latitude: dry all year (sinking zone)
|
||||
const lalitudeModifier = [4, 2, 2, 2, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 0.5];
|
||||
const MAX_PASSABLE_ELEVATION = 85;
|
||||
|
||||
// define wind directions based on cells latitude and prevailing winds there
|
||||
d3.range(0, cells.i.length, cellsX).forEach(function (c, i) {
|
||||
const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT;
|
||||
const band = ((Math.abs(lat) - 1) / 5) | 0;
|
||||
const latMod = lalitudeModifier[band];
|
||||
const tier = (Math.abs(lat - 89) / 30) | 0; // 30d tiers from 0 to 5 from N to S
|
||||
if (options.winds[tier] > 40 && options.winds[tier] < 140) westerly.push([c, latMod, tier]);
|
||||
else if (options.winds[tier] > 220 && options.winds[tier] < 320) easterly.push([c + cellsX - 1, latMod, tier]);
|
||||
if (options.winds[tier] > 100 && options.winds[tier] < 260) northerly++;
|
||||
else if (options.winds[tier] > 280 || options.winds[tier] < 80) southerly++;
|
||||
const latBand = ((Math.abs(lat) - 1) / 5) | 0;
|
||||
const latMod = lalitudeModifier[latBand];
|
||||
const windTier = (Math.abs(lat - 89) / 30) | 0; // 30d tiers from 0 to 5 from N to S
|
||||
const {isWest, isEast, isNorth, isSouth} = getWindDirections(windTier);
|
||||
|
||||
if (isWest) westerly.push([c, latMod, windTier]);
|
||||
if (isEast) easterly.push([c + cellsX - 1, latMod, windTier]);
|
||||
if (isNorth) northerly++;
|
||||
if (isSouth) southerly++;
|
||||
});
|
||||
|
||||
// distribute winds by direction
|
||||
if (westerly.length) passWind(westerly, 120 * modifier, 1, cellsX);
|
||||
if (easterly.length) passWind(easterly, 120 * modifier, -1, cellsX);
|
||||
|
||||
const vertT = southerly + northerly;
|
||||
if (northerly) {
|
||||
const bandN = ((Math.abs(mapCoordinates.latN) - 1) / 5) | 0;
|
||||
|
|
@ -1042,6 +1037,7 @@ function generatePrecipitation() {
|
|||
const maxPrecN = (northerly / vertT) * 60 * modifier * latModN;
|
||||
passWind(d3.range(0, cellsX, 1), maxPrecN, cellsX, cellsY);
|
||||
}
|
||||
|
||||
if (southerly) {
|
||||
const bandS = ((Math.abs(mapCoordinates.latS) - 1) / 5) | 0;
|
||||
const latModS = mapCoordinates.latT > 60 ? d3.mean(lalitudeModifier) : lalitudeModifier[bandS];
|
||||
|
|
@ -1049,20 +1045,34 @@ function generatePrecipitation() {
|
|||
passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY);
|
||||
}
|
||||
|
||||
function getWindDirections(tier) {
|
||||
const angle = options.winds[tier];
|
||||
|
||||
const isWest = angle > 40 && angle < 140;
|
||||
const isEast = angle > 220 && angle < 320;
|
||||
const isNorth = angle > 100 && angle < 260;
|
||||
const isSouth = angle > 280 || angle < 80;
|
||||
|
||||
return {isWest, isEast, isNorth, isSouth};
|
||||
}
|
||||
|
||||
function passWind(source, maxPrec, next, steps) {
|
||||
const maxPrecInit = maxPrec;
|
||||
|
||||
for (let first of source) {
|
||||
if (first[0]) {
|
||||
maxPrec = Math.min(maxPrecInit * first[1], 255);
|
||||
first = first[0];
|
||||
}
|
||||
|
||||
let humidity = maxPrec - cells.h[first]; // initial water amount
|
||||
if (humidity <= 0) continue; // if first cell in row is too elevated cosdired wind dry
|
||||
|
||||
for (let s = 0, current = first; s < steps; s++, current += next) {
|
||||
// no flux on permafrost
|
||||
if (cells.temp[current] < -5) continue;
|
||||
// water cell
|
||||
if (cells.temp[current] < -5) continue; // no flux in permafrost
|
||||
|
||||
if (cells.h[current] < 20) {
|
||||
// water cell
|
||||
if (cells.h[current + next] >= 20) {
|
||||
cells.prec[current + next] += Math.max(humidity / rand(10, 20), 1); // coastal precipitation
|
||||
} else {
|
||||
|
|
@ -1073,20 +1083,20 @@ function generatePrecipitation() {
|
|||
}
|
||||
|
||||
// land cell
|
||||
const precipitation = getPrecipitation(humidity, current, next);
|
||||
const isPassable = cells.h[current + next] <= MAX_PASSABLE_ELEVATION;
|
||||
const precipitation = isPassable ? getPrecipitation(humidity, current, next) : humidity;
|
||||
cells.prec[current] += precipitation;
|
||||
const evaporation = precipitation > 1.5 ? 1 : 0; // some humidity evaporates back to the atmosphere
|
||||
humidity = Math.min(Math.max(humidity - precipitation + evaporation, 0), maxPrec);
|
||||
humidity = isPassable ? minmax(humidity - precipitation + evaporation, 0, maxPrec) : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPrecipitation(humidity, i, n) {
|
||||
if (cells.h[i + n] > 85) return humidity; // 85 is max passable height
|
||||
const normalLoss = Math.max(humidity / (10 * modifier), 1); // precipitation in normal conditions
|
||||
const diff = Math.max(cells.h[i + n] - cells.h[i], 0); // difference in height
|
||||
const mod = (cells.h[i + n] / 70) ** 2; // 50 stands for hills, 70 for mountains
|
||||
return Math.min(Math.max(normalLoss + diff * mod, 1), humidity);
|
||||
return minmax(normalLoss + diff * mod, 1, humidity);
|
||||
}
|
||||
|
||||
void (function drawWindDirection() {
|
||||
|
|
@ -1400,22 +1410,21 @@ function reMarkFeatures() {
|
|||
// assign biome id for each cell
|
||||
function defineBiomes() {
|
||||
TIME && console.time('defineBiomes');
|
||||
const cells = pack.cells,
|
||||
f = pack.features,
|
||||
temp = grid.cells.temp,
|
||||
prec = grid.cells.prec;
|
||||
const {cells} = pack;
|
||||
const {temp, prec} = grid.cells;
|
||||
cells.biome = new Uint8Array(cells.i.length); // biomes array
|
||||
|
||||
for (const i of cells.i) {
|
||||
const t = temp[cells.g[i]]; // cell temperature
|
||||
const h = cells.h[i]; // cell height
|
||||
const m = h < 20 ? 0 : calculateMoisture(i); // cell moisture
|
||||
cells.biome[i] = getBiomeId(m, t, h);
|
||||
const temperature = temp[cells.g[i]];
|
||||
const height = cells.h[i];
|
||||
const moisture = height < 20 ? 0 : calculateMoisture(i);
|
||||
cells.biome[i] = getBiomeId(moisture, temperature, height);
|
||||
}
|
||||
|
||||
function calculateMoisture(i) {
|
||||
let moist = prec[cells.g[i]];
|
||||
if (cells.r[i]) moist += Math.max(cells.fl[i] / 20, 2);
|
||||
|
||||
const n = cells.c[i]
|
||||
.filter(isLand)
|
||||
.map((c) => prec[cells.g[c]])
|
||||
|
|
@ -1428,12 +1437,13 @@ function defineBiomes() {
|
|||
|
||||
// assign biome id to a cell
|
||||
function getBiomeId(moisture, temperature, height) {
|
||||
if (temperature < -5) return 11; // permafrost biome, including sea ice
|
||||
if (height < 20) return 0; // marine biome: liquid water cells
|
||||
if (moisture > 40 && temperature > -2 && (height < 25 || (moisture > 24 && height > 24))) return 12; // wetland biome
|
||||
const m = Math.min((moisture / 5) | 0, 4); // moisture band from 0 to 4
|
||||
const t = Math.min(Math.max(20 - temperature, 0), 25); // temparature band from 0 to 25
|
||||
return biomesData.biomesMartix[m][t];
|
||||
if (height < 20) return 0; // marine biome: all water cells
|
||||
if (temperature < -5) return 11; // permafrost biome
|
||||
if (moisture > 40 && temperature > -2 && (height < 25 || (moisture > 24 && height > 24 && height < 60))) return 12; // wetland biome
|
||||
|
||||
const moistureBand = Math.min((moisture / 5) | 0, 4); // [0-4]
|
||||
const temperatureBand = Math.min(Math.max(20 - temperature, 0), 25); // [0-25]
|
||||
return biomesData.biomesMartix[moistureBand][temperatureBand];
|
||||
}
|
||||
|
||||
// assess cells suitability to calculate population and rang cells for culture center and burgs placement
|
||||
|
|
@ -1486,295 +1496,6 @@ function rankCells() {
|
|||
TIME && console.timeEnd('rankCells');
|
||||
}
|
||||
|
||||
// generate some markers
|
||||
function addMarkers(number = 1) {
|
||||
if (!number) return;
|
||||
TIME && console.time('addMarkers');
|
||||
const cells = pack.cells,
|
||||
states = pack.states;
|
||||
|
||||
void (function addVolcanoes() {
|
||||
let mounts = Array.from(cells.i)
|
||||
.filter((i) => cells.h[i] > 70)
|
||||
.sort((a, b) => cells.h[b] - cells.h[a]);
|
||||
let count = mounts.length < 10 ? 0 : Math.ceil((mounts.length / 300) * number);
|
||||
if (count) addMarker('volcano', '🌋', 52, 50, 13);
|
||||
|
||||
while (count && mounts.length) {
|
||||
const cell = mounts.splice(biased(0, mounts.length - 1, 5), 1);
|
||||
const x = cells.p[cell][0],
|
||||
y = cells.p[cell][1];
|
||||
const id = appendMarker(cell, 'volcano');
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const name = P(0.3) ? 'Mount ' + proper : Math.random() > 0.3 ? proper + ' Volcano' : proper;
|
||||
notes.push({id, name, legend: `Active volcano. Height: ${getFriendlyHeight([x, y])}`});
|
||||
count--;
|
||||
}
|
||||
})();
|
||||
|
||||
void (function addHotSprings() {
|
||||
let springs = Array.from(cells.i)
|
||||
.filter((i) => cells.h[i] > 50)
|
||||
.sort((a, b) => cells.h[b] - cells.h[a]);
|
||||
let count = springs.length < 30 ? 0 : Math.ceil((springs.length / 1000) * number);
|
||||
if (count) addMarker('hot_springs', '♨️', 50, 52, 12.5);
|
||||
|
||||
while (count && springs.length) {
|
||||
const cell = springs.splice(biased(1, springs.length - 1, 3), 1);
|
||||
const id = appendMarker(cell, 'hot_springs');
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const temp = convertTemperature(gauss(30, 15, 20, 100));
|
||||
notes.push({id, name: proper + ' Hot Springs', legend: `A hot springs area. Temperature: ${temp}`});
|
||||
count--;
|
||||
}
|
||||
})();
|
||||
|
||||
void (function addMines() {
|
||||
let hills = Array.from(cells.i).filter((i) => cells.h[i] > 47 && cells.burg[i]);
|
||||
let count = !hills.length ? 0 : Math.ceil((hills.length / 7) * number);
|
||||
if (!count) return;
|
||||
|
||||
addMarker('mine', '⛏️', 48, 50, 13.5);
|
||||
const resources = {salt: 5, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1};
|
||||
|
||||
while (count && hills.length) {
|
||||
const cell = hills.splice(Math.floor(Math.random() * hills.length), 1);
|
||||
const id = appendMarker(cell, 'mine');
|
||||
const resource = rw(resources);
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const name = `${burg.name} — ${resource} mining town`;
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`;
|
||||
notes.push({id, name, legend});
|
||||
count--;
|
||||
}
|
||||
})();
|
||||
|
||||
void (function addBridges() {
|
||||
const meanRoad = d3.mean(cells.road.filter((r) => r));
|
||||
const meanFlux = d3.mean(cells.fl.filter((fl) => fl));
|
||||
|
||||
let bridges = Array.from(cells.i)
|
||||
.filter((i) => cells.burg[i] && cells.h[i] >= 20 && cells.r[i] && cells.fl[i] > meanFlux && cells.road[i] > meanRoad)
|
||||
.sort((a, b) => cells.road[b] + cells.fl[b] / 10 - (cells.road[a] + cells.fl[a] / 10));
|
||||
|
||||
let count = !bridges.length ? 0 : Math.ceil((bridges.length / 12) * number);
|
||||
if (count) addMarker('bridge', '🌉', 50, 50, 14);
|
||||
|
||||
while (count && bridges.length) {
|
||||
const cell = bridges.splice(0, 1);
|
||||
const id = appendMarker(cell, 'bridge');
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const river = pack.rivers.find((r) => r.i === pack.cells.r[cell]);
|
||||
const riverName = river ? `${river.name} ${river.type}` : 'river';
|
||||
const name = river && P(0.2) ? river.name : burg.name;
|
||||
notes.push({id, name: `${name} Bridge`, legend: `A stone bridge over the ${riverName} near ${burg.name}`});
|
||||
count--;
|
||||
}
|
||||
})();
|
||||
|
||||
void (function addInns() {
|
||||
const maxRoad = d3.max(cells.road) * 0.9;
|
||||
let taverns = Array.from(cells.i).filter((i) => cells.crossroad[i] && cells.h[i] >= 20 && cells.road[i] > maxRoad);
|
||||
if (!taverns.length) return;
|
||||
const count = Math.ceil(4 * number);
|
||||
addMarker('inn', '🍻', 50, 50, 14.5);
|
||||
|
||||
const color = ['Dark', 'Light', 'Bright', 'Golden', 'White', 'Black', 'Red', 'Pink', 'Purple', 'Blue', 'Green', 'Yellow', 'Amber', 'Orange', 'Brown', 'Grey'];
|
||||
const animal = [
|
||||
'Antelope',
|
||||
'Ape',
|
||||
'Badger',
|
||||
'Bear',
|
||||
'Beaver',
|
||||
'Bison',
|
||||
'Boar',
|
||||
'Buffalo',
|
||||
'Cat',
|
||||
'Crane',
|
||||
'Crocodile',
|
||||
'Crow',
|
||||
'Deer',
|
||||
'Dog',
|
||||
'Eagle',
|
||||
'Elk',
|
||||
'Fox',
|
||||
'Goat',
|
||||
'Goose',
|
||||
'Hare',
|
||||
'Hawk',
|
||||
'Heron',
|
||||
'Horse',
|
||||
'Hyena',
|
||||
'Ibis',
|
||||
'Jackal',
|
||||
'Jaguar',
|
||||
'Lark',
|
||||
'Leopard',
|
||||
'Lion',
|
||||
'Mantis',
|
||||
'Marten',
|
||||
'Moose',
|
||||
'Mule',
|
||||
'Narwhal',
|
||||
'Owl',
|
||||
'Panther',
|
||||
'Rat',
|
||||
'Raven',
|
||||
'Rook',
|
||||
'Scorpion',
|
||||
'Shark',
|
||||
'Sheep',
|
||||
'Snake',
|
||||
'Spider',
|
||||
'Swan',
|
||||
'Tiger',
|
||||
'Turtle',
|
||||
'Wolf',
|
||||
'Wolverine',
|
||||
'Camel',
|
||||
'Falcon',
|
||||
'Hound',
|
||||
'Ox'
|
||||
];
|
||||
const adj = [
|
||||
'New',
|
||||
'Good',
|
||||
'High',
|
||||
'Old',
|
||||
'Great',
|
||||
'Big',
|
||||
'Major',
|
||||
'Happy',
|
||||
'Main',
|
||||
'Huge',
|
||||
'Far',
|
||||
'Beautiful',
|
||||
'Fair',
|
||||
'Prime',
|
||||
'Ancient',
|
||||
'Golden',
|
||||
'Proud',
|
||||
'Lucky',
|
||||
'Fat',
|
||||
'Honest',
|
||||
'Giant',
|
||||
'Distant',
|
||||
'Friendly',
|
||||
'Loud',
|
||||
'Hungry',
|
||||
'Magical',
|
||||
'Superior',
|
||||
'Peaceful',
|
||||
'Frozen',
|
||||
'Divine',
|
||||
'Favorable',
|
||||
'Brave',
|
||||
'Sunny',
|
||||
'Flying'
|
||||
];
|
||||
|
||||
for (let i = 0; i < taverns.length && i < count; i++) {
|
||||
const cell = taverns.splice(Math.floor(Math.random() * taverns.length), 1);
|
||||
const id = appendMarker(cell, 'inn');
|
||||
const type = P(0.3) ? 'inn' : 'tavern';
|
||||
const name = P(0.5) ? ra(color) + ' ' + ra(animal) : P(0.6) ? ra(adj) + ' ' + ra(animal) : ra(adj) + ' ' + capitalize(type);
|
||||
notes.push({id, name: 'The ' + name, legend: `A big and famous roadside ${type}`});
|
||||
}
|
||||
})();
|
||||
|
||||
void (function addLighthouses() {
|
||||
const lands = cells.i.filter((i) => cells.harbor[i] > 6 && cells.c[i].some((c) => cells.h[c] < 20 && cells.road[c]));
|
||||
const lighthouses = Array.from(lands).map((i) => [i, cells.v[i][cells.c[i].findIndex((c) => cells.h[c] < 20 && cells.road[c])]]);
|
||||
if (lighthouses.length) addMarker('lighthouse', '🚨', 50, 50, 16);
|
||||
const count = Math.ceil(4 * number);
|
||||
|
||||
for (let i = 0; i < lighthouses.length && i < count; i++) {
|
||||
const cell = lighthouses[i][0],
|
||||
vertex = lighthouses[i][1];
|
||||
const id = appendMarker(cell, 'lighthouse');
|
||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||
notes.push({id, name: getAdjective(proper) + ' Lighthouse' + name, legend: `A lighthouse to keep the navigation safe`});
|
||||
}
|
||||
})();
|
||||
|
||||
void (function addWaterfalls() {
|
||||
const waterfalls = cells.i.filter((i) => cells.r[i] && cells.h[i] > 70);
|
||||
if (waterfalls.length) addMarker('waterfall', '⟱', 50, 54, 16.5);
|
||||
const count = Math.ceil(3 * number);
|
||||
|
||||
for (let i = 0; i < waterfalls.length && i < count; i++) {
|
||||
const cell = waterfalls[i];
|
||||
const id = appendMarker(cell, 'waterfall');
|
||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||
notes.push({id, name: getAdjective(proper) + ' Waterfall' + name, legend: `An extremely beautiful waterfall`});
|
||||
}
|
||||
})();
|
||||
|
||||
void (function addBattlefields() {
|
||||
let battlefields = Array.from(cells.i).filter((i) => cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25);
|
||||
let count = battlefields.length < 100 ? 0 : Math.ceil((battlefields.length / 500) * number);
|
||||
if (count) addMarker('battlefield', '⚔️', 50, 52, 12);
|
||||
|
||||
while (count && battlefields.length) {
|
||||
const cell = battlefields.splice(Math.floor(Math.random() * battlefields.length), 1);
|
||||
const id = appendMarker(cell, 'battlefield');
|
||||
const campaign = ra(states[cells.state[cell]].campaigns);
|
||||
const date = generateDate(campaign.start, campaign.end);
|
||||
const name = Names.getCulture(cells.culture[cell]) + ' Battlefield';
|
||||
const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`;
|
||||
notes.push({id, name, legend});
|
||||
count--;
|
||||
}
|
||||
})();
|
||||
|
||||
function addMarker(id, icon, x, y, size) {
|
||||
const markers = svg.select('#defs-markers');
|
||||
if (markers.select('#marker_' + id).size()) return;
|
||||
|
||||
const symbol = markers
|
||||
.append('symbol')
|
||||
.attr('id', 'marker_' + id)
|
||||
.attr('viewBox', '0 0 30 30');
|
||||
symbol.append('path').attr('d', 'M6,19 l9,10 L24,19').attr('fill', '#000000').attr('stroke', 'none');
|
||||
symbol.append('circle').attr('cx', 15).attr('cy', 15).attr('r', 10).attr('fill', '#ffffff').attr('stroke', '#000000').attr('stroke-width', 1);
|
||||
symbol
|
||||
.append('text')
|
||||
.attr('x', x + '%')
|
||||
.attr('y', y + '%')
|
||||
.attr('fill', '#000000')
|
||||
.attr('stroke', '#3200ff')
|
||||
.attr('stroke-width', 0)
|
||||
.attr('font-size', size + 'px')
|
||||
.attr('dominant-baseline', 'central')
|
||||
.text(icon);
|
||||
}
|
||||
|
||||
function appendMarker(cell, type) {
|
||||
const x = cells.p[cell][0],
|
||||
y = cells.p[cell][1];
|
||||
const id = getNextId('markerElement');
|
||||
const name = '#marker_' + type;
|
||||
|
||||
markers
|
||||
.append('use')
|
||||
.attr('id', id)
|
||||
.attr('xlink:href', name)
|
||||
.attr('data-id', name)
|
||||
.attr('data-x', x)
|
||||
.attr('data-y', y)
|
||||
.attr('x', x - 15)
|
||||
.attr('y', y - 30)
|
||||
.attr('data-size', 1)
|
||||
.attr('width', 30)
|
||||
.attr('height', 30);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
TIME && console.timeEnd('addMarkers');
|
||||
}
|
||||
|
||||
// regenerate some zones
|
||||
function addZones(number = 1) {
|
||||
TIME && console.time('addZones');
|
||||
|
|
@ -1823,9 +1544,18 @@ function addZones(number = 1) {
|
|||
});
|
||||
}
|
||||
|
||||
const invasion = rw({Invasion: 4, Occupation: 3, Raid: 2, Conquest: 2, Subjugation: 1, Foray: 1, Skirmishes: 1, Incursion: 2, Pillaging: 1, Intervention: 1});
|
||||
const name = getAdjective(invader.name) + ' ' + invasion;
|
||||
data.push({name, type: 'Invasion', cells: cellsArray, fill: 'url(#hatch1)'});
|
||||
const invasion = rw({
|
||||
Invasion: 4,
|
||||
Occupation: 3,
|
||||
Raid: 2,
|
||||
Conquest: 2,
|
||||
Subjugation: 1,
|
||||
Foray: 1,
|
||||
Skirmishes: 1,
|
||||
Incursion: 2,
|
||||
Pillaging: 1,
|
||||
Intervention: 1
|
||||
});
|
||||
}
|
||||
|
||||
function addRebels() {
|
||||
|
|
@ -2133,7 +1863,7 @@ function addZones(number = 1) {
|
|||
|
||||
// show map stats on generation complete
|
||||
function showStatistics() {
|
||||
const template = templateInput.value;
|
||||
const template = templateInput.options[templateInput.selectedIndex].text;
|
||||
const templateRandom = locked('template') ? '' : '(random)';
|
||||
const stats = ` Seed: ${seed}
|
||||
Canvas size: ${graphWidth}x${graphHeight}
|
||||
|
|
|
|||
1909
main.js.orig
Normal file
1909
main.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -415,7 +415,7 @@ window.BurgsAndStates = (function () {
|
|||
function getRiverCost(r, i, type) {
|
||||
if (type === 'River') return r ? 0 : 100; // penalty for river cultures
|
||||
if (!r) return 0; // no penalty for others if there is no river
|
||||
return Math.min(Math.max(cells.fl[i] / 10, 20), 100); // river penalty from 20 to 100 based on flux
|
||||
return minmax(cells.fl[i] / 10, 20, 100); // river penalty from 20 to 100 based on flux
|
||||
}
|
||||
|
||||
function getTypeCost(t, type) {
|
||||
|
|
@ -479,6 +479,7 @@ window.BurgsAndStates = (function () {
|
|||
const {cells, features, states} = pack;
|
||||
const paths = []; // text paths
|
||||
lineGen.curve(d3.curveBundle.beta(1));
|
||||
const mode = options.stateLabelsMode || "auto";
|
||||
|
||||
for (const s of states) {
|
||||
if (!s.i || s.removed || !s.cells || (list && !list.includes(s.i))) continue;
|
||||
|
|
@ -585,7 +586,8 @@ window.BurgsAndStates = (function () {
|
|||
|
||||
paths.forEach((p) => {
|
||||
const id = p[0];
|
||||
const s = states[p[0]];
|
||||
const state = states[p[0]];
|
||||
const {name, fullName} = state;
|
||||
|
||||
if (list) {
|
||||
t.select('#textPath_stateLabel' + id).remove();
|
||||
|
|
@ -599,22 +601,7 @@ window.BurgsAndStates = (function () {
|
|||
.attr('id', 'textPath_stateLabel' + id);
|
||||
const pathLength = p[1].length > 1 ? textPath.node().getTotalLength() / letterLength : 0; // path length in letters
|
||||
|
||||
let lines = [];
|
||||
let ratio = 100;
|
||||
|
||||
if (pathLength < s.name.length) {
|
||||
// only short name will fit
|
||||
lines = splitInTwo(s.name);
|
||||
ratio = Math.max(Math.min(rn((pathLength / lines[0].length) * 60), 150), 50);
|
||||
} else if (pathLength > s.fullName.length * 2.5) {
|
||||
// full name will fit in one line
|
||||
lines = [s.fullName];
|
||||
ratio = Math.max(Math.min(rn((pathLength / lines[0].length) * 70), 170), 70);
|
||||
} else {
|
||||
// try miltilined label
|
||||
lines = splitInTwo(s.fullName);
|
||||
ratio = Math.max(Math.min(rn((pathLength / lines[0].length) * 60), 150), 70);
|
||||
}
|
||||
const [lines, ratio] = getLines(mode, name, fullName, pathLength);
|
||||
|
||||
// prolongate path if it's too short
|
||||
if (pathLength && pathLength < lines[0].length) {
|
||||
|
|
@ -646,7 +633,7 @@ window.BurgsAndStates = (function () {
|
|||
.node();
|
||||
|
||||
el.insertAdjacentHTML('afterbegin', spans.join(''));
|
||||
if (lines.length < 2) return;
|
||||
if (mode === "full" || lines.length === 1) return;
|
||||
|
||||
// check whether multilined label is generally inside the state. If no, replace with short name label
|
||||
const cs = pack.cells.state;
|
||||
|
|
@ -657,14 +644,14 @@ window.BurgsAndStates = (function () {
|
|||
const c4 = () => +cs[findCell(b.x + b.width, b.y + b.height)] === id;
|
||||
const c5 = () => +cs[findCell(b.x + b.width / 2, b.y + b.height)] === id;
|
||||
const c6 = () => +cs[findCell(b.x, b.y + b.height)] === id;
|
||||
if (c1() + c2() + c3() + c4() + c5() + c6() > 3) return; // generally inside
|
||||
if (c1() + c2() + c3() + c4() + c5() + c6() > 3) return; // generally inside => exit
|
||||
|
||||
// use one-line name
|
||||
const name = pathLength > s.fullName.length * 1.8 ? s.fullName : s.name;
|
||||
example.text(name);
|
||||
// move to one-line name
|
||||
const text = pathLength > fullName.length * 1.8 ? fullName : name;
|
||||
example.text(text);
|
||||
const left = example.node().getBBox().width / -2; // x offset
|
||||
el.innerHTML = `<tspan x="${left}px">${name}</tspan>`;
|
||||
ratio = Math.max(Math.min(rn((pathLength / name.length) * 60), 130), 40);
|
||||
el.innerHTML = `<tspan x="${left}px">${text}</tspan>`;
|
||||
|
||||
el.setAttribute('font-size', ratio + '%');
|
||||
});
|
||||
|
||||
|
|
@ -673,13 +660,34 @@ window.BurgsAndStates = (function () {
|
|||
})();
|
||||
|
||||
TIME && console.timeEnd('drawStateLabels');
|
||||
function getLines(mode, name, fullName, pathLength) {
|
||||
// short name
|
||||
if (mode === "short" || (mode === "auto" && pathLength < name.length)) {
|
||||
const lines = splitInTwo(name);
|
||||
const ratio = pathLength / lines[0].length;
|
||||
return [lines, minmax(rn(ratio * 60), 50, 150)];
|
||||
}
|
||||
|
||||
// full name: one line
|
||||
if (pathLength > fullName.length * 2.5) {
|
||||
const lines = [fullName];
|
||||
const ratio = pathLength / lines[0].length;
|
||||
return [lines, minmax(rn(ratio * 70), 70, 170)];
|
||||
}
|
||||
|
||||
// full name: two lines
|
||||
const lines = splitInTwo(fullName);
|
||||
const ratio = pathLength / lines[0].length;
|
||||
return [lines, minmax(rn(ratio * 60), 70, 150)];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// calculate states data like area, population etc.
|
||||
const collectStatistics = function () {
|
||||
TIME && console.time('collectStatistics');
|
||||
const cells = pack.cells,
|
||||
states = pack.states;
|
||||
const {cells, states} = pack;
|
||||
|
||||
states.forEach((s) => {
|
||||
if (s.removed) return;
|
||||
s.cells = s.area = s.burgs = s.rural = s.urban = 0;
|
||||
|
|
@ -737,21 +745,28 @@ window.BurgsAndStates = (function () {
|
|||
TIME && console.timeEnd('assignColors');
|
||||
};
|
||||
|
||||
// generate historical conflicts of each state
|
||||
const generateCampaigns = function () {
|
||||
const wars = {War: 6, Conflict: 2, Campaign: 4, Invasion: 2, Rebellion: 2, Conquest: 2, Intervention: 1, Expedition: 1, Crusade: 1};
|
||||
|
||||
const wars = {War: 6, Conflict: 2, Campaign: 4, Invasion: 2, Rebellion: 2, Conquest: 2, Intervention: 1, Expedition: 1, Crusade: 1};
|
||||
const generateCampaign = state => {
|
||||
const neighbors = state.neighbors.length ? state.neighbors : [0];
|
||||
pack.states.forEach((s) => {
|
||||
if (!s.i || s.removed) return;
|
||||
const n = s.neighbors.length ? s.neighbors : [0];
|
||||
s.campaigns = n
|
||||
return neighbors
|
||||
.map((i) => {
|
||||
const name = i && P(0.8) ? pack.states[i].name : Names.getCultureShort(s.culture);
|
||||
const start = gauss(options.year - 100, 150, 1, options.year - 6);
|
||||
const end = start + gauss(4, 5, 1, options.year - start - 1);
|
||||
return {name: getAdjective(name) + ' ' + rw(wars), start, end};
|
||||
})
|
||||
.sort((a, b) => a.start - b.start);
|
||||
const name = i && P(0.8) ? pack.states[i].name : Names.getCultureShort(state.culture);
|
||||
const start = gauss(options.year - 100, 150, 1, options.year - 6);
|
||||
const end = start + gauss(4, 5, 1, options.year - start - 1);
|
||||
return {name: getAdjective(name) + " " + rw(wars), start, end};
|
||||
})
|
||||
.sort((a, b) => a.start - b.start);
|
||||
};
|
||||
|
||||
// generate historical conflicts of each state
|
||||
const generateCampaigns = function () {
|
||||
pack.states.forEach(s => {
|
||||
if (!s.i || s.removed) return;
|
||||
s.campaigns = generateCampaign(s);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -950,6 +965,13 @@ window.BurgsAndStates = (function () {
|
|||
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, 'Holy State': 1};
|
||||
const anarchy = {'Free Territory': 2, Council: 3, Commune: 1, Community: 1};
|
||||
"Most Serene Republic": 2,
|
||||
Tetrarchy: 1,
|
||||
Triumvirate: 1,
|
||||
Diarchy: 1,
|
||||
"Trade Company": 4,
|
||||
Junta: 1
|
||||
}; // weighted random
|
||||
|
||||
for (const s of states) {
|
||||
if (list && !list.includes(s.i)) continue;
|
||||
|
|
@ -974,6 +996,7 @@ window.BurgsAndStates = (function () {
|
|||
// Default name depends on exponent tier, some culture bases have special names for tiers
|
||||
if (s.diplomacy) {
|
||||
if (form === 'Duchy' && s.neighbors.length > 1 && rand(6) < s.neighbors.length && s.diplomacy.includes('Vassal')) return 'Marches'; // some vassal dutchies on borderland
|
||||
if (base === 1 && P(0.3) && s.diplomacy.includes("Vassal")) return "Dominion"; // English vassals
|
||||
if (P(0.3) && s.diplomacy.includes('Vassal')) return 'Protectorate'; // some vassals
|
||||
}
|
||||
|
||||
|
|
@ -1078,16 +1101,16 @@ window.BurgsAndStates = (function () {
|
|||
const localSeed = regenerate ? Math.floor(Math.random() * 1e9).toString() : seed;
|
||||
Math.random = aleaPRNG(localSeed);
|
||||
|
||||
const cells = pack.cells,
|
||||
states = pack.states,
|
||||
burgs = pack.burgs;
|
||||
const {cells, states, burgs} = pack;
|
||||
const provinces = (pack.provinces = [0]);
|
||||
cells.province = new Uint16Array(cells.i.length); // cell state
|
||||
const percentage = +provincesInput.value;
|
||||
|
||||
if (states.length < 2 || !percentage) {
|
||||
states.forEach((s) => (s.provinces = []));
|
||||
return;
|
||||
} // no provinces
|
||||
|
||||
const max = percentage == 100 ? 1000 : gauss(20, 5, 5, 100) * percentage ** 0.5; // max growth
|
||||
|
||||
const forms = {
|
||||
|
|
@ -1100,7 +1123,6 @@ window.BurgsAndStates = (function () {
|
|||
};
|
||||
|
||||
// generate provinces for a selected burgs
|
||||
Math.random = aleaPRNG(localSeed);
|
||||
states.forEach((s) => {
|
||||
s.provinces = [];
|
||||
if (!s.i || s.removed) return;
|
||||
|
|
|
|||
1394
modules/burgs-and-states.js.orig
Normal file
1394
modules/burgs-and-states.js.orig
Normal file
File diff suppressed because it is too large
Load diff
139
modules/cloud.js
Normal file
139
modules/cloud.js
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
Cloud provider implementations (Dropbox only as now)
|
||||
|
||||
provider Interface:
|
||||
|
||||
name: name of the provider
|
||||
async auth(): authenticate and get access tokens from provider
|
||||
async save(filename): save map file to provider as filename
|
||||
async load(filename): load filename from provider
|
||||
async list(): list available filenames at provider
|
||||
async getLink(filePath): get shareable link for file
|
||||
restore(): restore access tokens from storage if possible
|
||||
|
||||
*/
|
||||
|
||||
window.Cloud = (function () {
|
||||
// helpers to use in providers for token handling
|
||||
const lSKey = (x) => `auth-${x}`;
|
||||
const setToken = (prov, key) => localStorage.setItem(lSKey(prov), key);
|
||||
const getToken = (prov) => localStorage.getItem(lSKey(prov));
|
||||
|
||||
/**********************************************************/
|
||||
/* Dropbox provider */
|
||||
/**********************************************************/
|
||||
|
||||
const DBP = {
|
||||
name: 'dropbox',
|
||||
clientId: 'pdr9ae64ip0qno4',
|
||||
authWindow: undefined,
|
||||
token: null, // Access token
|
||||
api: null,
|
||||
|
||||
restore() {
|
||||
this.token = getToken(this.name);
|
||||
if (this.token) this.connect(this.token);
|
||||
},
|
||||
|
||||
async call(name, param) {
|
||||
try {
|
||||
return await this.api[name](param);
|
||||
} catch (e) {
|
||||
if (e.name !== 'DropboxResponseError') throw e;
|
||||
// retry with auth
|
||||
await this.auth();
|
||||
return await this.api[name](param);
|
||||
}
|
||||
},
|
||||
|
||||
connect(token) {
|
||||
const clientId = this.clientId;
|
||||
const auth = new Dropbox.DropboxAuth({clientId});
|
||||
auth.setAccessToken(token);
|
||||
this.api = new Dropbox.Dropbox({auth});
|
||||
},
|
||||
|
||||
async save(fileName, contents) {
|
||||
if (!this.api) await this.auth();
|
||||
const resp = this.call('filesUpload', {path: '/' + fileName, contents});
|
||||
DEBUG && console.log('Dropbox response:', resp);
|
||||
return true;
|
||||
},
|
||||
|
||||
async load(path) {
|
||||
if (!this.api) await this.auth();
|
||||
const resp = await this.call('filesDownload', {path});
|
||||
const blob = resp.result.fileBlob;
|
||||
if (!blob) throw new Error('Invalid response from dropbox.');
|
||||
return blob;
|
||||
},
|
||||
|
||||
async list() {
|
||||
if (!this.api) return null;
|
||||
const resp = await this.call('filesListFolder', {path: ''});
|
||||
return resp.result.entries.map((e) => ({name: e.name, path: e.path_lower}));
|
||||
},
|
||||
|
||||
auth() {
|
||||
const url = window.location.origin + window.location.pathname + 'dropbox.html';
|
||||
this.authWindow = window.open(url, 'auth', 'width=640,height=480');
|
||||
// child window expected to call
|
||||
// window.opener.Cloud.providers.dropbox.setDropBoxToken (see below)
|
||||
return new Promise((resolve, reject) => {
|
||||
const watchDog = () => {
|
||||
this.authWindow.close();
|
||||
reject(new Error('Timeout. No auth for dropbox.'));
|
||||
};
|
||||
setTimeout(watchDog, 120 * 1000);
|
||||
window.addEventListener('dropboxauth', (e) => {
|
||||
clearTimeout(watchDog);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Callback function for auth window.
|
||||
setDropBoxToken(token) {
|
||||
DEBUG && console.log('Access token:', token);
|
||||
setToken(this.name, token);
|
||||
this.connect(token);
|
||||
this.authWindow.close();
|
||||
window.dispatchEvent(new Event('dropboxauth'));
|
||||
},
|
||||
|
||||
async getLink(path) {
|
||||
if (!this.api) await this.auth();
|
||||
let resp;
|
||||
|
||||
// already exists?
|
||||
resp = await this.call('sharingListSharedLinks', {path});
|
||||
if (resp.result.links.length) return resp.result.links[0].url;
|
||||
|
||||
// create new
|
||||
resp = await this.call('sharingCreateSharedLinkWithSettings', {
|
||||
path,
|
||||
settings: {
|
||||
require_password: false,
|
||||
audience: 'public',
|
||||
access: 'viewer',
|
||||
requested_visibility: 'public',
|
||||
allow_download: true
|
||||
}
|
||||
});
|
||||
DEBUG && console.log('Dropbox link object:', resp.result);
|
||||
return resp.result.url;
|
||||
}
|
||||
};
|
||||
|
||||
// register providers here:
|
||||
const providers = {
|
||||
dropbox: DBP
|
||||
};
|
||||
|
||||
// restore all providers at startup
|
||||
for (const p of Object.values(providers)) p.restore();
|
||||
|
||||
return {providers};
|
||||
})();
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.COA = (function () {
|
||||
const tinctures = {
|
||||
|
|
@ -45,8 +45,38 @@ window.COA = (function () {
|
|||
|
||||
const charges = {
|
||||
// categories selection
|
||||
types: {conventional: 30, crosses: 10, 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: 2, architecture: 1, miscellaneous: 10, inescutcheon: 5},
|
||||
types: {
|
||||
conventional: 30,
|
||||
crosses: 10,
|
||||
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: 2,
|
||||
architecture: 1,
|
||||
miscellaneous: 10,
|
||||
inescutcheon: 5
|
||||
},
|
||||
semy: {conventional: 12, crosses: 3, plants: 1},
|
||||
// generic categories
|
||||
conventional: {
|
||||
|
|
@ -193,86 +223,86 @@ window.COA = (function () {
|
|||
Capital: {crown: 4, orb: 1, lute: 1, castle: 3, tower: 1},
|
||||
Сathedra: {chalice: 1, orb: 1, crosier: 2, lamb: 1, monk: 2, angel: 3, crossLatin: 2, crossPatriarchal: 1, crossOrthodox: 1, crossCalvary: 1},
|
||||
// specific cases
|
||||
natural: {fountain: "azure", garb: "or", raven: "sable"}, // charges to mainly use predefined colours
|
||||
natural: {fountain: 'azure', garb: 'or', raven: 'sable'}, // charges to mainly use predefined colours
|
||||
sinister: [
|
||||
// charges that can be 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",
|
||||
"monk",
|
||||
"owl",
|
||||
"axe",
|
||||
"camel",
|
||||
"fasces",
|
||||
"lionPassantGuardant",
|
||||
"helmet"
|
||||
'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',
|
||||
'monk',
|
||||
'owl',
|
||||
'axe',
|
||||
'camel',
|
||||
'fasces',
|
||||
'lionPassantGuardant',
|
||||
'helmet'
|
||||
],
|
||||
reversed: [
|
||||
// charges that can be reversed
|
||||
"goutte",
|
||||
"mullet",
|
||||
"mullet7",
|
||||
"crescent",
|
||||
"crossTau",
|
||||
"cancer",
|
||||
"sword",
|
||||
"sabresCrossed",
|
||||
"hand",
|
||||
"horseshoe",
|
||||
"bowWithArrow",
|
||||
"arrow",
|
||||
"arrowsSheaf",
|
||||
"rake",
|
||||
"crossTriquetra",
|
||||
"crossLatin",
|
||||
"crossTau"
|
||||
'goutte',
|
||||
'mullet',
|
||||
'mullet7',
|
||||
'crescent',
|
||||
'crossTau',
|
||||
'cancer',
|
||||
'sword',
|
||||
'sabresCrossed',
|
||||
'hand',
|
||||
'horseshoe',
|
||||
'bowWithArrow',
|
||||
'arrow',
|
||||
'arrowsSheaf',
|
||||
'rake',
|
||||
'crossTriquetra',
|
||||
'crossLatin',
|
||||
'crossTau'
|
||||
]
|
||||
};
|
||||
|
||||
|
|
@ -412,7 +442,28 @@ window.COA = (function () {
|
|||
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},
|
||||
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
|
||||
};
|
||||
|
||||
|
|
@ -471,7 +522,7 @@ window.COA = (function () {
|
|||
};
|
||||
|
||||
const generate = function (parent, kinship, dominion, type) {
|
||||
if (!parent || parent === "custom") {
|
||||
if (!parent || parent === 'custom') {
|
||||
parent = null;
|
||||
kinship = 0;
|
||||
dominion = 0;
|
||||
|
|
@ -479,127 +530,127 @@ window.COA = (function () {
|
|||
let usedPattern = null,
|
||||
usedTinctures = [];
|
||||
|
||||
const t1 = P(kinship) ? parent.t1 : getTincture("field");
|
||||
if (t1.includes("-")) usedPattern = t1;
|
||||
const t1 = P(kinship) ? parent.t1 : getTincture('field');
|
||||
if (t1.includes('-')) usedPattern = t1;
|
||||
const coa = {t1};
|
||||
|
||||
let charge = P(usedPattern ? 0.5 : 0.93) ? true : false; // 80% for charge
|
||||
const linedOrdinary = (charge && P(0.3)) || P(0.5) ? (parent?.ordinaries && P(kinship) ? parent.ordinaries[0].ordinary : rw(ordinaries.lined)) : null;
|
||||
const ordinary = (!charge && P(0.65)) || P(0.3) ? (linedOrdinary ? linedOrdinary : rw(ordinaries.straight)) : null; // 36% for ordinary
|
||||
const rareDivided = ["chief", "terrace", "chevron", "quarter", "flaunches"].includes(ordinary);
|
||||
const rareDivided = ['chief', 'terrace', 'chevron', 'quarter', 'flaunches'].includes(ordinary);
|
||||
const divisioned = rareDivided ? P(0.03) : charge && ordinary ? P(0.03) : charge ? P(0.3) : ordinary ? P(0.7) : P(0.995); // 33% for division
|
||||
const division = divisioned ? (parent?.division && P(kinship - 0.1) ? parent.division.division : rw(divisions.variants)) : null;
|
||||
if (charge) charge = parent?.charges && P(kinship - 0.1) ? parent.charges[0].charge : type && type !== "Generic" && P(0.2) ? rw(charges[type]) : selectCharge();
|
||||
if (charge) charge = parent?.charges && P(kinship - 0.1) ? parent.charges[0].charge : type && type !== 'Generic' && P(0.2) ? rw(charges[type]) : selectCharge();
|
||||
|
||||
if (division) {
|
||||
const t = getTincture("division", usedTinctures, P(0.98) ? coa.t1 : null);
|
||||
const t = getTincture('division', usedTinctures, P(0.98) ? coa.t1 : null);
|
||||
coa.division = {division, t};
|
||||
if (divisions[division]) coa.division.line = usedPattern || (ordinary && P(0.7)) ? "straight" : rw(divisions[division]);
|
||||
if (divisions[division]) coa.division.line = usedPattern || (ordinary && P(0.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(0.7)) ? "straight" : rw(lines);
|
||||
if (division && !charge && !usedPattern && P(0.5) && ordinary !== "bordure" && ordinary !== "orle") {
|
||||
if (P(0.8)) coa.ordinaries[0].divided = "counter";
|
||||
coa.ordinaries = [{ordinary, t: getTincture('charge', usedTinctures, coa.t1)}];
|
||||
if (linedOrdinary) coa.ordinaries[0].line = usedPattern || (division && P(0.7)) ? 'straight' : rw(lines);
|
||||
if (division && !charge && !usedPattern && P(0.5) && ordinary !== 'bordure' && ordinary !== 'orle') {
|
||||
if (P(0.8)) coa.ordinaries[0].divided = 'counter';
|
||||
// 40%
|
||||
else if (P(0.6)) coa.ordinaries[0].divided = "field";
|
||||
else if (P(0.6)) coa.ordinaries[0].divided = 'field';
|
||||
// 6%
|
||||
else coa.ordinaries[0].divided = "division"; // 4%
|
||||
else coa.ordinaries[0].divided = 'division'; // 4%
|
||||
}
|
||||
}
|
||||
|
||||
if (charge) {
|
||||
let p = "e",
|
||||
t = "gules";
|
||||
let p = 'e',
|
||||
t = 'gules';
|
||||
const ordinaryT = coa.ordinaries ? coa.ordinaries[0].t : null;
|
||||
if (positions.ordinariesOn[ordinary] && P(0.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(0.3) ? coa.t1 : getTincture("charge", [], ordinaryT);
|
||||
t = !usedPattern && P(0.3) ? coa.t1 : getTincture('charge', [], ordinaryT);
|
||||
} else if (positions.ordinariesOff[ordinary] && P(0.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(0.3) ? ordinaryT : getTincture("charge", usedTinctures, coa.t1);
|
||||
t = !usedPattern && P(0.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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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(0.95)) {
|
||||
if (p === 'ABCDEFGHIKL' && P(0.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(0.8) && charge === "inescutcheon") {
|
||||
const t = getTincture('charge', usedTinctures, coa.t1);
|
||||
coa.charges.push({charge, t, p: 'e'});
|
||||
} else if (P(0.8) && charge === 'inescutcheon') {
|
||||
// add charge to inescutcheon
|
||||
const charge = selectCharge(charges.types);
|
||||
const t2 = getTincture("charge", [], t);
|
||||
const t2 = getTincture('charge', [], t);
|
||||
coa.charges.push({charge, t: t2, p, size: 0.5});
|
||||
} else if (division && !ordinary) {
|
||||
const allowCounter = !usedPattern && (!coa.line || coa.line === "straight");
|
||||
const allowCounter = !usedPattern && (!coa.line || coa.line === 'straight');
|
||||
|
||||
// dimidiation: second charge at division basic positons
|
||||
if (P(0.3) && ["perPale", "perFess"].includes(division) && coa.line === "straight") {
|
||||
coa.charges[0].divided = "field";
|
||||
if (P(0.3) && ['perPale', 'perFess'].includes(division) && coa.line === 'straight') {
|
||||
coa.charges[0].divided = 'field';
|
||||
if (P(0.95)) {
|
||||
const p2 = p === "e" || P(0.5) ? "e" : rw(positions.divisions[division]);
|
||||
const p2 = p === 'e' || P(0.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"});
|
||||
const t = getTincture('charge', usedTinctures, coa.division.t);
|
||||
coa.charges.push({charge, t, p: p2, divided: 'division'});
|
||||
}
|
||||
} else if (allowCounter && P(0.4)) coa.charges[0].divided = "counter";
|
||||
} else if (allowCounter && P(0.4)) coa.charges[0].divided = 'counter';
|
||||
// counterchanged, 40%
|
||||
else if (["perPale", "perFess", "perBend", "perBendSinister"].includes(division) && P(0.8)) {
|
||||
else if (['perPale', 'perFess', 'perBend', 'perBendSinister'].includes(division) && P(0.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
|
||||
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);
|
||||
const t = getTincture('charge', usedTinctures, coa.division.t);
|
||||
coa.charges.push({charge, t, p: p2});
|
||||
} else if (["perCross", "perSaltire"].includes(division) && P(0.5)) {
|
||||
} else if (['perCross', 'perSaltire'].includes(division) && P(0.5)) {
|
||||
// place 4 charges in division standard positions
|
||||
const [p1, p2, p3, p4] = division === "perCross" ? ["j", "l", "m", "o"] : ["b", "d", "f", "h"];
|
||||
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 t2 = getTincture('charge', [], coa.division.t);
|
||||
|
||||
const c3 = selectCharge(charges.single);
|
||||
const t3 = getTincture("charge", [], coa.division.t);
|
||||
const t3 = getTincture('charge', [], coa.division.t);
|
||||
|
||||
const c4 = selectCharge(charges.single);
|
||||
const t4 = getTincture("charge", [], coa.t1);
|
||||
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%
|
||||
} else if (allowCounter && p.length > 1) coa.charges[0].divided = 'counter'; // counterchanged, 40%
|
||||
}
|
||||
|
||||
coa.charges.forEach(c => defineChargeAttributes(c));
|
||||
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("");
|
||||
c.p = [...new Set(c.p)].join('');
|
||||
|
||||
// define orientation
|
||||
if (P(0.02) && charges.sinister.includes(c.charge)) c.sinister = 1;
|
||||
|
|
@ -610,51 +661,51 @@ window.COA = (function () {
|
|||
// 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};
|
||||
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.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;
|
||||
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 (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.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]);
|
||||
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;
|
||||
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
|
||||
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' && element !== 'charge') usedTinctures.push(tincture); // add field tincture
|
||||
|
||||
if (type === "patterns") {
|
||||
if (type === 'patterns') {
|
||||
usedPattern = tincture;
|
||||
tincture = definePattern(tincture, element);
|
||||
}
|
||||
|
|
@ -663,72 +714,72 @@ window.COA = (function () {
|
|||
}
|
||||
|
||||
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";
|
||||
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";
|
||||
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 = "") {
|
||||
function definePattern(pattern, element, size = '') {
|
||||
let t1 = null,
|
||||
t2 = null;
|
||||
if (P(0.1)) size = "-small";
|
||||
else if (P(0.1)) size = "-smaller";
|
||||
else if (P(0.01)) size = "-big";
|
||||
else if (P(0.005)) size = "-smallest";
|
||||
if (P(0.1)) size = '-small';
|
||||
else if (P(0.1)) size = '-smaller';
|
||||
else if (P(0.01)) size = '-big';
|
||||
else if (P(0.005)) size = '-smallest';
|
||||
|
||||
// apply standard tinctures
|
||||
if (P(0.5) && ["vair", "vairInPale", "vairEnPointe"].includes(pattern)) {
|
||||
t1 = "azure";
|
||||
t2 = "argent";
|
||||
} else if (P(0.8) && pattern === "ermine") {
|
||||
t1 = "argent";
|
||||
t2 = "sable";
|
||||
} else if (pattern === "pappellony") {
|
||||
if (P(0.5) && ['vair', 'vairInPale', 'vairEnPointe'].includes(pattern)) {
|
||||
t1 = 'azure';
|
||||
t2 = 'argent';
|
||||
} else if (P(0.8) && pattern === 'ermine') {
|
||||
t1 = 'argent';
|
||||
t2 = 'sable';
|
||||
} else if (pattern === 'pappellony') {
|
||||
if (P(0.2)) {
|
||||
t1 = "gules";
|
||||
t2 = "or";
|
||||
t1 = 'gules';
|
||||
t2 = 'or';
|
||||
} else if (P(0.2)) {
|
||||
t1 = "argent";
|
||||
t2 = "sable";
|
||||
t1 = 'argent';
|
||||
t2 = 'sable';
|
||||
} else if (P(0.2)) {
|
||||
t1 = "azure";
|
||||
t2 = "argent";
|
||||
t1 = 'azure';
|
||||
t2 = 'argent';
|
||||
}
|
||||
} else if (pattern === "masoned") {
|
||||
} else if (pattern === 'masoned') {
|
||||
if (P(0.3)) {
|
||||
t1 = "gules";
|
||||
t2 = "argent";
|
||||
t1 = 'gules';
|
||||
t2 = 'argent';
|
||||
} else if (P(0.3)) {
|
||||
t1 = "argent";
|
||||
t2 = "sable";
|
||||
t1 = 'argent';
|
||||
t2 = 'sable';
|
||||
} else if (P(0.1)) {
|
||||
t1 = "or";
|
||||
t2 = "sable";
|
||||
t1 = 'or';
|
||||
t2 = 'sable';
|
||||
}
|
||||
} else if (pattern === "fretty") {
|
||||
if (t2 === "sable" || P(0.35)) {
|
||||
t1 = "argent";
|
||||
t2 = "gules";
|
||||
} else if (pattern === 'fretty') {
|
||||
if (t2 === 'sable' || P(0.35)) {
|
||||
t1 = 'argent';
|
||||
t2 = 'gules';
|
||||
} else if (P(0.25)) {
|
||||
t1 = "sable";
|
||||
t2 = "or";
|
||||
t1 = 'sable';
|
||||
t2 = 'or';
|
||||
} else if (P(0.15)) {
|
||||
t1 = "gules";
|
||||
t2 = "argent";
|
||||
t1 = 'gules';
|
||||
t2 = 'argent';
|
||||
}
|
||||
} else if (pattern === "semy") pattern += "_of_" + selectCharge(charges.semy);
|
||||
} else if (pattern === 'semy') pattern += '_of_' + selectCharge(charges.semy);
|
||||
|
||||
if (!t1 || !t2) {
|
||||
const startWithMetal = P(0.7);
|
||||
|
|
@ -737,7 +788,7 @@ window.COA = (function () {
|
|||
}
|
||||
|
||||
// division should not be the same tincture as base field
|
||||
if (element === "division") {
|
||||
if (element === 'division') {
|
||||
if (usedTinctures.includes(t1)) t1 = replaceTincture(t1);
|
||||
if (usedTinctures.includes(t2)) t2 = replaceTincture(t2);
|
||||
}
|
||||
|
|
@ -755,12 +806,12 @@ window.COA = (function () {
|
|||
}
|
||||
|
||||
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 0.7;
|
||||
if (p === "abcpqh" || p === "ez" || p === "be") return 0.5;
|
||||
if (["a", "b", "c", "d", "f", "g", "h", "i", "bh", "df"].includes(p)) return 0.5;
|
||||
if (["j", "l", "m", "o", "jlmo"].includes(p) && d === "perCross") return 0.6;
|
||||
if (p === 'e' && (o === 'bordure' || o === 'orle')) return 1.1;
|
||||
if (p === 'e') return 1.5;
|
||||
if (p === 'jln' || p === 'jlh') return 0.7;
|
||||
if (p === 'abcpqh' || p === 'ez' || p === 'be') return 0.5;
|
||||
if (['a', 'b', 'c', 'd', 'f', 'g', 'h', 'i', 'bh', 'df'].includes(p)) return 0.5;
|
||||
if (['j', 'l', 'm', 'o', 'jlmo'].includes(p) && d === 'perCross') return 0.6;
|
||||
if (p.length > 10) return 0.18; // >10 (bordure)
|
||||
if (p.length > 7) return 0.3; // 8, 9, 10
|
||||
if (p.length > 4) return 0.4; // 5, 6, 7
|
||||
|
|
@ -772,18 +823,18 @@ window.COA = (function () {
|
|||
};
|
||||
|
||||
const getShield = function (culture, state) {
|
||||
const emblemShape = document.getElementById("emblemShape");
|
||||
const shapeGroup = emblemShape.selectedOptions[0]?.parentNode.label || "Diversiform";
|
||||
if (shapeGroup !== "Diversiform") return emblemShape.value;
|
||||
const emblemShape = document.getElementById('emblemShape');
|
||||
const shapeGroup = emblemShape.selectedOptions[0]?.parentNode.label || 'Diversiform';
|
||||
if (shapeGroup !== 'Diversiform') return emblemShape.value;
|
||||
|
||||
if (emblemShape.value === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield;
|
||||
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";
|
||||
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));
|
||||
const toString = (coa) => JSON.stringify(coa).replaceAll('#', '%23');
|
||||
const copy = (coa) => JSON.parse(JSON.stringify(coa));
|
||||
|
||||
return {generate, toString, copy, getShield, shields};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.COArenderer = (function () {
|
||||
const colors = {
|
||||
argent: "#fafafa",
|
||||
or: "#ffe066",
|
||||
gules: "#d7374a",
|
||||
sable: "#333333",
|
||||
azure: "#377cd7",
|
||||
vert: "#26c061",
|
||||
purpure: "#522d5b",
|
||||
murrey: "#85185b",
|
||||
sanguine: "#b63a3a",
|
||||
tenné: "#cc7f19"
|
||||
argent: '#fafafa',
|
||||
or: '#ffe066',
|
||||
gules: '#d7374a',
|
||||
sable: '#333333',
|
||||
azure: '#377cd7',
|
||||
vert: '#26c061',
|
||||
purpure: '#522d5b',
|
||||
murrey: '#85185b',
|
||||
sanguine: '#b63a3a',
|
||||
tenné: '#cc7f19'
|
||||
};
|
||||
|
||||
const shieldPositions = {
|
||||
|
|
@ -1396,142 +1396,165 @@ window.COArenderer = (function () {
|
|||
};
|
||||
|
||||
const shieldBox = {
|
||||
heater: "0 10 200 200",
|
||||
spanish: "0 10 200 200",
|
||||
french: "0 10 200 200",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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"
|
||||
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"
|
||||
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",
|
||||
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",
|
||||
'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",
|
||||
'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",
|
||||
'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"
|
||||
'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 = {
|
||||
|
|
@ -1548,14 +1571,20 @@ window.COArenderer = (function () {
|
|||
gyronny: `<polygon points="0,0 200,200 200,100 0,100"/><polygon points="200,0 0,200 100,200 100,0"/>`,
|
||||
chevronny: `<path d="M0,80 100,-15 200,80 200,120 100,25 0,120z M0,160 100,65 200,160 200,200 100,105 0,200z M0,240 100,145 200,240 0,240z"/>`,
|
||||
// lined divisions
|
||||
perFessLined: line => `<path d="${line}"/><rect x="0" y="115" width="200" height="85" shape-rendering="crispedges"/>`,
|
||||
perPaleLined: line => `<path d="${line}" transform="rotate(-90 100 100)"/><rect x="115" y="0" width="85" height="200" shape-rendering="crispedges"/>`,
|
||||
perBendLined: line => `<path d="${line}" transform="translate(-10 -10) rotate(45 110 110) scale(1.1)"/><rect x="0" y="115" width="200" height="85" transform="translate(-10 -10) rotate(45 110 110) scale(1.1)" shape-rendering="crispedges"/>`,
|
||||
perBendSinisterLined: line => `<path d="${line}" transform="translate(-10 -10) rotate(-45 110 110) scale(1.1)"/><rect x="0" y="115" width="200" height="85" transform="translate(-10 -10) rotate(-45 110 110) scale(1.1)" shape-rendering="crispedges"/>`,
|
||||
perChevronLined: line => `<rect x="15" y="115" width="200" height="200" transform="translate(70 70) rotate(45 100 100)"/><path d="${line}" transform="translate(129 71) rotate(-45 -100 100) scale(-1 1)"/><path d="${line}" transform="translate(71 71) rotate(45 100 100)"/>`,
|
||||
perChevronReversedLined: line => `<rect x="15" y="115" width="200" height="200" transform="translate(-70 -70) rotate(225.001 100 100)"/><path d="${line}" transform="translate(-70.7 -70.7) rotate(225 100 100) scale(1 1)"/><path d="${line}" transform="translate(270.7 -70.7) rotate(-225 -100 100) scale(-1 1)"/>`,
|
||||
perCrossLined: line => `<rect x="100" y="0" width="100" height="92.5"/><rect x="0" y="107.5" width="100" height="92.5"/><path d="${line}" transform="translate(0 50) scale(.5001)"/><path d="${line}" transform="translate(200 150) scale(-.5)"/>`,
|
||||
perPileLined: line => `<path d="${line}" transform="translate(161.66 10) rotate(66.66 -100 100) scale(-1 1)"/><path d="${line}" transform="translate(38.33 10) rotate(-66.66 100 100)"/><polygon points="-2.15,0 84.15,200 115.85,200 202.15,0 200,200 0,200"/>`,
|
||||
perFessLined: (line) => `<path d="${line}"/><rect x="0" y="115" width="200" height="85" shape-rendering="crispedges"/>`,
|
||||
perPaleLined: (line) => `<path d="${line}" transform="rotate(-90 100 100)"/><rect x="115" y="0" width="85" height="200" shape-rendering="crispedges"/>`,
|
||||
perBendLined: (line) =>
|
||||
`<path d="${line}" transform="translate(-10 -10) rotate(45 110 110) scale(1.1)"/><rect x="0" y="115" width="200" height="85" transform="translate(-10 -10) rotate(45 110 110) scale(1.1)" shape-rendering="crispedges"/>`,
|
||||
perBendSinisterLined: (line) =>
|
||||
`<path d="${line}" transform="translate(-10 -10) rotate(-45 110 110) scale(1.1)"/><rect x="0" y="115" width="200" height="85" transform="translate(-10 -10) rotate(-45 110 110) scale(1.1)" shape-rendering="crispedges"/>`,
|
||||
perChevronLined: (line) =>
|
||||
`<rect x="15" y="115" width="200" height="200" transform="translate(70 70) rotate(45 100 100)"/><path d="${line}" transform="translate(129 71) rotate(-45 -100 100) scale(-1 1)"/><path d="${line}" transform="translate(71 71) rotate(45 100 100)"/>`,
|
||||
perChevronReversedLined: (line) =>
|
||||
`<rect x="15" y="115" width="200" height="200" transform="translate(-70 -70) rotate(225.001 100 100)"/><path d="${line}" transform="translate(-70.7 -70.7) rotate(225 100 100) scale(1 1)"/><path d="${line}" transform="translate(270.7 -70.7) rotate(-225 -100 100) scale(-1 1)"/>`,
|
||||
perCrossLined: (line) =>
|
||||
`<rect x="100" y="0" width="100" height="92.5"/><rect x="0" y="107.5" width="100" height="92.5"/><path d="${line}" transform="translate(0 50) scale(.5001)"/><path d="${line}" transform="translate(200 150) scale(-.5)"/>`,
|
||||
perPileLined: (line) =>
|
||||
`<path d="${line}" transform="translate(161.66 10) rotate(66.66 -100 100) scale(-1 1)"/><path d="${line}" transform="translate(38.33 10) rotate(-66.66 100 100)"/><polygon points="-2.15,0 84.15,200 115.85,200 202.15,0 200,200 0,200"/>`,
|
||||
// straight ordinaries
|
||||
fess: `<rect x="0" y="75" width="200" height="50"/>`,
|
||||
pale: `<rect x="75" y="0" width="50" height="200"/>`,
|
||||
|
|
@ -1591,85 +1620,182 @@ window.COArenderer = (function () {
|
|||
pilesInPoint: `<path d="M15,0 100,200 60,0Z M80,0 100,200 120,0Z M140,0 100,200 185,0Z"/>`,
|
||||
label: `<path d="m 46,54.8 6.6,-15.6 95.1,0 5.9,15.5 -16.8,0.1 4.5,-11.8 L 104,43 l 4.3,11.9 -16.8,0 4.3,-11.8 -37.2,0 4.5,11.8 -16.9,0 z"/>`,
|
||||
// lined ordinaries
|
||||
fessLined: line => `<path d="${line}" transform="translate(0 -25)"/><path d="${line}" transform="translate(0 25) rotate(180 100 100)"/><rect x="0" y="88" width="200" height="24" stroke="none"/>`,
|
||||
paleLined: line => `<path d="${line}" transform="rotate(-90 100 100) translate(0 -25)"/><path d="${line}" transform="rotate(90 100 100) translate(0 -25)"/><rect x="88" y="0" width="24" height="200" stroke="none"/>`,
|
||||
bendLined: line => `<path d="${line}" transform="translate(8 -18) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-28 18) rotate(225 110 100) scale(1.1 1)"/><rect x="0" y="88" width="200" height="24" transform="translate(-10 0) rotate(45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
bendSinisterLined: line => `<path d="${line}" transform="translate(-28 -18) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(8 18) rotate(-225 110 100) scale(1.1 1)"/><rect x="0" y="88" width="200" height="24" transform="translate(-10 0) rotate(-45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
chiefLined: line => `<path d="${line}" transform="translate(0,-25) rotate(180.00001 100 100)"/><rect width="200" height="62" stroke="none"/>`,
|
||||
barLined: line => `<path d="${line}" transform="translate(0,-12.5)"/><path d="${line}" transform="translate(0,12.5) rotate(180.00001 100 100)"/><rect x="0" y="94" width="200" height="12" stroke="none"/>`,
|
||||
gemelleLined: line => `<path d="${line}" transform="translate(0,-22.5)"/><path d="${line}" transform="translate(0,22.5) rotate(180.00001 100 100)"/>`,
|
||||
fessCotissedLined: line => `<path d="${line}" transform="translate(0 15) scale(1 .5)"/><path d="${line}" transform="translate(0 85) rotate(180 100 50) scale(1 .5)"/><rect x="0" y="80" width="200" height="40"/>`,
|
||||
fessDoubleCotissedLined: line => `<rect x="0" y="85" width="200" height="30"/><rect x="0" y="72.5" width="200" height="7.5"/><rect x="0" y="120" width="200" height="7.5"/><path d="${line}" transform="translate(0 10) scale(1 .5)"/><path d="${line}" transform="translate(0 90) rotate(180 100 50) scale(1 .5)"/>`,
|
||||
bendletLined: line => `<path d="${line}" transform="translate(2 -12) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-22 12) rotate(225 110 100) scale(1.1 1)"/><rect x="0" y="94" width="200" height="12" transform="translate(-10 0) rotate(45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
bendletSinisterLined: line => `<path d="${line}" transform="translate(-22 -12) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(2 12) rotate(-225 110 100) scale(1.1 1)"/><rect x="0" y="94" width="200" height="12" transform="translate(-10 0) rotate(-45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
terraceLined: line => `<path d="${line}" transform="translate(0,50)"/><rect x="0" y="164" width="200" height="36" stroke="none"/>`,
|
||||
crossLined: line => `<path d="${line}" transform="translate(0,-14.5)"/><path d="${line}" transform="rotate(180 100 100) translate(0,-14.5)"/><path d="${line}" transform="rotate(-90 100 100) translate(0,-14.5)"/><path d="${line}" transform="rotate(-270 100 100) translate(0,-14.5)"/>`,
|
||||
crossPartedLined: line => `<path d="${line}" transform="translate(0,-20)"/><path d="${line}" transform="rotate(180 100 100) translate(0,-20)"/><path d="${line}" transform="rotate(-90 100 100) translate(0,-20)"/><path d="${line}" transform="rotate(-270 100 100) translate(0,-20)"/>`,
|
||||
saltireLined: line => `<path d="${line}" transform="translate(0 -10) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-20 10) rotate(225 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-20 -10) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(0 10) rotate(-225 110 100) scale(1.1 1)"/>`,
|
||||
saltirePartedLined: line => `<path d="${line}" transform="translate(3 -13) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-23 13) rotate(225 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-23 -13) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(3 13) rotate(-225 110 100) scale(1.1 1)"/>`
|
||||
fessLined: (line) =>
|
||||
`<path d="${line}" transform="translate(0 -25)"/><path d="${line}" transform="translate(0 25) rotate(180 100 100)"/><rect x="0" y="88" width="200" height="24" stroke="none"/>`,
|
||||
paleLined: (line) =>
|
||||
`<path d="${line}" transform="rotate(-90 100 100) translate(0 -25)"/><path d="${line}" transform="rotate(90 100 100) translate(0 -25)"/><rect x="88" y="0" width="24" height="200" stroke="none"/>`,
|
||||
bendLined: (line) =>
|
||||
`<path d="${line}" transform="translate(8 -18) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-28 18) rotate(225 110 100) scale(1.1 1)"/><rect x="0" y="88" width="200" height="24" transform="translate(-10 0) rotate(45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
bendSinisterLined: (line) =>
|
||||
`<path d="${line}" transform="translate(-28 -18) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(8 18) rotate(-225 110 100) scale(1.1 1)"/><rect x="0" y="88" width="200" height="24" transform="translate(-10 0) rotate(-45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
chiefLined: (line) => `<path d="${line}" transform="translate(0,-25) rotate(180.00001 100 100)"/><rect width="200" height="62" stroke="none"/>`,
|
||||
barLined: (line) =>
|
||||
`<path d="${line}" transform="translate(0,-12.5)"/><path d="${line}" transform="translate(0,12.5) rotate(180.00001 100 100)"/><rect x="0" y="94" width="200" height="12" stroke="none"/>`,
|
||||
gemelleLined: (line) => `<path d="${line}" transform="translate(0,-22.5)"/><path d="${line}" transform="translate(0,22.5) rotate(180.00001 100 100)"/>`,
|
||||
fessCotissedLined: (line) =>
|
||||
`<path d="${line}" transform="translate(0 15) scale(1 .5)"/><path d="${line}" transform="translate(0 85) rotate(180 100 50) scale(1 .5)"/><rect x="0" y="80" width="200" height="40"/>`,
|
||||
fessDoubleCotissedLined: (line) =>
|
||||
`<rect x="0" y="85" width="200" height="30"/><rect x="0" y="72.5" width="200" height="7.5"/><rect x="0" y="120" width="200" height="7.5"/><path d="${line}" transform="translate(0 10) scale(1 .5)"/><path d="${line}" transform="translate(0 90) rotate(180 100 50) scale(1 .5)"/>`,
|
||||
bendletLined: (line) =>
|
||||
`<path d="${line}" transform="translate(2 -12) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-22 12) rotate(225 110 100) scale(1.1 1)"/><rect x="0" y="94" width="200" height="12" transform="translate(-10 0) rotate(45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
bendletSinisterLined: (line) =>
|
||||
`<path d="${line}" transform="translate(-22 -12) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(2 12) rotate(-225 110 100) scale(1.1 1)"/><rect x="0" y="94" width="200" height="12" transform="translate(-10 0) rotate(-45 110 100) scale(1.1 1)" stroke="none"/>`,
|
||||
terraceLined: (line) => `<path d="${line}" transform="translate(0,50)"/><rect x="0" y="164" width="200" height="36" stroke="none"/>`,
|
||||
crossLined: (line) =>
|
||||
`<path d="${line}" transform="translate(0,-14.5)"/><path d="${line}" transform="rotate(180 100 100) translate(0,-14.5)"/><path d="${line}" transform="rotate(-90 100 100) translate(0,-14.5)"/><path d="${line}" transform="rotate(-270 100 100) translate(0,-14.5)"/>`,
|
||||
crossPartedLined: (line) =>
|
||||
`<path d="${line}" transform="translate(0,-20)"/><path d="${line}" transform="rotate(180 100 100) translate(0,-20)"/><path d="${line}" transform="rotate(-90 100 100) translate(0,-20)"/><path d="${line}" transform="rotate(-270 100 100) translate(0,-20)"/>`,
|
||||
saltireLined: (line) =>
|
||||
`<path d="${line}" transform="translate(0 -10) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-20 10) rotate(225 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-20 -10) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(0 10) rotate(-225 110 100) scale(1.1 1)"/>`,
|
||||
saltirePartedLined: (line) =>
|
||||
`<path d="${line}" transform="translate(3 -13) rotate(45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-23 13) rotate(225 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(-23 -13) rotate(-45 110 100) scale(1.1 1)"/><path d="${line}" transform="translate(3 13) rotate(-225 110 100) scale(1.1 1)"/>`
|
||||
};
|
||||
|
||||
const patterns = {
|
||||
semy: (p, c1, c2, size, chargeId) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 200 200" stroke="#000"><rect width="200" height="200" fill="${c1}" stroke="none"/><g fill="${c2}"><use transform="translate(-100 -50)" href="#${chargeId}"/><use transform="translate(100 -50)" href="#${chargeId}"/><use transform="translate(0 50)" href="#${chargeId}"/></g></pattern>`,
|
||||
vair: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.25}" viewBox="0 0 25 50" stroke="#000" stroke-width=".2"><rect width="25" height="25" fill="${c1}" stroke="none"/><path d="m12.5,0 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c2}"/><rect x="0" y="25" width="25" height="25" fill="${c2}" stroke="none"/><path d="m25,25 l-6.25,6.25 v12.5 l-6.25,6.25 l-6.25,-6.25 v-12.5 l-6.25,-6.25 z" fill="${c1}"/><path d="M0 50 h25" fill="none"/></pattern>`,
|
||||
counterVair: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.25}" viewBox="0 0 25 50" stroke="#000" stroke-width=".2"><rect width="25" height="50" fill="${c2}" stroke="none"/><path d="m 12.5,0 6.25,6.25 v 12.5 L 25,25 18.75,31.25 v 12.5 L 12.5,50 6.25,43.75 V 31.25 L 0,25 6.25,18.75 V 6.25 Z" fill="${c1}"/></pattern>`,
|
||||
vairInPale: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 25 25"><rect width="25" height="25" fill="${c1}"/><path d="m12.5,0 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c2}" stroke="#000" stroke-width=".2"/></pattern>`,
|
||||
vairEnPointe: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.25}" viewBox="0 0 25 50"><rect width="25" height="25" fill="${c2}"/><path d="m12.5,0 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c1}"/><rect x="0" y="25" width="25" height="25" fill="${c1}" stroke-width="1" stroke="${c1}"/><path d="m12.5,25 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c2}"/></pattern>`,
|
||||
vairAncien: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c1}"/><path fill="${c2}" stroke="none" d="m 0,90 c 10,0 25,-5 25,-40 0,-25 10,-40 25,-40 15,0 25,15 25,40 0,35 15,40 25,40 v 10 H 0 Z"/><path fill="none" stroke="#000" d="M 0,90 c 10,0 25,-5 25,-40 0,-35 15,-40 25,-40 10,0 25,5 25,40 0,35 15,40 25,40 M0,100 h100"/></pattern>`,
|
||||
potent: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 200 200" stroke="#000"><rect width="200" height="100" fill="${c1}" stroke="none"/><rect y="100" width="200" height="100" fill="${c2}" stroke="none"/><path d="m25 50h50v-50h50v50h50v50h-150z" fill="${c2}"/><path d="m25 100v50h50v50h50v-50h50v-50z" fill="${c1}"/><path d="m0 0h200 M0 100h200" fill="none"/></pattern>`,
|
||||
counterPotent: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 200 200" stroke="none"><rect width="200" height="200" fill="${c1}"/><path d="m25 50h50v-50h50v50h50v100h-50v50h-50v-50h-50v-50z" fill="${c2}"/><path d="m0 0h200 M0 100h200 M0 200h200"/></pattern>`,
|
||||
potentInPale: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.0625}" viewBox="0 0 200 100" stroke-width="1"><rect width="200" height="100" fill="${c1}" stroke="none"/><path d="m25 50h50v-50h50v50h50v50h-150z" fill="${c2}" stroke="#000"/><path d="m0 0h200 M0 100h200" fill="none" stroke="#000"/></pattern>`,
|
||||
potentEnPointe: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 200 200" stroke="none"><rect width="200" height="200" fill="${c1}"/><path d="m0 0h25v50h50v50h50v-50h50v-50h25v100h-25v50h-50v50h-50v-50h-50v-50h-25v-100" fill="${c2}"/></pattern>`,
|
||||
semy: (p, c1, c2, size, chargeId) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" stroke="#000"><rect width="200" height="200" fill="${c1}" stroke="none"/><g fill="${c2}"><use transform="translate(-100 -50)" href="#${chargeId}"/><use transform="translate(100 -50)" href="#${chargeId}"/><use transform="translate(0 50)" href="#${chargeId}"/></g></pattern>`,
|
||||
vair: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 25 50" stroke="#000" stroke-width=".2"><rect width="25" height="25" fill="${c1}" stroke="none"/><path d="m12.5,0 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c2}"/><rect x="0" y="25" width="25" height="25" fill="${c2}" stroke="none"/><path d="m25,25 l-6.25,6.25 v12.5 l-6.25,6.25 l-6.25,-6.25 v-12.5 l-6.25,-6.25 z" fill="${c1}"/><path d="M0 50 h25" fill="none"/></pattern>`,
|
||||
counterVair: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 25 50" stroke="#000" stroke-width=".2"><rect width="25" height="50" fill="${c2}" stroke="none"/><path d="m 12.5,0 6.25,6.25 v 12.5 L 25,25 18.75,31.25 v 12.5 L 12.5,50 6.25,43.75 V 31.25 L 0,25 6.25,18.75 V 6.25 Z" fill="${c1}"/></pattern>`,
|
||||
vairInPale: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 25 25"><rect width="25" height="25" fill="${c1}"/><path d="m12.5,0 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c2}" stroke="#000" stroke-width=".2"/></pattern>`,
|
||||
vairEnPointe: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 25 50"><rect width="25" height="25" fill="${c2}"/><path d="m12.5,0 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c1}"/><rect x="0" y="25" width="25" height="25" fill="${c1}" stroke-width="1" stroke="${c1}"/><path d="m12.5,25 l6.25,6.25 v12.5 l6.25,6.25 h-25 l6.25,-6.25 v-12.5 z" fill="${c2}"/></pattern>`,
|
||||
vairAncien: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c1}"/><path fill="${c2}" stroke="none" d="m 0,90 c 10,0 25,-5 25,-40 0,-25 10,-40 25,-40 15,0 25,15 25,40 0,35 15,40 25,40 v 10 H 0 Z"/><path fill="none" stroke="#000" d="M 0,90 c 10,0 25,-5 25,-40 0,-35 15,-40 25,-40 10,0 25,5 25,40 0,35 15,40 25,40 M0,100 h100"/></pattern>`,
|
||||
potent: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" stroke="#000"><rect width="200" height="100" fill="${c1}" stroke="none"/><rect y="100" width="200" height="100" fill="${c2}" stroke="none"/><path d="m25 50h50v-50h50v50h50v50h-150z" fill="${c2}"/><path d="m25 100v50h50v50h50v-50h50v-50z" fill="${c1}"/><path d="m0 0h200 M0 100h200" fill="none"/></pattern>`,
|
||||
counterPotent: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" stroke="none"><rect width="200" height="200" fill="${c1}"/><path d="m25 50h50v-50h50v50h50v100h-50v50h-50v-50h-50v-50z" fill="${c2}"/><path d="m0 0h200 M0 100h200 M0 200h200"/></pattern>`,
|
||||
potentInPale: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.0625
|
||||
}" viewBox="0 0 200 100" stroke-width="1"><rect width="200" height="100" fill="${c1}" stroke="none"/><path d="m25 50h50v-50h50v50h50v50h-150z" fill="${c2}" stroke="#000"/><path d="m0 0h200 M0 100h200" fill="none" stroke="#000"/></pattern>`,
|
||||
potentEnPointe: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" stroke="none"><rect width="200" height="200" fill="${c1}"/><path d="m0 0h25v50h50v50h50v-50h50v-50h25v100h-25v50h-50v50h-50v-50h-50v-50h-25v-100" fill="${c2}"/></pattern>`,
|
||||
ermine: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 200 200" fill="${c2}"><rect width="200" height="200" fill="${c1}"/><g stroke="none" fill="${c2}"><g transform="translate(-100 -50)"><path d="m100 81.1c-4.25 17.6-12.7 29.8-21.2 38.9 3.65-0.607 7.9-3.04 11.5-5.47-2.42 4.86-4.86 8.51-7.3 12.7 1.82-0.607 6.07-4.86 12.7-10.9 1.21 8.51 2.42 17.6 4.25 23.6 1.82-5.47 3.04-15.2 4.25-23.6 3.65 3.65 7.3 7.9 12.7 10.9l-7.9-13.3c3.65 1.82 7.9 4.86 11.5 6.07-9.11-9.11-17-21.2-20.6-38.9z"/><path d="m82.4 81.7c-0.607-0.607-6.07 2.42-9.72-4.25 7.9 6.68 15.2-7.3 21.8 1.82 1.82 4.25-6.68 10.9-12.1 2.42z"/><path d="m117 81.7c0.607-1.21 6.07 2.42 9.11-4.86-7.3 7.3-15.2-7.3-21.2 2.42-1.82 4.25 6.68 10.9 12.1 2.42z"/><path d="m101 66.5c-1.02-0.607 3.58-4.25-3.07-8.51 5.63 7.9-10.2 10.9-1.54 17.6 3.58 2.42 12.2-2.42 4.6-9.11z"/></g><g transform="translate(100 -50)"><path d="m100 81.1c-4.25 17.6-12.7 29.8-21.2 38.9 3.65-0.607 7.9-3.04 11.5-5.47-2.42 4.86-4.86 8.51-7.3 12.7 1.82-0.607 6.07-4.86 12.7-10.9 1.21 8.51 2.42 17.6 4.25 23.6 1.82-5.47 3.04-15.2 4.25-23.6 3.65 3.65 7.3 7.9 12.7 10.9l-7.9-13.3c3.65 1.82 7.9 4.86 11.5 6.07-9.11-9.11-17-21.2-20.6-38.9z"/><path d="m82.4 81.7c-0.607-0.607-6.07 2.42-9.72-4.25 7.9 6.68 15.2-7.3 21.8 1.82 1.82 4.25-6.68 10.9-12.1 2.42z"/><path d="m117 81.7c0.607-1.21 6.07 2.42 9.11-4.86-7.3 7.3-15.2-7.3-21.2 2.42-1.82 4.25 6.68 10.9 12.1 2.42z"/><path d="m101 66.5c-1.02-0.607 3.58-4.25-3.07-8.51 5.63 7.9-10.2 10.9-1.54 17.6 3.58 2.42 12.2-2.42 4.6-9.11z"/></g><g transform="translate(0 50)"><path d="m100 81.1c-4.25 17.6-12.7 29.8-21.2 38.9 3.65-0.607 7.9-3.04 11.5-5.47-2.42 4.86-4.86 8.51-7.3 12.7 1.82-0.607 6.07-4.86 12.7-10.9 1.21 8.51 2.42 17.6 4.25 23.6 1.82-5.47 3.04-15.2 4.25-23.6 3.65 3.65 7.3 7.9 12.7 10.9l-7.9-13.3c3.65 1.82 7.9 4.86 11.5 6.07-9.11-9.11-17-21.2-20.6-38.9z"/><path d="m82.4 81.7c-0.607-0.607-6.07 2.42-9.72-4.25 7.9 6.68 15.2-7.3 21.8 1.82 1.82 4.25-6.68 10.9-12.1 2.42z"/><path d="m117 81.7c0.607-1.21 6.07 2.42 9.11-4.86-7.3 7.3-15.2-7.3-21.2 2.42-1.82 4.25 6.68 10.9 12.1 2.42z"/><path d="m101 66.5c-1.02-0.607 3.58-4.25-3.07-8.51 5.63 7.9-10.2 10.9-1.54 17.6 3.58 2.42 12.2-2.42 4.6-9.11z"/></g></g></pattern>`,
|
||||
chequy: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.25}" height="${size * 0.25}" viewBox="0 0 50 50" fill="${c2}"><rect width="50" height="50"/><rect width="25" height="25" fill="${c1}"/><rect x="25" y="25" width="25" height="25" fill="${c1}"/></pattern>`,
|
||||
lozengy: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 50 50"><rect width="50" height="50" fill="${c1}"/><polygon points="25,0 50,25 25,50 0,25" fill="${c2}"/></pattern>`,
|
||||
fusily: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.25}" viewBox="0 0 50 100"><rect width="50" height="100" fill="${c2}"/><polygon points="25,0 50,50 25,100 0,50" fill="${c1}"/></pattern>`,
|
||||
pally: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.5}" height="${size * 0.125}" viewBox="0 0 100 25"><rect width="100" height="25" fill="${c2}"/><rect x="25" y="0" width="25" height="25" fill="${c1}"/><rect x="75" y="0" width="25" height="25" fill="${c1}"/></pattern>`,
|
||||
barry: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.5}" viewBox="0 0 25 100"><rect width="25" height="100" fill="${c2}"/><rect x="0" y="25" width="25" height="25" fill="${c1}"/><rect x="0" y="75" width="25" height="25" fill="${c1}"/></pattern>`,
|
||||
gemelles: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 50 50"><rect width="50" height="50" fill="${c1}"/><rect y="5" width="50" height="10" fill="${c2}"/><rect y="40" width="50" height="10" fill="${c2}"/></pattern>`,
|
||||
bendy: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.5}" height="${size * 0.5}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c1}"/><polygon points="0,25 75,100 25,100 0,75" fill="${c2}"/><polygon points="25,0 75,0 100,25 100,75" fill="${c2}"/></pattern>`,
|
||||
bendySinister: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.5}" height="${size * 0.5}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c2}"/><polygon points="0,25 25,0 75,0 0,75" fill="${c1}"/><polygon points="25,100 100,25 100,75 75,100" fill="${c1}"/></pattern>`,
|
||||
palyBendy: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.6258}" height="${size * 0.3576}" viewBox="0 0 175 100"><rect y="0" x="0" width="175" height="100" fill="${c2}"/><g fill="${c1}"><path d="m0 20 35 30v50l-35-30z"/><path d="m35 0 35 30v50l-35-30z"/><path d="m70 0h23l12 10v50l-35-30z"/><path d="m70 80 23 20h-23z"/><path d="m105 60 35 30v10h-35z"/><path d="m105 0h35v40l-35-30z"/><path d="m 140,40 35,30 v 30 h -23 l -12,-10z"/><path d="M 175,0 V 20 L 152,0 Z"/></g></pattern>`,
|
||||
barryBendy: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.3572}" height="${size * 0.6251}" viewBox="0 0 100 175"><rect width="100" height="175" fill="${c2}"/><g fill="${c1}"><path d="m20 0 30 35h50l-30-35z"/><path d="m0 35 30 35h50l-30-35z"/><path d="m0 70v23l10 12h50l-30-35z"/><path d="m80 70 20 23v-23z"/><path d="m60 105 30 35h10v-35z"/><path d="m0 105v35h40l-30-35z"/><path d="m 40,140 30,35 h 30 v -23 l -10,-12 z"/><path d="m0 175h20l-20-23z"/></g></pattern>`,
|
||||
pappellony: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c1}"/><circle cx="0" cy="51" r="45" stroke="${c2}" fill="${c1}" stroke-width="10"/><circle cx="100" cy="51" r="45" stroke="${c2}" fill="${c1}" stroke-width="10"/><circle cx="50" cy="1" r="45" stroke="${c2}" fill="${c1}" stroke-width="10"/></pattern>`,
|
||||
pappellony2: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 100 100" stroke="#000" stroke-width="2"><rect width="100" height="100" fill="${c1}" stroke="none"/><circle cy="50" r="49" fill="${c2}"/><circle cx="100" cy="50" r="49" fill="${c2}"/><circle cx="50" cy="0" r="49" fill="${c1}"/></pattern>`,
|
||||
scaly: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 100 100" stroke="#000"><rect width="100" height="100" fill="${c1}" stroke="none"/><path d="M 0,84 C -40,84 -50,49 -50,49 -50,79 -27,99 0,99 27,99 50,79 50,49 50,49 40,84 0,84 Z" fill="${c2}"/><path d="M 100,84 C 60,84 50,49 50,49 c 0,30 23,50 50,50 27,0 50,-20 50,-50 0,0 -10,35 -50,35 z" fill="${c2}"/><path d="M 50,35 C 10,35 0,0 0,0 0,30 23,50 50,50 77,50 100,30 100,0 100,0 90,35 50,35 Z" fill="${c2}"/></pattern>`,
|
||||
chequy: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.25}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 50 50" fill="${c2}"><rect width="50" height="50"/><rect width="25" height="25" fill="${c1}"/><rect x="25" y="25" width="25" height="25" fill="${c1}"/></pattern>`,
|
||||
lozengy: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 50 50"><rect width="50" height="50" fill="${c1}"/><polygon points="25,0 50,25 25,50 0,25" fill="${c2}"/></pattern>`,
|
||||
fusily: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 50 100"><rect width="50" height="100" fill="${c2}"/><polygon points="25,0 50,50 25,100 0,50" fill="${c1}"/></pattern>`,
|
||||
pally: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.5}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 25"><rect width="100" height="25" fill="${c2}"/><rect x="25" y="0" width="25" height="25" fill="${c1}"/><rect x="75" y="0" width="25" height="25" fill="${c1}"/></pattern>`,
|
||||
barry: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.5
|
||||
}" viewBox="0 0 25 100"><rect width="25" height="100" fill="${c2}"/><rect x="0" y="25" width="25" height="25" fill="${c1}"/><rect x="0" y="75" width="25" height="25" fill="${c1}"/></pattern>`,
|
||||
gemelles: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 50 50"><rect width="50" height="50" fill="${c1}"/><rect y="5" width="50" height="10" fill="${c2}"/><rect y="40" width="50" height="10" fill="${c2}"/></pattern>`,
|
||||
bendy: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.5}" height="${
|
||||
size * 0.5
|
||||
}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c1}"/><polygon points="0,25 75,100 25,100 0,75" fill="${c2}"/><polygon points="25,0 75,0 100,25 100,75" fill="${c2}"/></pattern>`,
|
||||
bendySinister: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.5}" height="${
|
||||
size * 0.5
|
||||
}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c2}"/><polygon points="0,25 25,0 75,0 0,75" fill="${c1}"/><polygon points="25,100 100,25 100,75 75,100" fill="${c1}"/></pattern>`,
|
||||
palyBendy: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.6258}" height="${
|
||||
size * 0.3576
|
||||
}" viewBox="0 0 175 100"><rect y="0" x="0" width="175" height="100" fill="${c2}"/><g fill="${c1}"><path d="m0 20 35 30v50l-35-30z"/><path d="m35 0 35 30v50l-35-30z"/><path d="m70 0h23l12 10v50l-35-30z"/><path d="m70 80 23 20h-23z"/><path d="m105 60 35 30v10h-35z"/><path d="m105 0h35v40l-35-30z"/><path d="m 140,40 35,30 v 30 h -23 l -12,-10z"/><path d="M 175,0 V 20 L 152,0 Z"/></g></pattern>`,
|
||||
barryBendy: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.3572}" height="${
|
||||
size * 0.6251
|
||||
}" viewBox="0 0 100 175"><rect width="100" height="175" fill="${c2}"/><g fill="${c1}"><path d="m20 0 30 35h50l-30-35z"/><path d="m0 35 30 35h50l-30-35z"/><path d="m0 70v23l10 12h50l-30-35z"/><path d="m80 70 20 23v-23z"/><path d="m60 105 30 35h10v-35z"/><path d="m0 105v35h40l-30-35z"/><path d="m 40,140 30,35 h 30 v -23 l -10,-12 z"/><path d="m0 175h20l-20-23z"/></g></pattern>`,
|
||||
pappellony: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100"><rect width="100" height="100" fill="${c1}"/><circle cx="0" cy="51" r="45" stroke="${c2}" fill="${c1}" stroke-width="10"/><circle cx="100" cy="51" r="45" stroke="${c2}" fill="${c1}" stroke-width="10"/><circle cx="50" cy="1" r="45" stroke="${c2}" fill="${c1}" stroke-width="10"/></pattern>`,
|
||||
pappellony2: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100" stroke="#000" stroke-width="2"><rect width="100" height="100" fill="${c1}" stroke="none"/><circle cy="50" r="49" fill="${c2}"/><circle cx="100" cy="50" r="49" fill="${c2}"/><circle cx="50" cy="0" r="49" fill="${c1}"/></pattern>`,
|
||||
scaly: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100" stroke="#000"><rect width="100" height="100" fill="${c1}" stroke="none"/><path d="M 0,84 C -40,84 -50,49 -50,49 -50,79 -27,99 0,99 27,99 50,79 50,49 50,49 40,84 0,84 Z" fill="${c2}"/><path d="M 100,84 C 60,84 50,49 50,49 c 0,30 23,50 50,50 27,0 50,-20 50,-50 0,0 -10,35 -50,35 z" fill="${c2}"/><path d="M 50,35 C 10,35 0,0 0,0 0,30 23,50 50,50 77,50 100,30 100,0 100,0 90,35 50,35 Z" fill="${c2}"/></pattern>`,
|
||||
plumetty: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 50 100" stroke-width=".8"><rect width="50" height="100" fill="${c2}" stroke="none"/><path fill="${c1}" stroke="none" d="M 25,100 C 44,88 49.5,74 50,50 33.5,40 25,25 25,4e-7 25,25 16.5,40 0,50 0.5,74 6,88 25,100 Z"/><path fill="none" stroke="${c2}" d="m17 40c5.363 2.692 10.7 2.641 16 0m-19 7c7.448 4.105 14.78 3.894 22 0m-27 7c6-2 10.75 3.003 16 3 5.412-0.0031 10-5 16-3m-35 9c4-7 12 3 19 2 7 1 15-9 19-2m-35 6c6-2 11 3 16 3s10-5 16-3m-30 7c8 0 8 3 14 3s7-3 14-3m-25 8c7.385 4.048 14.72 3.951 22 0m-19 8c5.455 2.766 10.78 2.566 16 0m-8 6v-78"/><g fill="none" stroke="${c1}"><path d="m42 90c2.678 1.344 5.337 2.004 8 2m-11 5c3.686 2.032 7.344 3.006 10.97 3m0.0261-1.2e-4v-30"/><path d="m0 92c2.689 0.0045 5.328-0.6687 8-2m-8 10c3.709-0.0033 7.348-1.031 11-3m-11 3v-30"/><path d="m0 7c5.412-0.0031 10-5 16-3m-16 11c7 1 15-9 19-2m-19 9c5 0 10-5 16-3m-16 10c6 0 7-3 14-3m-14.02 11c3.685-0.002185 7.357-1.014 11.02-3m-11 10c2.694-0.01117 5.358-0.7036 7.996-2m-8 6v-48"/><path d="m34 4c6-2 10.75 3.003 16 3m-19 6c4-7 12 3 19 2m-16 4c6-2 11 3 16 3m-14 4c8 0 8 3 14 3m-11 5c3.641 1.996 7.383 2.985 11 3m-8 5c2.762 1.401 5.303 2.154 8.002 2.112m-0.00154 3.888v-48"/></g></pattern>`,
|
||||
masoned: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.125}" height="${size * 0.125}" viewBox="0 0 100 100" fill="none"><rect width="100" height="100" fill="${c1}"/><rect width="100" height="50" stroke="${c2}" stroke-width="4"/><line x1="50" y1="50" x2="50" y2="100" stroke="${c2}" stroke-width="5"/></pattern>`,
|
||||
fretty: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.2}" height="${size * 0.2}" viewBox="0 0 140 140" stroke="#000" stroke-width="2"><rect width="140" height="140" fill="${c1}" stroke="none"/><path d="m-15 5 150 150 20-20-150-150z" fill="${c2}"/><path d="m10 150 140-140-20-20-140 140z" fill="${c2}" stroke="none"/><path d="m0 120 20 20 120-120-20-20z" fill="none"/></pattern>`,
|
||||
grillage: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.25}" height="${size * 0.25}" viewBox="0 0 200 200" stroke="#000" stroke-width="2"><rect width="200" height="200" fill="${c1}" stroke="none"/><path d="m205 65v-30h-210v30z" fill="${c2}"/><path d="m65-5h-30v210h30z" fill="${c2}"/><path d="m205 165v-30h-210v30z" fill="${c2}"/><path d="m165,65h-30v140h30z" fill="${c2}"/><path d="m 165,-5h-30v40h30z" fill="${c2}"/></pattern>`,
|
||||
chainy: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.167}" height="${size * 0.167}" viewBox="0 0 200 200" stroke="#000" stroke-width="2"><rect x="-6.691e-6" width="200" height="200" fill="${c1}" stroke="none"/><path d="m155-5-20-20-160 160 20 20z" fill="${c2}"/><path d="m45 205 160-160 20 20-160 160z" fill="${c2}"/><path d="m45-5 20-20 160 160-20 20-160-160" fill="${c2}"/><path d="m-5 45-20 20 160 160 20-20-160-160" fill="${c2}"/></pattern>`,
|
||||
masoned: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.125}" height="${
|
||||
size * 0.125
|
||||
}" viewBox="0 0 100 100" fill="none"><rect width="100" height="100" fill="${c1}"/><rect width="100" height="50" stroke="${c2}" stroke-width="4"/><line x1="50" y1="50" x2="50" y2="100" stroke="${c2}" stroke-width="5"/></pattern>`,
|
||||
fretty: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.2}" height="${
|
||||
size * 0.2
|
||||
}" viewBox="0 0 140 140" stroke="#000" stroke-width="2"><rect width="140" height="140" fill="${c1}" stroke="none"/><path d="m-15 5 150 150 20-20-150-150z" fill="${c2}"/><path d="m10 150 140-140-20-20-140 140z" fill="${c2}" stroke="none"/><path d="m0 120 20 20 120-120-20-20z" fill="none"/></pattern>`,
|
||||
grillage: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.25}" height="${
|
||||
size * 0.25
|
||||
}" viewBox="0 0 200 200" stroke="#000" stroke-width="2"><rect width="200" height="200" fill="${c1}" stroke="none"/><path d="m205 65v-30h-210v30z" fill="${c2}"/><path d="m65-5h-30v210h30z" fill="${c2}"/><path d="m205 165v-30h-210v30z" fill="${c2}"/><path d="m165,65h-30v140h30z" fill="${c2}"/><path d="m 165,-5h-30v40h30z" fill="${c2}"/></pattern>`,
|
||||
chainy: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.167}" height="${
|
||||
size * 0.167
|
||||
}" viewBox="0 0 200 200" stroke="#000" stroke-width="2"><rect x="-6.691e-6" width="200" height="200" fill="${c1}" stroke="none"/><path d="m155-5-20-20-160 160 20 20z" fill="${c2}"/><path d="m45 205 160-160 20 20-160 160z" fill="${c2}"/><path d="m45-5 20-20 160 160-20 20-160-160" fill="${c2}"/><path d="m-5 45-20 20 160 160 20-20-160-160" fill="${c2}"/></pattern>`,
|
||||
maily: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.167}" height="${
|
||||
size * 0.167
|
||||
}" viewBox="0 0 200 200" stroke="#000" stroke-width="1.2"><path fill="${c1}" stroke="none" d="M0 0h200v200H0z"/><g fill="${c2}"><path d="m80-2c-5.27e-4 2.403-0.1094 6.806-0.3262 9.199 5.014-1.109 10.1-1.768 15.19-2.059 0.09325-1.712 0.1401-5.426 0.1406-7.141z"/><path d="m100 5a95 95 0 0 0-95 95 95 95 0 0 0 95 95 95 95 0 0 0 95-95 95 95 0 0 0-95-95zm0 15a80 80 0 0 1 80 80 80 80 0 0 1-80 80 80 80 0 0 1-80-80 80 80 0 0 1 80-80z"/><path d="m92.8 20.33c-5.562 0.4859-11.04 1.603-16.34 3.217-7.793 25.31-27.61 45.12-52.91 52.91-5.321 1.638-10.8 2.716-16.34 3.217-2.394 0.2168-6.796 0.3256-9.199 0.3262v15c1.714-4.79e-4 5.429-0.04737 7.141-0.1406 5.109-0.2761 10.19-0.9646 15.19-2.059 36.24-7.937 64.54-36.24 72.47-72.47z"/><path d="m202 80c-2.403-5.31e-4 -6.806-0.1094-9.199-0.3262 1.109 5.014 1.768 10.1 2.059 15.19 1.712 0.09326 5.426 0.1401 7.141 0.1406z"/><path d="m179.7 92.8c-0.4859-5.562-1.603-11.04-3.217-16.34-25.31-7.793-45.12-27.61-52.91-52.91-1.638-5.321-2.716-10.8-3.217-16.34-0.2168-2.394-0.3256-6.796-0.3262-9.199h-15c4.8e-4 1.714 0.0474 5.429 0.1406 7.141 0.2761 5.109 0.9646 10.19 2.059 15.19 7.937 36.24 36.24 64.54 72.47 72.47z"/><path d="m120 202c5.3e-4 -2.403 0.1094-6.806 0.3262-9.199-5.014 1.109-10.1 1.768-15.19 2.059-0.0933 1.712-0.1402 5.426-0.1406 7.141z"/><path d="m107.2 179.7c5.562-0.4859 11.04-1.603 16.34-3.217 7.793-25.31 27.61-45.12 52.91-52.91 5.321-1.638 10.8-2.716 16.34-3.217 2.394-0.2168 6.796-0.3256 9.199-0.3262v-15c-1.714 4.7e-4 -5.429 0.0474-7.141 0.1406-5.109 0.2761-10.19 0.9646-15.19 2.059-36.24 7.937-64.54 36.24-72.47 72.47z"/><path d="m -2,120 c 2.403,5.4e-4 6.806,0.1094 9.199,0.3262 -1.109,-5.014 -1.768,-10.1 -2.059,-15.19 -1.712,-0.0933 -5.426,-0.1402 -7.141,-0.1406 z"/><path d="m 20.33,107.2 c 0.4859,5.562 1.603,11.04 3.217,16.34 25.31,7.793 45.12,27.61 52.91,52.91 1.638,5.321 2.716,10.8 3.217,16.34 0.2168,2.394 0.3256,6.796 0.3262,9.199 L 95,202 c -4.8e-4,-1.714 -0.0472,-5.44 -0.1404,-7.152 -0.2761,-5.109 -0.9646,-10.19 -2.059,-15.19 -7.937,-36.24 -36.24,-64.54 -72.47,-72.47 z"/></g></pattern>`,
|
||||
honeycombed: (p, c1, c2, size) => `<pattern id="${p}" width="${size * 0.143}" height="${size * 0.24514}" viewBox="0 0 70 120"><rect width="70" height="120" fill="${c1}"/><path d="M 70,0 V 20 L 35,40 m 35,80 V 100 L 35,80 M 0,120 V 100 L 35,80 V 40 L 0,20 V 0" stroke="${c2}" fill="none" stroke-width="3"/></pattern>`
|
||||
honeycombed: (p, c1, c2, size) =>
|
||||
`<pattern id="${p}" width="${size * 0.143}" height="${
|
||||
size * 0.24514
|
||||
}" viewBox="0 0 70 120"><rect width="70" height="120" fill="${c1}"/><path d="M 70,0 V 20 L 35,40 m 35,80 V 100 L 35,80 M 0,120 V 100 L 35,80 V 40 L 0,20 V 0" stroke="${c2}" fill="none" stroke-width="3"/></pattern>`
|
||||
};
|
||||
|
||||
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 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 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 viewBox = shieldBox[shield] || '0 0 200 200';
|
||||
|
||||
const shieldClip = `<clipPath id="${shield}_${id}"><path d="${shieldPath}"/></clipPath>`;
|
||||
const divisionClip = division ? `<clipPath id="divisionClip_${id}">${getTemplate(division.division, division.line)}</clipPath>` : "";
|
||||
const divisionClip = division ? `<clipPath id="divisionClip_${id}">${getTemplate(division.division, division.line)}</clipPath>` : '';
|
||||
const loadedCharges = await getCharges(coa, id, shieldPath);
|
||||
const loadedPatterns = getPatterns(coa, id);
|
||||
const blacklight = `<radialGradient id="backlight_${id}" cx="100%" cy="100%" r="150%"><stop stop-color="#fff" stop-opacity=".3" offset="0"/><stop stop-color="#fff" stop-opacity=".15" offset=".25"/><stop stop-color="#000" stop-opacity="0" offset="1"/></radialGradient>`;
|
||||
const field = `<rect x="0" y="0" width="200" height="200" fill="${clr(coa.t1)}"/>`;
|
||||
const divisionGroup = division ? templateDivision() : "";
|
||||
const divisionGroup = division ? templateDivision() : '';
|
||||
const overlay = `<path d="${shieldPath}" fill="url(#backlight_${id})" stroke="#333"/>`;
|
||||
|
||||
const svg = `<svg id="${id}" width="200" height="200" viewBox="${viewBox}">
|
||||
|
|
@ -1678,67 +1804,67 @@ window.COArenderer = (function () {
|
|||
${overlay}</svg>`;
|
||||
|
||||
// insert coa svg to defs
|
||||
document.getElementById("coas").insertAdjacentHTML("beforeend", svg);
|
||||
document.getElementById('coas').insertAdjacentHTML('beforeend', svg);
|
||||
return true;
|
||||
|
||||
function templateDivision() {
|
||||
let svg = "";
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
if (ordinary.divided === 'field') svg += templateOrdinary(ordinary, ordinary.t);
|
||||
else if (ordinary.divided === 'counter') svg += templateOrdinary(ordinary, tDiv);
|
||||
}
|
||||
|
||||
// In division part
|
||||
svg += `<g clip-path="url(#divisionClip_${id})"><rect x="0" y="0" width="200" height="200" fill="${clr(division.t)}"/>`;
|
||||
|
||||
for (const ordinary of ordinariesRegular) {
|
||||
if (ordinary.divided === "division") svg += templateOrdinary(ordinary, ordinary.t);
|
||||
else if (ordinary.divided === "counter") svg += templateOrdinary(ordinary, coa.t1);
|
||||
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);
|
||||
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);
|
||||
if (ordinary.divided === 'division') svg += templateOrdinary(ordinary, ordinary.t);
|
||||
else if (ordinary.divided === 'counter') svg += templateOrdinary(ordinary, coa.t1);
|
||||
}
|
||||
|
||||
return (svg += `</g>`);
|
||||
}
|
||||
|
||||
function templateAboveAll() {
|
||||
let svg = "";
|
||||
let svg = '';
|
||||
|
||||
ordinariesRegular
|
||||
.filter(o => !o.divided)
|
||||
.forEach(ordinary => {
|
||||
.filter((o) => !o.divided)
|
||||
.forEach((ordinary) => {
|
||||
svg += templateOrdinary(ordinary, ordinary.t);
|
||||
});
|
||||
|
||||
charges
|
||||
.filter(o => !o.divided || !division)
|
||||
.forEach(charge => {
|
||||
.filter((o) => !o.divided || !division)
|
||||
.forEach((charge) => {
|
||||
svg += templateCharge(charge, charge.t);
|
||||
});
|
||||
|
||||
ordinariesAboveCharges
|
||||
.filter(o => !o.divided)
|
||||
.forEach(ordinary => {
|
||||
.filter((o) => !o.divided)
|
||||
.forEach((ordinary) => {
|
||||
svg += templateOrdinary(ordinary, ordinary.t);
|
||||
});
|
||||
|
||||
|
|
@ -1748,17 +1874,17 @@ window.COArenderer = (function () {
|
|||
function templateOrdinary(ordinary, tincture) {
|
||||
const fill = clr(tincture);
|
||||
let svg = `<g fill="${fill}" stroke="none">`;
|
||||
if (ordinary.ordinary === "bordure") svg += `<path d="${shieldPath}" fill="none" stroke="${fill}" stroke-width="16.7%"/>`;
|
||||
else if (ordinary.ordinary === "orle") svg += `<path d="${shieldPath}" fill="none" stroke="${fill}" stroke-width="5%" transform="scale(.85)" transform-origin="center">`;
|
||||
if (ordinary.ordinary === 'bordure') svg += `<path d="${shieldPath}" fill="none" stroke="${fill}" stroke-width="16.7%"/>`;
|
||||
else if (ordinary.ordinary === 'orle') svg += `<path d="${shieldPath}" fill="none" stroke="${fill}" stroke-width="5%" transform="scale(.85)" transform-origin="center">`;
|
||||
else svg += getTemplate(ordinary.ordinary, ordinary.line);
|
||||
return svg + `</g>`;
|
||||
}
|
||||
|
||||
function templateCharge(charge, tincture) {
|
||||
const fill = clr(tincture);
|
||||
const chargePositions = [...new Set(charge.p)].filter(position => positions[position]);
|
||||
const chargePositions = [...new Set(charge.p)].filter((position) => positions[position]);
|
||||
|
||||
let svg = "";
|
||||
let svg = '';
|
||||
svg += `<g fill="${fill}" stroke="#000">`;
|
||||
for (const p of chargePositions) {
|
||||
const transform = getElTransform(charge, p);
|
||||
|
|
@ -1780,69 +1906,69 @@ window.COArenderer = (function () {
|
|||
};
|
||||
|
||||
async function getCharges(coa, id, shieldPath) {
|
||||
let charges = coa.charges ? coa.charges.map(charge => charge.charge) : []; // add charges
|
||||
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 `<g id="inescutcheon_${id}"><path transform="translate(66 66) scale(.34)" d="${shieldPath}"/></g>`;
|
||||
uniqueCharges.map(async (charge) => {
|
||||
if (charge === 'inescutcheon') return `<g id="inescutcheon_${id}"><path transform="translate(66 66) scale(.34)" d="${shieldPath}"/></g>`;
|
||||
const fetched = await fetchCharge(charge, id);
|
||||
return fetched;
|
||||
})
|
||||
);
|
||||
return fetchedCharges.join("");
|
||||
return fetchedCharges.join('');
|
||||
}
|
||||
|
||||
const url = PRODUCTION ? "./charges/" : "http://armoria.herokuapp.com/charges/"; // on local machine fetch files from server
|
||||
const url = location.hostname ? './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 => {
|
||||
const fetched = fetch(url + charge + '.svg')
|
||||
.then((res) => {
|
||||
if (res.ok) return res.text();
|
||||
else throw new Error("Cannot fetch charge");
|
||||
else throw new Error('Cannot fetch charge');
|
||||
})
|
||||
.then(text => {
|
||||
const html = document.createElement("html");
|
||||
.then((text) => {
|
||||
const html = document.createElement('html');
|
||||
html.innerHTML = text;
|
||||
const g = html.querySelector("g");
|
||||
g.setAttribute("id", charge + "_" + id);
|
||||
const g = html.querySelector('g');
|
||||
g.setAttribute('id', charge + '_' + id);
|
||||
return g.outerHTML;
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
.catch((err) => console.error(err));
|
||||
return fetched;
|
||||
}
|
||||
|
||||
function getPatterns(coa, id) {
|
||||
const isPattern = string => string.includes("-");
|
||||
const isPattern = (string) => string.includes('-');
|
||||
let patternsToAdd = [];
|
||||
if (coa.t1.includes("-")) patternsToAdd.push(coa.t1); // add field pattern
|
||||
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 "";
|
||||
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("-");
|
||||
.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);
|
||||
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("");
|
||||
.join('');
|
||||
}
|
||||
|
||||
function getSizeMod(size) {
|
||||
if (size === "small") return 0.8;
|
||||
if (size === "smaller") return 0.5;
|
||||
if (size === "smallest") return 0.25;
|
||||
if (size === "big") return 1.6;
|
||||
if (size === 'small') return 0.8;
|
||||
if (size === 'smaller') return 0.5;
|
||||
if (size === 'smallest') return 0.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 linedId = id + 'Lined';
|
||||
if (!line || line === 'straight' || !templates[linedId]) return templates[id];
|
||||
const linePath = lines[line];
|
||||
return templates[linedId](linePath);
|
||||
}
|
||||
|
|
@ -1862,8 +1988,8 @@ window.COArenderer = (function () {
|
|||
|
||||
// render coa if does not exist
|
||||
const trigger = async function (id, coa) {
|
||||
if (coa === "custom") {
|
||||
console.warn("Cannot render custom emblem", coa);
|
||||
if (coa === 'custom') {
|
||||
console.warn('Cannot render custom emblem', coa);
|
||||
return;
|
||||
}
|
||||
if (!coa) {
|
||||
|
|
@ -1874,15 +2000,15 @@ window.COArenderer = (function () {
|
|||
};
|
||||
|
||||
const add = function (type, i, coa, x, y) {
|
||||
const id = type + "COA" + i;
|
||||
const g = document.getElementById(type + "Emblems");
|
||||
const id = type + 'COA' + i;
|
||||
const g = document.getElementById(type + 'Emblems');
|
||||
|
||||
if (emblems.selectAll("use").size()) {
|
||||
const size = +g.getAttribute("font-size") || 50;
|
||||
if (emblems.selectAll('use').size()) {
|
||||
const size = +g.getAttribute('font-size') || 50;
|
||||
const use = `<use data-i="${i}" x="${x - size / 2}" y="${y - size / 2}" width="1em" height="1em" href="#${id}"/>`;
|
||||
g.insertAdjacentHTML("beforeend", use);
|
||||
g.insertAdjacentHTML('beforeend', use);
|
||||
}
|
||||
if (layerIsOn("toggleEmblems")) trigger(id, coa);
|
||||
if (layerIsOn('toggleEmblems')) trigger(id, coa);
|
||||
};
|
||||
|
||||
return {trigger, add, shieldPaths};
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.Cultures = (function () {
|
||||
let cells;
|
||||
|
||||
const generate = function () {
|
||||
TIME && console.time("generateCultures");
|
||||
TIME && console.time('generateCultures');
|
||||
cells = pack.cells;
|
||||
cells.culture = new Uint16Array(cells.i.length); // cell cultures
|
||||
let count = Math.min(+culturesInput.value, +culturesSet.selectedOptions[0].dataset.max);
|
||||
|
||||
const populated = cells.i.filter(i => cells.s[i]); // populated cells
|
||||
const populated = cells.i.filter((i) => cells.s[i]); // populated cells
|
||||
if (populated.length < count * 25) {
|
||||
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, shield: "round"}];
|
||||
pack.cultures = [{name: 'Wildlands', i: 0, base: 1, shield: 'round'}];
|
||||
alertMessage.innerHTML = `
|
||||
The climate is harsh and people cannot live in this world.<br>
|
||||
No cultures, states and burgs will be created.<br>
|
||||
Please consider changing climate settings in the World Configurator`;
|
||||
$("#alert").dialog({
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: "Extreme climate warning",
|
||||
title: 'Extreme climate warning',
|
||||
buttons: {
|
||||
Ok: function () {
|
||||
$(this).dialog("close");
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -35,12 +35,12 @@ window.Cultures = (function () {
|
|||
There are only ${populated.length} populated cells and it's insufficient livable area.<br>
|
||||
Only ${count} out of ${culturesInput.value} requested cultures will be generated.<br>
|
||||
Please consider changing climate settings in the World Configurator`;
|
||||
$("#alert").dialog({
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: "Extreme climate warning",
|
||||
title: 'Extreme climate warning',
|
||||
buttons: {
|
||||
Ok: function () {
|
||||
$(this).dialog("close");
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -50,11 +50,11 @@ window.Cultures = (function () {
|
|||
const cultures = (pack.cultures = selectCultures(count));
|
||||
const centers = d3.quadtree();
|
||||
const colors = getColors(count);
|
||||
const emblemShape = document.getElementById("emblemShape").value;
|
||||
const emblemShape = document.getElementById('emblemShape').value;
|
||||
|
||||
const codes = [];
|
||||
cultures.forEach(function (c, i) {
|
||||
const cell = (c.center = placeCenter(c.sort ? c.sort : i => cells.s[i]));
|
||||
const cell = (c.center = placeCenter(c.sort ? c.sort : (i) => cells.s[i]));
|
||||
centers.add(cells.p[cell]);
|
||||
c.i = i + 1;
|
||||
delete c.odd;
|
||||
|
|
@ -66,7 +66,7 @@ window.Cultures = (function () {
|
|||
c.code = abbreviate(c.name, codes);
|
||||
codes.push(c.code);
|
||||
cells.culture[cell] = i + 1;
|
||||
if (emblemShape === "random") c.shield = getRandomShield();
|
||||
if (emblemShape === 'random') c.shield = getRandomShield();
|
||||
});
|
||||
|
||||
function placeCenter(v) {
|
||||
|
|
@ -82,20 +82,20 @@ window.Cultures = (function () {
|
|||
}
|
||||
|
||||
// the first culture with id 0 is for wildlands
|
||||
cultures.unshift({name: "Wildlands", i: 0, base: 1, origin: null, shield: "round"});
|
||||
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");
|
||||
ERROR && console.error('Name base is empty, default nameBases will be applied');
|
||||
nameBases = Names.getNameBases();
|
||||
}
|
||||
|
||||
cultures.forEach(c => (c.base = c.base % nameBases.length));
|
||||
cultures.forEach((c) => (c.base = c.base % nameBases.length));
|
||||
|
||||
function selectCultures(c) {
|
||||
let def = getDefault(c);
|
||||
if (c === def.length) return def;
|
||||
if (def.every(d => d.odd === 1)) return def.splice(0, c);
|
||||
if (def.every((d) => d.odd === 1)) return def.splice(0, c);
|
||||
|
||||
const count = Math.min(c, def.length);
|
||||
const cultures = [];
|
||||
|
|
@ -113,28 +113,28 @@ window.Cultures = (function () {
|
|||
|
||||
// set culture type based on culture center position
|
||||
function defineCultureType(i) {
|
||||
if (cells.h[i] < 70 && [1, 2, 4].includes(cells.biome[i])) return "Nomadic"; // high penalty in forest biomes and near coastline
|
||||
if (cells.h[i] > 50) return "Highland"; // no penalty for hills and moutains, high for other elevations
|
||||
if (cells.h[i] < 70 && [1, 2, 4].includes(cells.biome[i])) return 'Nomadic'; // high penalty in forest biomes and near coastline
|
||||
if (cells.h[i] > 50) return 'Highland'; // no penalty for hills and moutains, high for other elevations
|
||||
const f = pack.features[cells.f[cells.haven[i]]]; // opposite feature
|
||||
if (f.type === "lake" && f.cells > 5) return "Lake"; // low water cross penalty and high for growth not along coastline
|
||||
if ((cells.harbor[i] && f.type !== "lake" && P(0.1)) || (cells.harbor[i] === 1 && P(0.6)) || (pack.features[cells.f[i]].group === "isle" && P(0.4))) return "Naval"; // low water cross penalty and high for non-along-coastline growth
|
||||
if (cells.r[i] && cells.fl[i] > 100) return "River"; // no River cross penalty, penalty for non-River growth
|
||||
if (cells.t[i] > 2 && [3, 7, 8, 9, 10, 12].includes(cells.biome[i])) return "Hunting"; // high penalty in non-native biomes
|
||||
return "Generic";
|
||||
if (f.type === 'lake' && f.cells > 5) return 'Lake'; // low water cross penalty and high for growth not along coastline
|
||||
if ((cells.harbor[i] && f.type !== 'lake' && P(0.1)) || (cells.harbor[i] === 1 && P(0.6)) || (pack.features[cells.f[i]].group === 'isle' && P(0.4))) return 'Naval'; // low water cross penalty and high for non-along-coastline growth
|
||||
if (cells.r[i] && cells.fl[i] > 100) return 'River'; // no River cross penalty, penalty for non-River growth
|
||||
if (cells.t[i] > 2 && [3, 7, 8, 9, 10, 12].includes(cells.biome[i])) return 'Hunting'; // high penalty in non-native biomes
|
||||
return 'Generic';
|
||||
}
|
||||
|
||||
function defineCultureExpansionism(type) {
|
||||
let base = 1; // Generic
|
||||
if (type === "Lake") base = 0.8;
|
||||
else if (type === "Naval") base = 1.5;
|
||||
else if (type === "River") base = 0.9;
|
||||
else if (type === "Nomadic") base = 1.5;
|
||||
else if (type === "Hunting") base = 0.7;
|
||||
else if (type === "Highland") base = 1.2;
|
||||
if (type === 'Lake') base = 0.8;
|
||||
else if (type === 'Naval') base = 1.5;
|
||||
else if (type === 'River') base = 0.9;
|
||||
else if (type === 'Nomadic') base = 1.5;
|
||||
else if (type === 'Hunting') base = 0.7;
|
||||
else if (type === 'Highland') base = 1.2;
|
||||
return rn(((Math.random() * powerInput.value) / 2 + 1) * base, 1);
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("generateCultures");
|
||||
TIME && console.timeEnd('generateCultures');
|
||||
};
|
||||
|
||||
const add = function (center) {
|
||||
|
|
@ -149,22 +149,22 @@ window.Cultures = (function () {
|
|||
} else {
|
||||
// add random culture besed on one of the current ones
|
||||
culture = rand(pack.cultures.length - 1);
|
||||
name = Names.getCulture(culture, 5, 8, "");
|
||||
name = Names.getCulture(culture, 5, 8, '');
|
||||
base = pack.cultures[culture].base;
|
||||
}
|
||||
const code = abbreviate(
|
||||
name,
|
||||
pack.cultures.map(c => c.code)
|
||||
pack.cultures.map((c) => c.code)
|
||||
);
|
||||
const i = pack.cultures.length;
|
||||
const color = d3.color(d3.scaleSequential(d3.interpolateRainbow)(Math.random())).hex();
|
||||
|
||||
// define emblem shape
|
||||
let shield = culture.shield;
|
||||
const emblemShape = document.getElementById("emblemShape").value;
|
||||
if (emblemShape === "random") shield = getRandomShield();
|
||||
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});
|
||||
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) {
|
||||
|
|
@ -175,156 +175,156 @@ window.Cultures = (function () {
|
|||
t = cells.t,
|
||||
h = cells.h,
|
||||
temp = grid.cells.temp;
|
||||
const n = cell => Math.ceil((s[cell] / sMax) * 3); // normalized cell score
|
||||
const n = (cell) => Math.ceil((s[cell] / sMax) * 3); // normalized cell score
|
||||
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
|
||||
const sf = (cell, fee = 4) => (cells.haven[cell] && pack.features[cells.f[cells.haven[cell]]].type !== 'lake' ? 1 : fee); // not on sea coast fee
|
||||
|
||||
if (culturesSet.value === "european") {
|
||||
if (culturesSet.value === 'european') {
|
||||
return [
|
||||
{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: 0.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: 0.05, sort: i => n(i) / td(i, 14), shield: "round"},
|
||||
{name: "Euskati", base: 20, odd: 0.05, sort: i => (n(i) / td(i, 15)) * h[i], shield: "oldFrench"},
|
||||
{name: "Keltan", base: 22, odd: 0.05, sort: i => (n(i) / td(i, 11) / bd(i, [6, 8])) * t[i], shield: "oval"}
|
||||
{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: 0.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: 0.05, sort: (i) => n(i) / td(i, 14), shield: 'round'},
|
||||
{name: 'Euskati', base: 20, odd: 0.05, sort: (i) => (n(i) / td(i, 15)) * h[i], shield: 'oldFrench'},
|
||||
{name: 'Keltan', base: 22, odd: 0.05, sort: (i) => (n(i) / td(i, 11) / bd(i, [6, 8])) * t[i], shield: 'oval'}
|
||||
];
|
||||
}
|
||||
|
||||
if (culturesSet.value === "oriental") {
|
||||
if (culturesSet.value === 'oriental') {
|
||||
return [
|
||||
{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: 0.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: 0.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: 0.2, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield: "vesicaPiscis"},
|
||||
{name: "Carnatic", base: 26, odd: 0.5, sort: i => n(i) / td(i, 26), shield: "round"},
|
||||
{name: "Vietic", base: 29, odd: 0.8, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield: "banner"},
|
||||
{name: "Guantzu", base: 30, odd: 0.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"}
|
||||
{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: 0.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: 0.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: 0.2, sort: (i) => n(i) / td(i, 24) / sf(i) / t[i], shield: 'vesicaPiscis'},
|
||||
{name: 'Carnatic', base: 26, odd: 0.5, sort: (i) => n(i) / td(i, 26), shield: 'round'},
|
||||
{name: 'Vietic', base: 29, odd: 0.8, sort: (i) => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield: 'banner'},
|
||||
{name: 'Guantzu', base: 30, odd: 0.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);
|
||||
if (culturesSet.value === 'english') {
|
||||
const getName = () => Names.getBase(1, 5, 9, '', 0);
|
||||
return [
|
||||
{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"}
|
||||
{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") {
|
||||
if (culturesSet.value === 'antique') {
|
||||
return [
|
||||
{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: 0.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) ** 0.5 / bd(i, [6, 8]), shield: "round"},
|
||||
{name: "Germanic", base: 0, odd: 1, sort: i => n(i) / td(i, 10) ** 0.5 / bd(i, [6, 8]), shield: "round"},
|
||||
{name: "Persian", base: 24, odd: 0.8, sort: i => (n(i) / td(i, 18)) * h[i], shield: "oval"}, // Iranian
|
||||
{name: "Scythian", base: 24, odd: 0.5, sort: i => n(i) / td(i, 11) ** 0.5 / bd(i, [4]), shield: "round"}, // Iranian
|
||||
{name: "Cantabrian", base: 20, odd: 0.5, sort: i => (n(i) / td(i, 16)) * h[i], shield: "oval"}, // Basque
|
||||
{name: "Estian", base: 9, odd: 0.2, sort: i => (n(i) / td(i, 5)) * t[i], shield: "pavise"}, // Finnic
|
||||
{name: "Carthaginian", base: 17, odd: 0.3, sort: i => n(i) / td(i, 19) / sf(i), shield: "oval"}, // Berber
|
||||
{name: "Mesopotamian", base: 23, odd: 0.2, sort: i => n(i) / td(i, 22) / bd(i, [1, 2, 3]), shield: "oval"} // 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: 0.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) ** 0.5 / bd(i, [6, 8]), shield: 'round'},
|
||||
{name: 'Germanic', base: 0, odd: 1, sort: (i) => n(i) / td(i, 10) ** 0.5 / bd(i, [6, 8]), shield: 'round'},
|
||||
{name: 'Persian', base: 24, odd: 0.8, sort: (i) => (n(i) / td(i, 18)) * h[i], shield: 'oval'}, // Iranian
|
||||
{name: 'Scythian', base: 24, odd: 0.5, sort: (i) => n(i) / td(i, 11) ** 0.5 / bd(i, [4]), shield: 'round'}, // Iranian
|
||||
{name: 'Cantabrian', base: 20, odd: 0.5, sort: (i) => (n(i) / td(i, 16)) * h[i], shield: 'oval'}, // Basque
|
||||
{name: 'Estian', base: 9, odd: 0.2, sort: (i) => (n(i) / td(i, 5)) * t[i], shield: 'pavise'}, // Finnic
|
||||
{name: 'Carthaginian', base: 17, odd: 0.3, sort: (i) => n(i) / td(i, 19) / sf(i), shield: 'oval'}, // Berber
|
||||
{name: 'Mesopotamian', base: 23, odd: 0.2, sort: (i) => n(i) / td(i, 22) / bd(i, [1, 2, 3]), shield: 'oval'} // Mesopotamian
|
||||
];
|
||||
}
|
||||
|
||||
if (culturesSet.value === "highFantasy") {
|
||||
if (culturesSet.value === 'highFantasy') {
|
||||
return [
|
||||
// fantasy races
|
||||
{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: 0.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: 0.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: 0.5, sort: i => (h[i] * t[i]) / bd(i, [1, 2, 10, 11]), shield: "moriaOrc"}, // Orc
|
||||
{name: "Yotunn (Giants)", base: 38, odd: 0.7, sort: i => td(i, -10), shield: "pavise"}, // Giant
|
||||
{name: "Rake (Drakonic)", base: 39, odd: 0.7, sort: i => -s[i], shield: "fantasy2"}, // Draconic
|
||||
{name: "Arago (Arachnid)", base: 40, odd: 0.7, sort: i => t[i] - s[i], shield: "horsehead2"}, // Arachnid
|
||||
{name: "Aj'Snaga (Serpents)", base: 41, odd: 0.7, sort: i => n(i) / bd(i, [12], 10), shield: "fantasy1"}, // Serpents
|
||||
{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: 0.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: 0.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: 0.5, sort: (i) => (h[i] * t[i]) / bd(i, [1, 2, 10, 11]), shield: 'moriaOrc'}, // Orc
|
||||
{name: 'Yotunn (Giants)', base: 38, odd: 0.7, sort: (i) => td(i, -10), shield: 'pavise'}, // Giant
|
||||
{name: 'Rake (Drakonic)', base: 39, odd: 0.7, sort: (i) => -s[i], shield: 'fantasy2'}, // Draconic
|
||||
{name: 'Arago (Arachnid)', base: 40, odd: 0.7, sort: (i) => t[i] - s[i], shield: 'horsehead2'}, // Arachnid
|
||||
{name: "Aj'Snaga (Serpents)", base: 41, odd: 0.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"}
|
||||
{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") {
|
||||
if (culturesSet.value === 'darkFantasy') {
|
||||
return [
|
||||
// common real-world English
|
||||
{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"},
|
||||
{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: 0.7, sort: i => n(i) / td(i, 5) / sf(i), shield: "oldFrench"},
|
||||
{name: "Schwarzen", base: 0, odd: 0.3, sort: i => n(i) / td(i, 10) / bd(i, [6, 8]), shield: "gonfalon"},
|
||||
{name: "Luarian", base: 2, odd: 0.3, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield: "oldFrench"},
|
||||
{name: "Hetallian", base: 3, odd: 0.3, sort: i => n(i) / td(i, 15), shield: "oval"},
|
||||
{name: "Astellian", base: 4, odd: 0.3, sort: i => n(i) / td(i, 16), shield: "spanish"},
|
||||
{name: 'Norse', base: 6, odd: 0.7, sort: (i) => n(i) / td(i, 5) / sf(i), shield: 'oldFrench'},
|
||||
{name: 'Schwarzen', base: 0, odd: 0.3, sort: (i) => n(i) / td(i, 10) / bd(i, [6, 8]), shield: 'gonfalon'},
|
||||
{name: 'Luarian', base: 2, odd: 0.3, sort: (i) => n(i) / td(i, 12) / bd(i, [6, 8]), shield: 'oldFrench'},
|
||||
{name: 'Hetallian', base: 3, odd: 0.3, sort: (i) => n(i) / td(i, 15), shield: 'oval'},
|
||||
{name: 'Astellian', base: 4, odd: 0.3, sort: (i) => n(i) / td(i, 16), shield: 'spanish'},
|
||||
// rare real-world exotic
|
||||
{name: "Kiswaili", base: 28, odd: 0.05, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield: "vesicaPiscis"},
|
||||
{name: "Yoruba", base: 21, odd: 0.05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield: "vesicaPiscis"},
|
||||
{name: "Koryo", base: 10, odd: 0.05, sort: i => n(i) / td(i, 12) / t[i], shield: "round"},
|
||||
{name: "Hantzu", base: 11, odd: 0.05, sort: i => n(i) / td(i, 13), shield: "banner"},
|
||||
{name: "Yamoto", base: 12, odd: 0.05, sort: i => n(i) / td(i, 15) / t[i], shield: "round"},
|
||||
{name: "Guantzu", base: 30, odd: 0.05, sort: i => n(i) / td(i, 17), shield: "banner"},
|
||||
{name: "Ulus", base: 31, odd: 0.05, sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: "banner"},
|
||||
{name: "Turan", base: 16, odd: 0.05, sort: i => n(i) / td(i, 12), shield: "round"},
|
||||
{name: "Berberan", base: 17, odd: 0.05, sort: i => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i], shield: "round"},
|
||||
{name: "Eurabic", base: 18, odd: 0.05, sort: i => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i], shield: "round"},
|
||||
{name: "Slovan", base: 5, odd: 0.05, sort: i => (n(i) / td(i, 6)) * t[i], shield: "round"},
|
||||
{name: "Keltan", base: 22, odd: 0.1, sort: i => n(i) / td(i, 11) ** 0.5 / bd(i, [6, 8]), shield: "vesicaPiscis"},
|
||||
{name: "Elladan", base: 7, odd: 0.2, sort: i => (n(i) / td(i, 18) / sf(i)) * h[i], shield: "boeotian"},
|
||||
{name: "Romian", base: 8, odd: 0.2, sort: i => n(i) / td(i, 14) / t[i], shield: "roman"},
|
||||
{name: 'Kiswaili', base: 28, odd: 0.05, sort: (i) => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield: 'vesicaPiscis'},
|
||||
{name: 'Yoruba', base: 21, odd: 0.05, sort: (i) => n(i) / td(i, 15) / bd(i, [5, 7]), shield: 'vesicaPiscis'},
|
||||
{name: 'Koryo', base: 10, odd: 0.05, sort: (i) => n(i) / td(i, 12) / t[i], shield: 'round'},
|
||||
{name: 'Hantzu', base: 11, odd: 0.05, sort: (i) => n(i) / td(i, 13), shield: 'banner'},
|
||||
{name: 'Yamoto', base: 12, odd: 0.05, sort: (i) => n(i) / td(i, 15) / t[i], shield: 'round'},
|
||||
{name: 'Guantzu', base: 30, odd: 0.05, sort: (i) => n(i) / td(i, 17), shield: 'banner'},
|
||||
{name: 'Ulus', base: 31, odd: 0.05, sort: (i) => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: 'banner'},
|
||||
{name: 'Turan', base: 16, odd: 0.05, sort: (i) => n(i) / td(i, 12), shield: 'round'},
|
||||
{name: 'Berberan', base: 17, odd: 0.05, sort: (i) => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i], shield: 'round'},
|
||||
{name: 'Eurabic', base: 18, odd: 0.05, sort: (i) => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i], shield: 'round'},
|
||||
{name: 'Slovan', base: 5, odd: 0.05, sort: (i) => (n(i) / td(i, 6)) * t[i], shield: 'round'},
|
||||
{name: 'Keltan', base: 22, odd: 0.1, sort: (i) => n(i) / td(i, 11) ** 0.5 / bd(i, [6, 8]), shield: 'vesicaPiscis'},
|
||||
{name: 'Elladan', base: 7, odd: 0.2, sort: (i) => (n(i) / td(i, 18) / sf(i)) * h[i], shield: 'boeotian'},
|
||||
{name: 'Romian', base: 8, odd: 0.2, sort: (i) => n(i) / td(i, 14) / t[i], shield: 'roman'},
|
||||
// fantasy races
|
||||
{name: "Eldar", base: 33, odd: 0.5, sort: i => (n(i) / bd(i, [6, 7, 8, 9], 10)) * t[i], shield: "fantasy5"}, // Elves
|
||||
{name: "Trow", base: 34, odd: 0.8, sort: i => (n(i) / bd(i, [7, 8, 9, 12], 10)) * t[i], shield: "hessen"}, // Dark Elves
|
||||
{name: "Durinn", base: 35, odd: 0.8, sort: i => n(i) + h[i], shield: "erebor"}, // Dwarven
|
||||
{name: "Kobblin", base: 36, odd: 0.8, sort: i => t[i] - s[i], shield: "moriaOrc"}, // Goblin
|
||||
{name: "Uruk", base: 37, odd: 0.8, sort: i => (h[i] * t[i]) / bd(i, [1, 2, 10, 11]), shield: "urukHai"}, // Orc
|
||||
{name: "Yotunn", base: 38, odd: 0.8, sort: i => td(i, -10), shield: "pavise"}, // Giant
|
||||
{name: "Drake", base: 39, odd: 0.9, sort: i => -s[i], shield: "fantasy2"}, // Draconic
|
||||
{name: "Rakhnid", base: 40, odd: 0.9, sort: i => t[i] - s[i], shield: "horsehead2"}, // Arachnid
|
||||
{name: "Aj'Snaga", base: 41, odd: 0.9, sort: i => n(i) / bd(i, [12], 10), shield: "fantasy1"} // Serpents
|
||||
{name: 'Eldar', base: 33, odd: 0.5, sort: (i) => (n(i) / bd(i, [6, 7, 8, 9], 10)) * t[i], shield: 'fantasy5'}, // Elves
|
||||
{name: 'Trow', base: 34, odd: 0.8, sort: (i) => (n(i) / bd(i, [7, 8, 9, 12], 10)) * t[i], shield: 'hessen'}, // Dark Elves
|
||||
{name: 'Durinn', base: 35, odd: 0.8, sort: (i) => n(i) + h[i], shield: 'erebor'}, // Dwarven
|
||||
{name: 'Kobblin', base: 36, odd: 0.8, sort: (i) => t[i] - s[i], shield: 'moriaOrc'}, // Goblin
|
||||
{name: 'Uruk', base: 37, odd: 0.8, sort: (i) => (h[i] * t[i]) / bd(i, [1, 2, 10, 11]), shield: 'urukHai'}, // Orc
|
||||
{name: 'Yotunn', base: 38, odd: 0.8, sort: (i) => td(i, -10), shield: 'pavise'}, // Giant
|
||||
{name: 'Drake', base: 39, odd: 0.9, sort: (i) => -s[i], shield: 'fantasy2'}, // Draconic
|
||||
{name: 'Rakhnid', base: 40, odd: 0.9, sort: (i) => t[i] - s[i], shield: 'horsehead2'}, // Arachnid
|
||||
{name: "Aj'Snaga", base: 41, odd: 0.9, sort: (i) => n(i) / bd(i, [12], 10), shield: 'fantasy1'} // Serpents
|
||||
];
|
||||
}
|
||||
|
||||
if (culturesSet.value === "random") {
|
||||
if (culturesSet.value === 'random') {
|
||||
return d3.range(count).map(function () {
|
||||
const rnd = rand(nameBases.length - 1);
|
||||
const name = Names.getBaseShort(rnd);
|
||||
|
|
@ -334,44 +334,44 @@ window.Cultures = (function () {
|
|||
|
||||
// all-world
|
||||
return [
|
||||
{name: "Shwazen", base: 0, odd: 0.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: 0.6, sort: i => n(i) / td(i, 12) / bd(i, [6, 8]), shield: "oldFrench"},
|
||||
{name: "Tallian", base: 3, odd: 0.6, sort: i => n(i) / td(i, 15), shield: "horsehead2"},
|
||||
{name: "Astellian", base: 4, odd: 0.6, sort: i => n(i) / td(i, 16), shield: "spanish"},
|
||||
{name: "Slovan", base: 5, odd: 0.7, sort: i => (n(i) / td(i, 6)) * t[i], shield: "round"},
|
||||
{name: "Norse", base: 6, odd: 0.7, sort: i => n(i) / td(i, 5), shield: "heater"},
|
||||
{name: "Elladan", base: 7, odd: 0.7, sort: i => (n(i) / td(i, 18)) * h[i], shield: "boeotian"},
|
||||
{name: "Romian", base: 8, odd: 0.7, sort: i => n(i) / td(i, 15), shield: "roman"},
|
||||
{name: "Soumi", base: 9, odd: 0.3, sort: i => (n(i) / td(i, 5) / bd(i, [9])) * t[i], shield: "pavise"},
|
||||
{name: "Koryo", base: 10, odd: 0.1, sort: i => n(i) / td(i, 12) / t[i], shield: "round"},
|
||||
{name: "Hantzu", base: 11, odd: 0.1, sort: i => n(i) / td(i, 13), shield: "banner"},
|
||||
{name: "Yamoto", base: 12, odd: 0.1, sort: i => n(i) / td(i, 15) / t[i], shield: "round"},
|
||||
{name: "Portuzian", base: 13, odd: 0.4, sort: i => n(i) / td(i, 17) / sf(i), shield: "spanish"},
|
||||
{name: "Nawatli", base: 14, odd: 0.1, sort: i => h[i] / td(i, 18) / bd(i, [7]), shield: "square"},
|
||||
{name: "Vengrian", base: 15, odd: 0.2, sort: i => (n(i) / td(i, 11) / bd(i, [4])) * t[i], shield: "wedged"},
|
||||
{name: "Turchian", base: 16, odd: 0.2, sort: i => n(i) / td(i, 13), shield: "round"},
|
||||
{name: "Berberan", base: 17, odd: 0.1, sort: i => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i], shield: "round"},
|
||||
{name: "Eurabic", base: 18, odd: 0.2, sort: i => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i], shield: "round"},
|
||||
{name: "Inuk", base: 19, odd: 0.05, sort: i => td(i, -1) / bd(i, [10, 11]) / sf(i), shield: "square"},
|
||||
{name: "Euskati", base: 20, odd: 0.05, sort: i => (n(i) / td(i, 15)) * h[i], shield: "spanish"},
|
||||
{name: "Yoruba", base: 21, odd: 0.05, sort: i => n(i) / td(i, 15) / bd(i, [5, 7]), shield: "vesicaPiscis"},
|
||||
{name: "Keltan", base: 22, odd: 0.05, sort: i => (n(i) / td(i, 11) / bd(i, [6, 8])) * t[i], shield: "vesicaPiscis"},
|
||||
{name: "Efratic", base: 23, odd: 0.05, sort: i => (n(i) / td(i, 22)) * t[i], shield: "diamond"},
|
||||
{name: "Tehrani", base: 24, odd: 0.1, sort: i => (n(i) / td(i, 18)) * h[i], shield: "round"},
|
||||
{name: "Maui", base: 25, odd: 0.05, sort: i => n(i) / td(i, 24) / sf(i) / t[i], shield: "round"},
|
||||
{name: "Carnatic", base: 26, odd: 0.05, sort: i => n(i) / td(i, 26), shield: "round"},
|
||||
{name: "Inqan", base: 27, odd: 0.05, sort: i => h[i] / td(i, 13), shield: "square"},
|
||||
{name: "Kiswaili", base: 28, odd: 0.1, sort: i => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield: "vesicaPiscis"},
|
||||
{name: "Vietic", base: 29, odd: 0.1, sort: i => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield: "banner"},
|
||||
{name: "Guantzu", base: 30, odd: 0.1, sort: i => n(i) / td(i, 17), shield: "banner"},
|
||||
{name: "Ulus", base: 31, odd: 0.1, sort: i => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: "banner"}
|
||||
{name: 'Shwazen', base: 0, odd: 0.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: 0.6, sort: (i) => n(i) / td(i, 12) / bd(i, [6, 8]), shield: 'oldFrench'},
|
||||
{name: 'Tallian', base: 3, odd: 0.6, sort: (i) => n(i) / td(i, 15), shield: 'horsehead2'},
|
||||
{name: 'Astellian', base: 4, odd: 0.6, sort: (i) => n(i) / td(i, 16), shield: 'spanish'},
|
||||
{name: 'Slovan', base: 5, odd: 0.7, sort: (i) => (n(i) / td(i, 6)) * t[i], shield: 'round'},
|
||||
{name: 'Norse', base: 6, odd: 0.7, sort: (i) => n(i) / td(i, 5), shield: 'heater'},
|
||||
{name: 'Elladan', base: 7, odd: 0.7, sort: (i) => (n(i) / td(i, 18)) * h[i], shield: 'boeotian'},
|
||||
{name: 'Romian', base: 8, odd: 0.7, sort: (i) => n(i) / td(i, 15), shield: 'roman'},
|
||||
{name: 'Soumi', base: 9, odd: 0.3, sort: (i) => (n(i) / td(i, 5) / bd(i, [9])) * t[i], shield: 'pavise'},
|
||||
{name: 'Koryo', base: 10, odd: 0.1, sort: (i) => n(i) / td(i, 12) / t[i], shield: 'round'},
|
||||
{name: 'Hantzu', base: 11, odd: 0.1, sort: (i) => n(i) / td(i, 13), shield: 'banner'},
|
||||
{name: 'Yamoto', base: 12, odd: 0.1, sort: (i) => n(i) / td(i, 15) / t[i], shield: 'round'},
|
||||
{name: 'Portuzian', base: 13, odd: 0.4, sort: (i) => n(i) / td(i, 17) / sf(i), shield: 'spanish'},
|
||||
{name: 'Nawatli', base: 14, odd: 0.1, sort: (i) => h[i] / td(i, 18) / bd(i, [7]), shield: 'square'},
|
||||
{name: 'Vengrian', base: 15, odd: 0.2, sort: (i) => (n(i) / td(i, 11) / bd(i, [4])) * t[i], shield: 'wedged'},
|
||||
{name: 'Turchian', base: 16, odd: 0.2, sort: (i) => n(i) / td(i, 13), shield: 'round'},
|
||||
{name: 'Berberan', base: 17, odd: 0.1, sort: (i) => (n(i) / td(i, 19) / bd(i, [1, 2, 3], 7)) * t[i], shield: 'round'},
|
||||
{name: 'Eurabic', base: 18, odd: 0.2, sort: (i) => (n(i) / td(i, 26) / bd(i, [1, 2], 7)) * t[i], shield: 'round'},
|
||||
{name: 'Inuk', base: 19, odd: 0.05, sort: (i) => td(i, -1) / bd(i, [10, 11]) / sf(i), shield: 'square'},
|
||||
{name: 'Euskati', base: 20, odd: 0.05, sort: (i) => (n(i) / td(i, 15)) * h[i], shield: 'spanish'},
|
||||
{name: 'Yoruba', base: 21, odd: 0.05, sort: (i) => n(i) / td(i, 15) / bd(i, [5, 7]), shield: 'vesicaPiscis'},
|
||||
{name: 'Keltan', base: 22, odd: 0.05, sort: (i) => (n(i) / td(i, 11) / bd(i, [6, 8])) * t[i], shield: 'vesicaPiscis'},
|
||||
{name: 'Efratic', base: 23, odd: 0.05, sort: (i) => (n(i) / td(i, 22)) * t[i], shield: 'diamond'},
|
||||
{name: 'Tehrani', base: 24, odd: 0.1, sort: (i) => (n(i) / td(i, 18)) * h[i], shield: 'round'},
|
||||
{name: 'Maui', base: 25, odd: 0.05, sort: (i) => n(i) / td(i, 24) / sf(i) / t[i], shield: 'round'},
|
||||
{name: 'Carnatic', base: 26, odd: 0.05, sort: (i) => n(i) / td(i, 26), shield: 'round'},
|
||||
{name: 'Inqan', base: 27, odd: 0.05, sort: (i) => h[i] / td(i, 13), shield: 'square'},
|
||||
{name: 'Kiswaili', base: 28, odd: 0.1, sort: (i) => n(i) / td(i, 29) / bd(i, [1, 3, 5, 7]), shield: 'vesicaPiscis'},
|
||||
{name: 'Vietic', base: 29, odd: 0.1, sort: (i) => n(i) / td(i, 25) / bd(i, [7], 7) / t[i], shield: 'banner'},
|
||||
{name: 'Guantzu', base: 30, odd: 0.1, sort: (i) => n(i) / td(i, 17), shield: 'banner'},
|
||||
{name: 'Ulus', base: 31, odd: 0.1, sort: (i) => (n(i) / td(i, 5) / bd(i, [2, 4, 10], 7)) * t[i], shield: 'banner'}
|
||||
];
|
||||
};
|
||||
|
||||
// expand cultures across the map (Dijkstra-like algorithm)
|
||||
const expand = function () {
|
||||
TIME && console.time("expandCultures");
|
||||
TIME && console.time('expandCultures');
|
||||
cells = pack.cells;
|
||||
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
|
|
@ -407,41 +407,41 @@ window.Cultures = (function () {
|
|||
});
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("expandCultures");
|
||||
TIME && console.timeEnd('expandCultures');
|
||||
};
|
||||
|
||||
function getBiomeCost(c, biome, type) {
|
||||
if (cells.biome[pack.cultures[c].center] === biome) return 10; // tiny penalty for native biome
|
||||
if (type === "Hunting") return biomesData.cost[biome] * 5; // non-native biome penalty for hunters
|
||||
if (type === "Nomadic" && biome > 4 && biome < 10) return biomesData.cost[biome] * 10; // forest biome penalty for nomads
|
||||
if (type === 'Hunting') return biomesData.cost[biome] * 5; // non-native biome penalty for hunters
|
||||
if (type === 'Nomadic' && biome > 4 && biome < 10) return biomesData.cost[biome] * 10; // forest biome penalty for nomads
|
||||
return biomesData.cost[biome] * 2; // general non-native biome penalty
|
||||
}
|
||||
|
||||
function getHeightCost(i, h, type) {
|
||||
const f = pack.features[cells.f[i]],
|
||||
a = cells.area[i];
|
||||
if (type === "Lake" && f.type === "lake") return 10; // no lake crossing penalty for Lake cultures
|
||||
if (type === "Naval" && h < 20) return a * 2; // low sea/lake crossing penalty for Naval cultures
|
||||
if (type === "Nomadic" && h < 20) return a * 50; // giant sea/lake crossing penalty for Nomads
|
||||
if (type === 'Lake' && f.type === 'lake') return 10; // no lake crossing penalty for Lake cultures
|
||||
if (type === 'Naval' && h < 20) return a * 2; // low sea/lake crossing penalty for Naval cultures
|
||||
if (type === 'Nomadic' && h < 20) return a * 50; // giant sea/lake crossing penalty for Nomads
|
||||
if (h < 20) return a * 6; // general sea/lake crossing penalty
|
||||
if (type === "Highland" && h < 44) return 3000; // giant penalty for highlanders on lowlands
|
||||
if (type === "Highland" && h < 62) return 200; // giant penalty for highlanders on lowhills
|
||||
if (type === "Highland") return 0; // no penalty for highlanders on highlands
|
||||
if (type === 'Highland' && h < 44) return 3000; // giant penalty for highlanders on lowlands
|
||||
if (type === 'Highland' && h < 62) return 200; // giant penalty for highlanders on lowhills
|
||||
if (type === 'Highland') return 0; // no penalty for highlanders on highlands
|
||||
if (h >= 67) return 200; // general mountains crossing penalty
|
||||
if (h >= 44) return 30; // general hills crossing penalty
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getRiverCost(r, i, type) {
|
||||
if (type === "River") return r ? 0 : 100; // penalty for river cultures
|
||||
if (type === 'River') return r ? 0 : 100; // penalty for river cultures
|
||||
if (!r) return 0; // no penalty for others if there is no river
|
||||
return Math.min(Math.max(cells.fl[i] / 10, 20), 100); // river penalty from 20 to 100 based on flux
|
||||
return minmax(cells.fl[i] / 10, 20, 100); // river penalty from 20 to 100 based on flux
|
||||
}
|
||||
|
||||
function getTypeCost(t, type) {
|
||||
if (t === 1) return type === "Naval" || type === "Lake" ? 0 : type === "Nomadic" ? 60 : 20; // penalty for coastline
|
||||
if (t === 2) return type === "Naval" || type === "Nomadic" ? 30 : 0; // low penalty for land level 2 for Navals and nomads
|
||||
if (t !== -1) return type === "Naval" || type === "Lake" ? 100 : 0; // penalty for mainland for navals
|
||||
if (t === 1) return type === 'Naval' || type === 'Lake' ? 0 : type === 'Nomadic' ? 60 : 20; // penalty for coastline
|
||||
if (t === 2) return type === 'Naval' || type === 'Nomadic' ? 30 : 0; // low penalty for land level 2 for Navals and nomads
|
||||
if (t !== -1) return type === 'Naval' || type === 'Lake' ? 100 : 0; // penalty for mainland for navals
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
494
modules/export.js
Normal file
494
modules/export.js
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
'use strict';
|
||||
// Functions to export map to image or data files
|
||||
|
||||
// download map as SVG
|
||||
async function saveSVG() {
|
||||
TIME && console.time('saveSVG');
|
||||
const url = await getMapURL('svg', {fullMap: true});
|
||||
const link = document.createElement('a');
|
||||
link.download = getFileName() + '.svg';
|
||||
link.href = url;
|
||||
link.click();
|
||||
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, 'success', 5000);
|
||||
TIME && console.timeEnd('saveSVG');
|
||||
}
|
||||
|
||||
// download map as PNG
|
||||
async function savePNG() {
|
||||
TIME && console.time('savePNG');
|
||||
const url = await getMapURL('png');
|
||||
|
||||
const link = document.createElement('a');
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
link.download = getFileName() + '.png';
|
||||
canvas.toBlob(function (blob) {
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.click();
|
||||
window.setTimeout(function () {
|
||||
canvas.remove();
|
||||
window.URL.revokeObjectURL(link.href);
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, 'success', 5000);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
TIME && console.timeEnd('savePNG');
|
||||
}
|
||||
|
||||
// download map as JPEG
|
||||
async function saveJPEG() {
|
||||
TIME && console.time('saveJPEG');
|
||||
const url = await getMapURL('png');
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = async function () {
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92);
|
||||
const URL = await canvas.toDataURL('image/jpeg', quality);
|
||||
const link = document.createElement('a');
|
||||
link.download = getFileName() + '.jpeg';
|
||||
link.href = URL;
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, 'success', 7000);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
};
|
||||
|
||||
TIME && console.timeEnd('saveJPEG');
|
||||
}
|
||||
|
||||
// download map as png tiles
|
||||
async function saveTiles() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// download schema
|
||||
const urlSchema = await getMapURL('tiles', {debug: true, fullMap: true});
|
||||
const zip = new JSZip();
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
|
||||
const imgSchema = new Image();
|
||||
imgSchema.src = urlSchema;
|
||||
imgSchema.onload = function () {
|
||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob((blob) => zip.file(`fmg_tile_schema.png`, blob));
|
||||
};
|
||||
|
||||
// download tiles
|
||||
const url = await getMapURL('tiles', {fullMap: true});
|
||||
const tilesX = +document.getElementById('tileColsInput').value;
|
||||
const tilesY = +document.getElementById('tileRowsInput').value;
|
||||
const scale = +document.getElementById('tileScaleInput').value;
|
||||
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
const tolesTotal = tilesX * tilesY;
|
||||
|
||||
const width = graphWidth * scale;
|
||||
const height = width * (tileH / tileW);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
let loaded = 0;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
||||
const name = `fmg_tile_${i}.png`;
|
||||
canvas.toBlob((blob) => {
|
||||
zip.file(name, blob);
|
||||
loaded += 1;
|
||||
if (loaded === tolesTotal) return downloadZip();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function downloadZip() {
|
||||
const name = `${getFileName()}.zip`;
|
||||
zip.generateAsync({type: 'blob'}).then((blob) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = name;
|
||||
link.click();
|
||||
link.remove();
|
||||
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// parse map svg to object url
|
||||
async function getMapURL(type, options = {}) {
|
||||
const {debug = false, globe = false, noLabels = false, noWater = false, fullMap = false} = options;
|
||||
|
||||
if (fullMap) drawScaleBar(1);
|
||||
|
||||
const cloneEl = document.getElementById('map').cloneNode(true); // clone svg
|
||||
cloneEl.id = 'fantasyMap';
|
||||
document.body.appendChild(cloneEl);
|
||||
const clone = d3.select(cloneEl);
|
||||
if (!debug) 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 (globe) clone.select('#scaleBar')?.remove();
|
||||
if (noLabels) {
|
||||
clone.select('#labels #states')?.remove();
|
||||
clone.select('#labels #burgLabels')?.remove();
|
||||
clone.select('#icons #burgIcons')?.remove();
|
||||
}
|
||||
if (noWater) {
|
||||
clone.select('#oceanBase').attr('opacity', 0);
|
||||
clone.select('#oceanPattern').attr('opacity', 0);
|
||||
}
|
||||
if (fullMap) {
|
||||
// reset transform to show the whole map
|
||||
clone.attr('width', graphWidth).attr('height', graphHeight);
|
||||
clone.select('#viewbox').attr('transform', null);
|
||||
drawScaleBar(scale);
|
||||
}
|
||||
|
||||
if (type === 'svg') removeUnusedElements(clone);
|
||||
if (customization && type === 'mesh') updateMeshCells(clone);
|
||||
inlineStyle(clone);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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') && emblems.selectAll('use').size()) {
|
||||
cloneEl
|
||||
.getElementById('emblems')
|
||||
?.querySelectorAll('use')
|
||||
.forEach((el) => {
|
||||
const href = el.getAttribute('href') || el.getAttribute('xlink:href');
|
||||
if (!href) return;
|
||||
const emblem = document.getElementById(href.slice(1));
|
||||
if (emblem) cloneDefs.append(emblem.cloneNode(true));
|
||||
});
|
||||
} else {
|
||||
cloneDefs.querySelector('#defs-emblems')?.remove();
|
||||
}
|
||||
|
||||
// replace ocean pattern href to base64
|
||||
if (location.hostname && cloneEl.getElementById('oceanicPattern')) {
|
||||
const el = cloneEl.getElementById('oceanicPattern');
|
||||
const url = el.getAttribute('href');
|
||||
await new Promise((resolve) => {
|
||||
getBase64(url, (base64) => {
|
||||
el.setAttribute('href', base64);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add relief icons
|
||||
if (cloneEl.getElementById('terrain')) {
|
||||
const uniqueElements = new Set();
|
||||
const terrainNodes = cloneEl.getElementById('terrain').childNodes;
|
||||
for (let i = 0; i < terrainNodes.length; i++) {
|
||||
const href = terrainNodes[i].getAttribute('href') || terrainNodes[i].getAttribute('xlink:href');
|
||||
uniqueElements.add(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));
|
||||
}
|
||||
|
||||
// add grid pattern
|
||||
if (cloneEl.getElementById('gridOverlay')?.hasChildNodes()) {
|
||||
const type = cloneEl.getElementById('gridOverlay').getAttribute('type');
|
||||
const pattern = svgDefs.getElementById('pattern_' + type);
|
||||
if (pattern) cloneDefs.appendChild(pattern.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',
|
||||
'<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>'
|
||||
);
|
||||
|
||||
// add xlink: for href to support svg1.1
|
||||
if (type === 'svg') {
|
||||
cloneEl.querySelectorAll('[href]').forEach((el) => {
|
||||
const href = el.getAttribute('href');
|
||||
el.removeAttribute('href');
|
||||
el.setAttribute('xlink:href', href);
|
||||
});
|
||||
}
|
||||
|
||||
const usedFonts = getUsedFonts(cloneEl);
|
||||
const fontsToLoad = usedFonts.filter((font) => font.src);
|
||||
if (fontsToLoad.length) {
|
||||
const dataURLfonts = await loadFontsAsDataURI(fontsToLoad);
|
||||
|
||||
const fontFaces = dataURLfonts
|
||||
.map(({family, src, unicodeRange = '', variant = 'normal'}) => {
|
||||
return `@font-face {font-family: "${family}"; src: ${src}; unicode-range: ${unicodeRange}; font-variant: ${variant};}`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.setAttribute('type', 'text/css');
|
||||
style.innerHTML = fontFaces;
|
||||
cloneEl.querySelector('defs').appendChild(style);
|
||||
}
|
||||
|
||||
clone.remove();
|
||||
|
||||
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + 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;
|
||||
}
|
||||
|
||||
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
|
||||
function removeUnusedElements(clone) {
|
||||
if (!terrain.selectAll('use').size()) clone.select('#defs-relief')?.remove();
|
||||
|
||||
for (let empty = 1; empty; ) {
|
||||
empty = 0;
|
||||
clone.selectAll('g').each(function () {
|
||||
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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateMeshCells(clone) {
|
||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter((i) => grid.cells.h[i] >= 20);
|
||||
const scheme = getColorScheme();
|
||||
clone.select('#heights').attr('filter', 'url(#blur1)');
|
||||
clone
|
||||
.select('#heights')
|
||||
.selectAll('polygon')
|
||||
.data(data)
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getGridPolygon(d))
|
||||
.attr('id', (d) => 'cell' + d)
|
||||
.attr('stroke', (d) => getColor(grid.cells.h[d], scheme));
|
||||
}
|
||||
|
||||
// for each g element get inline style
|
||||
function inlineStyle(clone) {
|
||||
const emptyG = clone.append('g').node();
|
||||
const defaultStyles = window.getComputedStyle(emptyG);
|
||||
|
||||
clone.selectAll('g, #ruler *, #scaleBar > text').each(function () {
|
||||
const compStyle = window.getComputedStyle(this);
|
||||
let style = '';
|
||||
|
||||
for (let i = 0; i < compStyle.length; i++) {
|
||||
const key = compStyle[i];
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
// Firefox mask hack
|
||||
if (key === 'mask-image' && value !== defaultStyles.getPropertyValue(key)) {
|
||||
style += "mask-image: url('#land');";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === 'cursor') continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ':' + value + ';';
|
||||
}
|
||||
|
||||
for (const key in compStyle) {
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
if (key === 'cursor') continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ':' + value + ';';
|
||||
}
|
||||
|
||||
if (style != '') this.setAttribute('style', style);
|
||||
});
|
||||
|
||||
emptyG.remove();
|
||||
}
|
||||
|
||||
function saveGeoJSON_Cells() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
const cells = pack.cells;
|
||||
const getPopulation = (i) => {
|
||||
const [r, u] = getCellPopulation(i);
|
||||
return rn(r + u);
|
||||
};
|
||||
const getHeight = (i) => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]]));
|
||||
|
||||
cells.i.forEach((i) => {
|
||||
const coordinates = getCellCoordinates(cells.v[i]);
|
||||
const height = getHeight(i);
|
||||
const biome = cells.biome[i];
|
||||
const type = pack.features[cells.f[i]].type;
|
||||
const population = getPopulation(i);
|
||||
const state = cells.state[i];
|
||||
const province = cells.province[i];
|
||||
const culture = cells.culture[i];
|
||||
const religion = cells.religion[i];
|
||||
const neighbors = cells.c[i];
|
||||
|
||||
const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors};
|
||||
const feature = {type: 'Feature', geometry: {type: 'Polygon', coordinates}, properties};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Cells') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function saveGeoJSON_Routes() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
|
||||
routes.selectAll('g > path').each(function () {
|
||||
const coordinates = getRoutePoints(this);
|
||||
const id = this.id;
|
||||
const type = this.parentElement.id;
|
||||
|
||||
const feature = {type: 'Feature', geometry: {type: 'LineString', coordinates}, properties: {id, type}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Routes') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function saveGeoJSON_Rivers() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
|
||||
rivers.selectAll('path').each(function () {
|
||||
const coordinates = getRiverPoints(this);
|
||||
const id = this.id;
|
||||
const width = +this.dataset.increment;
|
||||
const increment = +this.dataset.increment;
|
||||
const river = pack.rivers.find((r) => r.i === +id.slice(5));
|
||||
const name = river ? river.name : '';
|
||||
const type = river ? river.type : '';
|
||||
const i = river ? river.i : '';
|
||||
const basin = river ? river.basin : '';
|
||||
|
||||
const feature = {type: 'Feature', geometry: {type: 'LineString', coordinates}, properties: {id, i, basin, name, type, width, increment}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Rivers') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function saveGeoJSON_Markers() {
|
||||
const features = pack.markers.map((marker) => {
|
||||
const {i, type, icon, x, y, size, fill, stroke} = marker;
|
||||
const coordinates = getQGIScoordinates(x, y);
|
||||
const id = `marker${i}`;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
const properties = {id, type, icon, ...note, size, fill, stroke};
|
||||
return {type: 'Feature', geometry: {type: 'Point', coordinates}, properties};
|
||||
});
|
||||
|
||||
const json = {type: 'FeatureCollection', features};
|
||||
|
||||
const fileName = getFileName('Markers') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), fileName, 'application/json');
|
||||
}
|
||||
|
||||
function getCellCoordinates(vertices) {
|
||||
const p = pack.vertices.p;
|
||||
const coordinates = vertices.map((n) => getQGIScoordinates(p[n][0], p[n][1]));
|
||||
return [coordinates.concat([coordinates[0]])];
|
||||
}
|
||||
|
||||
function getRoutePoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength();
|
||||
const increment = l / Math.ceil(l / 2);
|
||||
for (let i = 0; i <= l; i += increment) {
|
||||
const p = node.getPointAtLength(i);
|
||||
points.push(getQGIScoordinates(p.x, p.y));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
function getRiverPoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength() / 2; // half-length
|
||||
const increment = 0.25; // defines density of points
|
||||
for (let i = l, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p1 = node.getPointAtLength(i);
|
||||
const p2 = node.getPointAtLength(c);
|
||||
const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
||||
points.push([x, y]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
388
modules/fonts.js
388
modules/fonts.js
|
|
@ -1,141 +1,271 @@
|
|||
// helper finctions to work with fonts
|
||||
'use strict';
|
||||
|
||||
async function addFonts(url) {
|
||||
$("head").append('<link rel="stylesheet" type="text/css" href="' + url + '">');
|
||||
const fonts = [
|
||||
{family: 'Arial'},
|
||||
{family: 'Times New Roman'},
|
||||
{family: 'Georgia'},
|
||||
{family: 'Garamond'},
|
||||
{family: 'Lucida Sans Unicode'},
|
||||
{family: 'Courier New'},
|
||||
{family: 'Verdana'},
|
||||
{family: 'Impact'},
|
||||
{family: 'Comic Sans MS'},
|
||||
{family: 'Papyrus'},
|
||||
{
|
||||
family: 'Almendra SC',
|
||||
src: 'url(https://fonts.gstatic.com/s/almendrasc/v13/Iure6Yx284eebowr7hbyTaZOrLQ.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD'
|
||||
},
|
||||
{
|
||||
family: 'Amatic SC',
|
||||
src: 'url(https://fonts.gstatic.com/s/amaticsc/v11/TUZ3zwprpvBS1izr_vOMscGKfrUC.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD'
|
||||
},
|
||||
{
|
||||
family: 'Architects Daughter',
|
||||
src: 'url(https://fonts.gstatic.com/s/architectsdaughter/v8/RXTgOOQ9AAtaVOHxx0IUBM3t7GjCYufj5TXV5VnA2p8.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Bitter',
|
||||
src: 'url(https://fonts.gstatic.com/s/bitter/v12/zfs6I-5mjWQ3nxqccMoL2A.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Caesar Dressing',
|
||||
src: 'url(https://fonts.gstatic.com/s/caesardressing/v6/yYLx0hLa3vawqtwdswbotmK4vrRHdrz7.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD'
|
||||
},
|
||||
{
|
||||
family: 'Cinzel',
|
||||
src: 'url(https://fonts.gstatic.com/s/cinzel/v7/zOdksD_UUTk1LJF9z4tURA.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Dancing Script',
|
||||
src: 'url(https://fonts.gstatic.com/s/dancingscript/v9/KGBfwabt0ZRLA5W1ywjowUHdOuSHeh0r6jGTOGdAKHA.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Fredericka the Great',
|
||||
src: 'url(https://fonts.gstatic.com/s/frederickathegreat/v6/9Bt33CxNwt7aOctW2xjbCstzwVKsIBVV--Sjxbc.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD'
|
||||
},
|
||||
{
|
||||
family: 'Gloria Hallelujah',
|
||||
src: 'url(https://fonts.gstatic.com/s/gloriahallelujah/v9/CA1k7SlXcY5kvI81M_R28cNDay8z-hHR7F16xrcXsJw.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Great Vibes',
|
||||
src: 'url(https://fonts.gstatic.com/s/greatvibes/v5/6q1c0ofG6NKsEhAc2eh-3Y4P5ICox8Kq3LLUNMylGO4.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'IM Fell English',
|
||||
src: 'url(https://fonts.gstatic.com/s/imfellenglish/v7/xwIisCqGFi8pff-oa9uSVAkYLEKE0CJQa8tfZYc_plY.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Kaushan Script',
|
||||
src: 'url(https://fonts.gstatic.com/s/kaushanscript/v6/qx1LSqts-NtiKcLw4N03IEd0sm1ffa_JvZxsF_BEwQk.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'MedievalSharp',
|
||||
src: 'url(https://fonts.gstatic.com/s/medievalsharp/v9/EvOJzAlL3oU5AQl2mP5KdgptMqhwMg.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD'
|
||||
},
|
||||
{
|
||||
family: 'Metamorphous',
|
||||
src: 'url(https://fonts.gstatic.com/s/metamorphous/v7/Wnz8HA03aAXcC39ZEX5y133EOyqs.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD'
|
||||
},
|
||||
{
|
||||
family: 'Montez',
|
||||
src: 'url(https://fonts.gstatic.com/s/montez/v8/aq8el3-0osHIcFK6bXAPkw.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Nova Script',
|
||||
src: 'url(https://fonts.gstatic.com/s/novascript/v10/7Au7p_IpkSWSTWaFWkumvlQKGFw.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD'
|
||||
},
|
||||
{
|
||||
family: 'Orbitron',
|
||||
src: 'url(https://fonts.gstatic.com/s/orbitron/v9/HmnHiRzvcnQr8CjBje6GQvesZW2xOQ-xsNqO47m55DA.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Satisfy',
|
||||
src: 'url(https://fonts.gstatic.com/s/satisfy/v8/2OzALGYfHwQjkPYWELy-cw.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Shadows Into Light',
|
||||
src: 'url(https://fonts.gstatic.com/s/shadowsintolight/v7/clhLqOv7MXn459PTh0gXYFK2TSYBz0eNcHnp4YqE4Ts.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
},
|
||||
{
|
||||
family: 'Uncial Antiqua',
|
||||
src: 'url(https://fonts.gstatic.com/s/uncialantiqua/v5/N0bM2S5WOex4OUbESzoESK-i-MfWQZQ.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD'
|
||||
},
|
||||
{
|
||||
family: 'Underdog',
|
||||
src: 'url(https://fonts.gstatic.com/s/underdog/v6/CHygV-jCElj7diMroWSlWV8.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD'
|
||||
},
|
||||
{
|
||||
family: 'Yellowtail',
|
||||
src: 'url(https://fonts.gstatic.com/s/yellowtail/v8/GcIHC9QEwVkrA19LJU1qlPk_vArhqVIZ0nv9q090hN8.woff2)',
|
||||
unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215'
|
||||
}
|
||||
];
|
||||
|
||||
declareDefaultFonts(); // execute once on load
|
||||
|
||||
function declareFont(font) {
|
||||
const {family, src, ...rest} = font;
|
||||
if (!src) return;
|
||||
|
||||
const fontFace = new FontFace(family, src, {...rest, display: 'block'});
|
||||
document.fonts.add(fontFace);
|
||||
addFontOption(family);
|
||||
}
|
||||
|
||||
function declareDefaultFonts() {
|
||||
fonts.forEach((font) => {
|
||||
if (font.src) declareFont(font);
|
||||
else addFontOption(font.family);
|
||||
});
|
||||
}
|
||||
|
||||
function getUsedFonts(svg) {
|
||||
const usedFontFamilies = new Set();
|
||||
|
||||
const labelGroups = svg.querySelectorAll('#labels g');
|
||||
for (const labelGroup of labelGroups) {
|
||||
const font = labelGroup.getAttribute('font-family');
|
||||
if (font) usedFontFamilies.add(font);
|
||||
}
|
||||
|
||||
const provinceFont = provs.attr('font-family');
|
||||
if (provinceFont) usedFontFamilies.add(provinceFont);
|
||||
|
||||
const legend = svg.querySelector('#legend');
|
||||
const legendFont = legend?.getAttribute('font-family');
|
||||
if (legendFont) usedFontFamilies.add(legendFont);
|
||||
|
||||
const usedFonts = fonts.filter((font) => usedFontFamilies.has(font.family));
|
||||
return usedFonts;
|
||||
}
|
||||
|
||||
function addFontOption(family) {
|
||||
const options = document.getElementById('styleSelectFont');
|
||||
// const existingOption = options.querySelector(`[value="${family}"]`);
|
||||
// if (existingOption) return;
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = family;
|
||||
option.innerText = family;
|
||||
option.style.fontFamily = family;
|
||||
options.add(option);
|
||||
}
|
||||
|
||||
async function fetchGoogleFont(family) {
|
||||
const url = `https://fonts.googleapis.com/css2?family=${family.replace(/ /g, '+')}`;
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
const text = await resp.text();
|
||||
let s = document.createElement("style");
|
||||
s.innerHTML = text;
|
||||
document.head.appendChild(s);
|
||||
let styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0];
|
||||
let FontRule = rule_1 => {
|
||||
let family = rule_1.style.getPropertyValue("font-family");
|
||||
let font = family.replace(/['"]+/g, "").replace(/ /g, "+");
|
||||
let weight = rule_1.style.getPropertyValue("font-weight");
|
||||
if (weight && weight !== "400") font += ":" + weight;
|
||||
if (fonts.indexOf(font) == -1) {
|
||||
fonts.push(font);
|
||||
fetched++;
|
||||
}
|
||||
};
|
||||
let fetched = 0;
|
||||
for (let r of styleSheet.cssRules) {
|
||||
FontRule(r);
|
||||
}
|
||||
document.head.removeChild(s);
|
||||
return fetched;
|
||||
|
||||
const fontFaceRules = text.match(/font-face\s*{[^}]+}/g);
|
||||
const fonts = fontFaceRules.map((fontFace) => {
|
||||
const srcURL = fontFace.match(/url\(['"]?(.+?)['"]?\)/)[1];
|
||||
const src = `url(${srcURL})`;
|
||||
const unicodeRange = fontFace.match(/unicode-range: (.*?);/)?.[1];
|
||||
const variant = fontFace.match(/font-style: (.*?);/)?.[1];
|
||||
|
||||
const font = {family, src};
|
||||
if (unicodeRange) font.unicodeRange = unicodeRange;
|
||||
if (variant && variant !== 'normal') font.variant = variant;
|
||||
return font;
|
||||
});
|
||||
|
||||
return fonts;
|
||||
} catch (err) {
|
||||
return ERROR && console.error(err);
|
||||
ERROR && console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function loadUsedFonts() {
|
||||
const fontsInUse = getFontsList(svg);
|
||||
const fontsToLoad = fontsInUse.filter(font => !fonts.includes(font));
|
||||
if (fontsToLoad?.length) {
|
||||
const url = "https://fonts.googleapis.com/css?family=" + fontsToLoad.join("|");
|
||||
addFonts(url);
|
||||
}
|
||||
}
|
||||
|
||||
function getFontsList(svg) {
|
||||
const fontsInUse = [];
|
||||
|
||||
svg.selectAll("#labels > g").each(function () {
|
||||
if (!this.hasChildNodes()) return;
|
||||
const font = this.dataset.font;
|
||||
if (font) fontsInUse.push(font);
|
||||
});
|
||||
if (legend?.node()?.hasChildNodes()) fontsInUse.push(legend.attr("data-font"));
|
||||
|
||||
return [...new Set(fontsInUse)];
|
||||
}
|
||||
|
||||
// code from Kaiido's answer https://stackoverflow.com/questions/42402584/how-to-use-google-fonts-in-canvas-when-drawing-dom-objects-in-svg
|
||||
function GFontToDataURI(url) {
|
||||
if (!url) return Promise.resolve();
|
||||
return fetch(url) // first fecth the embed stylesheet page
|
||||
.then(resp => resp.text()) // we only need the text of it
|
||||
.then(text => {
|
||||
let s = document.createElement("style");
|
||||
s.innerHTML = text;
|
||||
document.head.appendChild(s);
|
||||
const styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0];
|
||||
|
||||
const FontRule = rule => {
|
||||
const src = rule.style.getPropertyValue("src");
|
||||
const url = src ? src.split("url(")[1].split(")")[0] : "";
|
||||
return {rule, src, url: url.substring(url.length - 1, 1)};
|
||||
};
|
||||
const fontProms = [];
|
||||
|
||||
for (const r of styleSheet.cssRules) {
|
||||
let fR = FontRule(r);
|
||||
if (!fR.url) continue;
|
||||
|
||||
fontProms.push(
|
||||
fetch(fR.url) // fetch the actual font-file (.woff)
|
||||
.then(resp => resp.blob())
|
||||
.then(blob => {
|
||||
return new Promise(resolve => {
|
||||
let f = new FileReader();
|
||||
f.onload = e => resolve(f.result);
|
||||
f.readAsDataURL(blob);
|
||||
});
|
||||
})
|
||||
.then(dataURL => fR.rule.cssText.replace(fR.url, dataURL))
|
||||
);
|
||||
}
|
||||
document.head.removeChild(s); // clean up
|
||||
return Promise.all(fontProms); // wait for all this has been done
|
||||
});
|
||||
}
|
||||
|
||||
// fetch default fonts if not done before
|
||||
function loadDefaultFonts() {
|
||||
if (!$('link[href="fonts.css"]').length) {
|
||||
$("head").append('<link rel="stylesheet" type="text/css" href="fonts.css">');
|
||||
const fontsToAdd = ["Amatic+SC:700", "IM+Fell+English", "Great+Vibes", "MedievalSharp", "Metamorphous", "Nova+Script", "Uncial+Antiqua", "Underdog", "Caesar+Dressing", "Bitter", "Yellowtail", "Montez", "Shadows+Into+Light", "Fredericka+the+Great", "Orbitron", "Dancing+Script:700", "Architects+Daughter", "Kaushan+Script", "Gloria+Hallelujah", "Satisfy", "Comfortaa:700", "Cinzel"];
|
||||
fontsToAdd.forEach(function (f) {
|
||||
if (fonts.indexOf(f) === -1) fonts.push(f);
|
||||
});
|
||||
updateFontOptions();
|
||||
}
|
||||
}
|
||||
|
||||
function fetchFonts(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (url === "") return tip("Use a direct link to any @font-face declaration or just font name to fetch from Google Fonts");
|
||||
|
||||
if (url.indexOf("http") === -1) {
|
||||
url = url.replace(url.charAt(0), url.charAt(0).toUpperCase()).split(" ").join("+");
|
||||
url = "https://fonts.googleapis.com/css?family=" + url;
|
||||
}
|
||||
|
||||
addFonts(url).then(fetched => {
|
||||
if (fetched === undefined) return tip("Cannot fetch font for this value!", false, "error");
|
||||
if (fetched === 0) return tip("Already in the fonts list!", false, "error");
|
||||
|
||||
updateFontOptions();
|
||||
if (fetched === 1) {
|
||||
tip("Font " + fonts[fonts.length - 1] + " is fetched");
|
||||
} else if (fetched > 1) {
|
||||
tip(fetched + " fonts are added to the list");
|
||||
}
|
||||
resolve(fetched);
|
||||
});
|
||||
function readBlobAsDataURL(blob) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => resolve(reader.result);
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
// Update font list for Label and Burg Editors
|
||||
function updateFontOptions() {
|
||||
styleSelectFont.innerHTML = "";
|
||||
for (let i = 0; i < fonts.length; i++) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = i;
|
||||
const font = fonts[i].split(":")[0].replace(/\+/g, " ");
|
||||
opt.style.fontFamily = opt.innerHTML = font;
|
||||
styleSelectFont.add(opt);
|
||||
}
|
||||
async function loadFontsAsDataURI(fonts) {
|
||||
const promises = fonts.map(async (font) => {
|
||||
const url = font.src.match(/url\(['"]?(.+?)['"]?\)/)[1];
|
||||
const resp = await fetch(url);
|
||||
const blob = await resp.blob();
|
||||
const dataURL = await readBlobAsDataURL(blob);
|
||||
|
||||
return {...font, src: `url('${dataURL}')`};
|
||||
});
|
||||
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
async function addGoogleFont(family) {
|
||||
const fontRanges = await fetchGoogleFont(family);
|
||||
if (!fontRanges) return tip('Cannot fetch Google font for this value', true, 'error', 4000);
|
||||
tip(`Google font ${family} is loading...`, true, 'warn', 4000);
|
||||
|
||||
const promises = fontRanges.map((range) => {
|
||||
const {src, unicodeRange, variant} = range;
|
||||
const fontFace = new FontFace(family, src, {unicodeRange, variant, display: 'block'});
|
||||
return fontFace.load();
|
||||
});
|
||||
|
||||
Promise.all(promises)
|
||||
.then((fontFaces) => {
|
||||
fontFaces.forEach((fontFace) => document.fonts.add(fontFace));
|
||||
fonts.push(...fontRanges);
|
||||
tip(`Google font ${family} is added to the list`, true, 'success', 4000);
|
||||
addFontOption(family);
|
||||
document.getElementById('styleSelectFont').value = family;
|
||||
changeFont();
|
||||
})
|
||||
.catch((err) => {
|
||||
tip(`Failed to load Google font ${family}`, true, 'error', 4000);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
function addLocalFont(family) {
|
||||
fonts.push({family});
|
||||
|
||||
const fontFace = new FontFace(family, `local(${family})`, {display: 'block'});
|
||||
document.fonts.add(fontFace);
|
||||
tip(`Local font ${family} is added to the fonts list`, true, 'success', 4000);
|
||||
addFontOption(family);
|
||||
document.getElementById('styleSelectFont').value = family;
|
||||
changeFont();
|
||||
}
|
||||
|
||||
function addWebFont(family, url) {
|
||||
const src = `url('${url}')`;
|
||||
fonts.push({family, src});
|
||||
|
||||
const fontFace = new FontFace(family, src, {display: 'block'});
|
||||
document.fonts.add(fontFace);
|
||||
tip(`Font ${family} is added to the list`, true, 'success', 4000);
|
||||
addFontOption(family);
|
||||
document.getElementById('styleSelectFont').value = family;
|
||||
changeFont();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,38 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.HeightmapGenerator = (function () {
|
||||
let cells, p;
|
||||
|
||||
const generate = function () {
|
||||
TIME && console.time("generateHeightmap");
|
||||
TIME && console.time('generateHeightmap');
|
||||
cells = grid.cells;
|
||||
p = grid.points;
|
||||
cells.h = new Uint8Array(grid.points.length);
|
||||
|
||||
const template = document.getElementById("templateInput").value;
|
||||
const template = document.getElementById('templateInput').value;
|
||||
const templateString = HeightmapTemplates[template];
|
||||
const steps = templateString.split("\n");
|
||||
const steps = templateString.split('\n');
|
||||
|
||||
if (!steps.length) throw new Error(`Heightmap template: no steps. Template: ${template}. Steps: ${steps}`);
|
||||
|
||||
for (const step of steps) {
|
||||
const elements = step.trim().split(" ");
|
||||
const elements = step.trim().split(' ');
|
||||
if (elements.length < 2) throw new Error(`Heightmap template: steps < 2. Template: ${template}. Step: ${elements}`);
|
||||
addStep(...elements);
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("generateHeightmap");
|
||||
TIME && console.timeEnd('generateHeightmap');
|
||||
};
|
||||
|
||||
function addStep(a1, a2, a3, a4, a5) {
|
||||
if (a1 === "Hill") return addHill(a2, a3, a4, a5);
|
||||
if (a1 === "Pit") return addPit(a2, a3, a4, a5);
|
||||
if (a1 === "Range") return addRange(a2, a3, a4, a5);
|
||||
if (a1 === "Trough") return addTrough(a2, a3, a4, a5);
|
||||
if (a1 === "Strait") return addStrait(a2, a3);
|
||||
if (a1 === "Add") return modify(a3, +a2, 1);
|
||||
if (a1 === "Multiply") return modify(a3, 0, +a2);
|
||||
if (a1 === "Smooth") return smooth(a2);
|
||||
if (a1 === 'Hill') return addHill(a2, a3, a4, a5);
|
||||
if (a1 === 'Pit') return addPit(a2, a3, a4, a5);
|
||||
if (a1 === 'Range') return addRange(a2, a3, a4, a5);
|
||||
if (a1 === 'Trough') return addTrough(a2, a3, a4, a5);
|
||||
if (a1 === 'Strait') return addStrait(a2, a3);
|
||||
if (a1 === 'Add') return modify(a3, +a2, 1);
|
||||
if (a1 === 'Multiply') return modify(a3, 0, +a2);
|
||||
if (a1 === 'Smooth') return smooth(a2);
|
||||
}
|
||||
|
||||
function getBlobPower() {
|
||||
|
|
@ -201,13 +201,13 @@ window.HeightmapGenerator = (function () {
|
|||
while (queue.length) {
|
||||
const frontier = queue.slice();
|
||||
(queue = []), i++;
|
||||
frontier.forEach(i => {
|
||||
frontier.forEach((i) => {
|
||||
cells.h[i] = lim(cells.h[i] + h * (Math.random() * 0.3 + 0.85));
|
||||
});
|
||||
h = h ** power - 1;
|
||||
if (h < 2) break;
|
||||
frontier.forEach(f => {
|
||||
cells.c[f].forEach(i => {
|
||||
frontier.forEach((f) => {
|
||||
cells.c[f].forEach((i) => {
|
||||
if (!used[i]) {
|
||||
queue.push(i);
|
||||
used[i] = 1;
|
||||
|
|
@ -295,13 +295,13 @@ window.HeightmapGenerator = (function () {
|
|||
while (queue.length) {
|
||||
const frontier = queue.slice();
|
||||
(queue = []), i++;
|
||||
frontier.forEach(i => {
|
||||
frontier.forEach((i) => {
|
||||
cells.h[i] = lim(cells.h[i] - h * (Math.random() * 0.3 + 0.85));
|
||||
});
|
||||
h = h ** power - 1;
|
||||
if (h < 2) break;
|
||||
frontier.forEach(f => {
|
||||
cells.c[f].forEach(i => {
|
||||
frontier.forEach((f) => {
|
||||
cells.c[f].forEach((i) => {
|
||||
if (!used[i]) {
|
||||
queue.push(i);
|
||||
used[i] = 1;
|
||||
|
|
@ -323,11 +323,11 @@ window.HeightmapGenerator = (function () {
|
|||
}
|
||||
};
|
||||
|
||||
const addStrait = function (width, direction = "vertical") {
|
||||
const addStrait = function (width, direction = 'vertical') {
|
||||
width = Math.min(getNumberInRange(width), grid.cellsX / 3);
|
||||
if (width < 1 && P(width)) return;
|
||||
const used = new Uint8Array(cells.h.length);
|
||||
const vert = direction === "vertical";
|
||||
const vert = direction === 'vertical';
|
||||
const startX = vert ? Math.floor(Math.random() * graphWidth * 0.4 + graphWidth * 0.3) : 5;
|
||||
const startY = vert ? 5 : Math.floor(Math.random() * graphHeight * 0.4 + graphHeight * 0.3);
|
||||
const endX = vert ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2) : graphWidth - 5;
|
||||
|
|
@ -377,34 +377,36 @@ window.HeightmapGenerator = (function () {
|
|||
};
|
||||
|
||||
const modify = function (range, add, mult, power) {
|
||||
const min = range === "land" ? 20 : range === "all" ? 0 : +range.split("-")[0];
|
||||
const max = range === "land" || range === "all" ? 100 : +range.split("-")[1];
|
||||
grid.cells.h = grid.cells.h.map(h => (h >= min && h <= max ? mod(h) : h));
|
||||
const min = range === 'land' ? 20 : range === 'all' ? 0 : +range.split('-')[0];
|
||||
const max = range === 'land' || range === 'all' ? 100 : +range.split('-')[1];
|
||||
const isLand = min === 20;
|
||||
|
||||
function mod(v) {
|
||||
if (add) v = min === 20 ? Math.max(v + add, 20) : v + add;
|
||||
if (mult !== 1) v = min === 20 ? (v - 20) * mult + 20 : v * mult;
|
||||
if (power) v = min === 20 ? (v - 20) ** power + 20 : v ** power;
|
||||
return lim(v);
|
||||
}
|
||||
grid.cells.h = grid.cells.h.map((h) => {
|
||||
if (h < min || h > max) return h;
|
||||
|
||||
if (add) h = isLand ? Math.max(h + add, 20) : h + add;
|
||||
if (mult !== 1) h = isLand ? (h - 20) * mult + 20 : h * mult;
|
||||
if (power) h = isLand ? (h - 20) ** power + 20 : h ** power;
|
||||
return lim(h);
|
||||
});
|
||||
};
|
||||
|
||||
const smooth = function (fr = 2, add = 0) {
|
||||
cells.h = cells.h.map((h, i) => {
|
||||
const a = [h];
|
||||
cells.c[i].forEach(c => a.push(cells.h[c]));
|
||||
cells.c[i].forEach((c) => a.push(cells.h[c]));
|
||||
return lim((h * (fr - 1) + d3.mean(a) + add) / fr);
|
||||
});
|
||||
};
|
||||
|
||||
function getPointInRange(range, length) {
|
||||
if (typeof range !== "string") {
|
||||
ERROR && console.error("Range should be a string");
|
||||
if (typeof range !== 'string') {
|
||||
ERROR && console.error('Range should be a string');
|
||||
return;
|
||||
}
|
||||
|
||||
const min = range.split("-")[0] / 100 || 0;
|
||||
const max = range.split("-")[1] / 100 || min;
|
||||
const min = range.split('-')[0] / 100 || 0;
|
||||
const max = range.split('-')[1] / 100 || min;
|
||||
return rand(min * length, max * length);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.HeightmapTemplates = (function () {
|
||||
const volcano = `Hill 1 90-100 44-56 40-60
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.Lakes = (function () {
|
||||
const setClimateData = function (h) {
|
||||
const cells = pack.cells;
|
||||
const lakeOutCells = new Uint16Array(cells.i.length);
|
||||
|
||||
pack.features.forEach(f => {
|
||||
if (f.type !== "lake") return;
|
||||
pack.features.forEach((f) => {
|
||||
if (f.type !== 'lake') return;
|
||||
|
||||
// default flux: sum of precipitation around lake
|
||||
f.flux = f.shoreline.reduce((acc, c) => acc + grid.cells.prec[cells.g[c]], 0);
|
||||
|
||||
// temperature and evaporation to detect closed lakes
|
||||
f.temp = f.cells < 6 ? grid.cells.temp[cells.g[f.firstCell]] : rn(d3.mean(f.shoreline.map(c => grid.cells.temp[cells.g[c]])), 1);
|
||||
f.temp = f.cells < 6 ? grid.cells.temp[cells.g[f.firstCell]] : rn(d3.mean(f.shoreline.map((c) => grid.cells.temp[cells.g[c]])), 1);
|
||||
const height = (f.height - 18) ** heightExponentInput.value; // height in meters
|
||||
const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp); // based on Penman formula, [1-11]
|
||||
f.evaporation = rn(evaporation * f.cells);
|
||||
|
|
@ -31,16 +31,16 @@ window.Lakes = (function () {
|
|||
// get array of land cells aroound lake
|
||||
const getShoreline = function (lake) {
|
||||
const uniqueCells = new Set();
|
||||
lake.vertices.forEach(v => pack.vertices.c[v].forEach(c => pack.cells.h[c] >= 20 && uniqueCells.add(c)));
|
||||
lake.vertices.forEach((v) => pack.vertices.c[v].forEach((c) => pack.cells.h[c] >= 20 && uniqueCells.add(c)));
|
||||
lake.shoreline = [...uniqueCells];
|
||||
};
|
||||
|
||||
const prepareLakeData = h => {
|
||||
const prepareLakeData = (h) => {
|
||||
const cells = pack.cells;
|
||||
const ELEVATION_LIMIT = +document.getElementById("lakeElevationLimitOutput").value;
|
||||
const ELEVATION_LIMIT = +document.getElementById('lakeElevationLimitOutput').value;
|
||||
|
||||
pack.features.forEach(f => {
|
||||
if (f.type !== "lake") return;
|
||||
pack.features.forEach((f) => {
|
||||
if (f.type !== 'lake') return;
|
||||
delete f.flux;
|
||||
delete f.inlets;
|
||||
delete f.outlet;
|
||||
|
|
@ -74,7 +74,7 @@ window.Lakes = (function () {
|
|||
|
||||
if (h[n] < 20) {
|
||||
const nFeature = pack.features[cells.f[n]];
|
||||
if (nFeature.type === "ocean" || f.height > nFeature.height) {
|
||||
if (nFeature.type === 'ocean' || f.height > nFeature.height) {
|
||||
deep = false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -91,25 +91,25 @@ window.Lakes = (function () {
|
|||
|
||||
const cleanupLakeData = function () {
|
||||
for (const feature of pack.features) {
|
||||
if (feature.type !== "lake") continue;
|
||||
if (feature.type !== 'lake') continue;
|
||||
delete feature.river;
|
||||
delete feature.enteringFlux;
|
||||
delete feature.outCell;
|
||||
delete feature.closed;
|
||||
feature.height = rn(feature.height, 3);
|
||||
|
||||
const inlets = feature.inlets?.filter(r => pack.rivers.find(river => river.i === r));
|
||||
const inlets = feature.inlets?.filter((r) => pack.rivers.find((river) => river.i === r));
|
||||
if (!inlets || !inlets.length) delete feature.inlets;
|
||||
else feature.inlets = inlets;
|
||||
|
||||
const outlet = feature.outlet && pack.rivers.find(river => river.i === feature.outlet);
|
||||
const outlet = feature.outlet && pack.rivers.find((river) => river.i === feature.outlet);
|
||||
if (!outlet) delete feature.outlet;
|
||||
}
|
||||
};
|
||||
|
||||
const defineGroup = function () {
|
||||
for (const feature of pack.features) {
|
||||
if (feature.type !== "lake") continue;
|
||||
if (feature.type !== 'lake') continue;
|
||||
const lakeEl = lakes.select(`[data-f="${feature.i}"]`).node();
|
||||
if (!lakeEl) continue;
|
||||
|
||||
|
|
@ -121,29 +121,29 @@ window.Lakes = (function () {
|
|||
const generateName = function () {
|
||||
Math.random = aleaPRNG(seed);
|
||||
for (const feature of pack.features) {
|
||||
if (feature.type !== "lake") continue;
|
||||
if (feature.type !== 'lake') continue;
|
||||
feature.name = getName(feature);
|
||||
}
|
||||
};
|
||||
|
||||
const getName = function (feature) {
|
||||
const landCell = pack.cells.c[feature.firstCell].find(c => pack.cells.h[c] >= 20);
|
||||
const landCell = pack.cells.c[feature.firstCell].find((c) => pack.cells.h[c] >= 20);
|
||||
const culture = pack.cells.culture[landCell];
|
||||
return Names.getCulture(culture);
|
||||
};
|
||||
|
||||
function getGroup(feature) {
|
||||
if (feature.temp < -3) return "frozen";
|
||||
if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 10 === 0) return "lava";
|
||||
if (feature.temp < -3) return 'frozen';
|
||||
if (feature.height > 60 && feature.cells < 10 && feature.firstCell % 10 === 0) return 'lava';
|
||||
|
||||
if (!feature.inlets && !feature.outlet) {
|
||||
if (feature.evaporation > feature.flux * 4) return "dry";
|
||||
if (feature.cells < 3 && feature.firstCell % 10 === 0) return "sinkhole";
|
||||
if (feature.evaporation > feature.flux * 4) return 'dry';
|
||||
if (feature.cells < 3 && feature.firstCell % 10 === 0) return 'sinkhole';
|
||||
}
|
||||
|
||||
if (!feature.outlet && feature.evaporation > feature.flux) return "salt";
|
||||
if (!feature.outlet && feature.evaporation > feature.flux) return 'salt';
|
||||
|
||||
return "freshwater";
|
||||
return 'freshwater';
|
||||
}
|
||||
|
||||
return {setClimateData, cleanupLakeData, prepareLakeData, defineGroup, generateName, getName, getShoreline};
|
||||
|
|
|
|||
306
modules/load.js
306
modules/load.js
|
|
@ -1,5 +1,5 @@
|
|||
// Functions to save and load the map
|
||||
'use strict';
|
||||
// Functions to load and parse .map files
|
||||
|
||||
function quickLoad() {
|
||||
ldb.get('lastMap', (blob) => {
|
||||
|
|
@ -12,6 +12,37 @@ function quickLoad() {
|
|||
});
|
||||
}
|
||||
|
||||
async function loadFromDropbox() {
|
||||
const mapPath = document.getElementById("loadFromDropboxSelect")?.value;
|
||||
|
||||
DEBUG && console.log("Loading map from Dropbox:", mapPath);
|
||||
const blob = await Cloud.providers.dropbox.load(mapPath);
|
||||
uploadMap(blob);
|
||||
}
|
||||
|
||||
async function createSharableDropboxLink() {
|
||||
const mapFile = document.querySelector("#loadFromDropbox select").value;
|
||||
const sharableLink = document.getElementById("sharableLink");
|
||||
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
|
||||
let url;
|
||||
try {
|
||||
url = await Cloud.providers.dropbox.getLink(mapFile);
|
||||
} catch {
|
||||
tip("Dropbox API error. Can not create link.", true, "error", 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
const fmg = window.location.href.split("?")[0];
|
||||
const reallink = `${fmg}?maplink=${url}`;
|
||||
// voodoo magic required by the yellow god of CORS
|
||||
const link = reallink.replace("www.dropbox.com/s/", "dl.dropboxusercontent.com/1/view/");
|
||||
const shortLink = link.slice(0, 50) + "...";
|
||||
|
||||
sharableLinkContainer.style.display = "block";
|
||||
sharableLink.innerText = shortLink;
|
||||
sharableLink.setAttribute("href", link);
|
||||
}
|
||||
|
||||
function loadMapPrompt(blob) {
|
||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||
if (workingTime < 5) {
|
||||
|
|
@ -46,52 +77,110 @@ function loadMapPrompt(blob) {
|
|||
}
|
||||
}
|
||||
|
||||
function loadMapFromURL(maplink, random) {
|
||||
const URL = decodeURIComponent(maplink);
|
||||
|
||||
fetch(URL, {method: "GET", mode: "cors"})
|
||||
.then(response => {
|
||||
if (response.ok) return response.blob();
|
||||
throw new Error("Cannot load map from URL");
|
||||
})
|
||||
.then(blob => uploadMap(blob))
|
||||
.catch(error => {
|
||||
showUploadErrorMessage(error.message, URL, random);
|
||||
if (random) generateMapOnLoad();
|
||||
});
|
||||
}
|
||||
|
||||
function showUploadErrorMessage(error, URL, random) {
|
||||
ERROR && console.error(error);
|
||||
alertMessage.innerHTML = `Cannot load map from the ${link(URL, "link provided")}.
|
||||
${random ? `A new random map is generated. ` : ""}
|
||||
Please ensure the linked file is reachable and CORS is allowed on server side`;
|
||||
$("#alert").dialog({
|
||||
title: "Loading error",
|
||||
width: "32em",
|
||||
buttons: {
|
||||
OK: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function uploadMap(file, callback) {
|
||||
uploadMap.timeStart = performance.now();
|
||||
const OLDEST_SUPPORTED_VERSION = 0.7;
|
||||
const currentVersion = parseFloat(version);
|
||||
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = function (fileLoadedEvent) {
|
||||
if (callback) callback();
|
||||
document.getElementById('coas').innerHTML = ''; // remove auto-generated emblems
|
||||
const result = fileLoadedEvent.target.result;
|
||||
const [mapData, mapVersion] = parseLoadedResult(result);
|
||||
|
||||
const dataLoaded = fileLoadedEvent.target.result;
|
||||
const isInvalid = !mapData || isNaN(mapVersion) || mapData.length < 26 || !mapData[5];
|
||||
const isUpdated = mapVersion === currentVersion;
|
||||
const data = dataLoaded.split('\r\n');
|
||||
const isAncient = mapVersion < OLDEST_SUPPORTED_VERSION;
|
||||
const isNewer = mapVersion > currentVersion;
|
||||
const isOutdated = mapVersion < currentVersion;
|
||||
|
||||
const mapVersion = data[0].split('|')[0] || data[0];
|
||||
if (mapVersion === version) {
|
||||
parseLoadedData(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const archive = link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog', 'archived version');
|
||||
const parsed = parseFloat(mapVersion);
|
||||
let message = '',
|
||||
load = false;
|
||||
if (isNaN(parsed) || data.length < 26 || !data[5]) {
|
||||
message = `The file you are trying to load is outdated or not a valid .map file.
|
||||
<br>Please try to open it using an ${archive}`;
|
||||
} else if (parsed < 0.7) {
|
||||
message = `The map version you are trying to load (${mapVersion}) is too old and cannot be updated to the current version.
|
||||
<br>Please keep using an ${archive}`;
|
||||
} else {
|
||||
load = true;
|
||||
message = `The map version (${mapVersion}) does not match the Generator version (${version}).
|
||||
<br>Click OK to get map <b>auto-updated</b>. In case of issues please keep using an ${archive} of the Generator`;
|
||||
}
|
||||
alertMessage.innerHTML = message;
|
||||
$('#alert').dialog({
|
||||
title: 'Version conflict',
|
||||
width: '38em',
|
||||
buttons: {
|
||||
OK: function () {
|
||||
$(this).dialog('close');
|
||||
if (load) parseLoadedData(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (isUpdated) return parseLoadedData(mapData);
|
||||
if (isAncient) return showUploadMessage("ancient", mapData, mapVersion);
|
||||
if (isNewer) return showUploadMessage("newer", mapData, mapVersion);
|
||||
if (isOutdated) return showUploadMessage("outdated", mapData, mapVersion);
|
||||
};
|
||||
|
||||
fileReader.readAsText(file, 'UTF-8');
|
||||
fileReader.readAsText(file, "UTF-8");
|
||||
}
|
||||
|
||||
function parseLoadedResult(result) {
|
||||
try {
|
||||
// data can be in FMG internal format or base64 encoded
|
||||
const isDelimited = result.substr(0, 10).includes("|");
|
||||
const decoded = isDelimited ? result : decodeURIComponent(atob(result));
|
||||
const mapData = decoded.split("\r\n");
|
||||
const mapVersion = parseFloat(mapData[0].split("|")[0] || mapData[0]);
|
||||
return [mapData, mapVersion];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [null, null];
|
||||
}
|
||||
}
|
||||
|
||||
function showUploadMessage(type, mapData, mapVersion) {
|
||||
const archive = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "archived version");
|
||||
let message, title, canBeLoaded;
|
||||
|
||||
if (type === "invalid") {
|
||||
message = `The file does not look like a valid <i>.map</i> file.<br>Please check the data format`;
|
||||
title = "Invalid file";
|
||||
canBeLoaded = false;
|
||||
} else if (type === "ancient") {
|
||||
message = `The map version you are trying to load (${mapVersion}) is too old and cannot be updated to the current version.<br>Please keep using an ${archive}`;
|
||||
title = "Ancient file";
|
||||
canBeLoaded = false;
|
||||
} else if (type === "newer") {
|
||||
message = `The map version you are trying to load (${mapVersion}) is newer than the current version.<br>Please load the file in the appropriate version`;
|
||||
title = "Newer file";
|
||||
canBeLoaded = false;
|
||||
} else if (type === "outdated") {
|
||||
message = `The map version (${mapVersion}) does not match the Generator version (${version}).<br>Click OK to get map <b>auto-updated</b>.<br>In case of issues please keep using an ${archive} of the Generator`;
|
||||
title = "Outdated file";
|
||||
canBeLoaded = true;
|
||||
}
|
||||
|
||||
alertMessage.innerHTML = message;
|
||||
const buttons = {
|
||||
OK: function () {
|
||||
$(this).dialog('close');
|
||||
if (canBeLoaded) parseLoadedData(mapData);
|
||||
}
|
||||
};
|
||||
$("#alert").dialog({title, buttons});
|
||||
}
|
||||
|
||||
function parseLoadedData(data) {
|
||||
|
|
@ -133,8 +222,8 @@ function parseLoadedData(data) {
|
|||
if (settings[11]) barPosY.value = settings[11];
|
||||
if (settings[12]) populationRate = populationRateInput.value = populationRateOutput.value = settings[12];
|
||||
if (settings[13]) urbanization = urbanizationInput.value = urbanizationOutput.value = settings[13];
|
||||
if (settings[14]) mapSizeInput.value = mapSizeOutput.value = Math.max(Math.min(settings[14], 100), 1);
|
||||
if (settings[15]) latitudeInput.value = latitudeOutput.value = Math.max(Math.min(settings[15], 100), 0);
|
||||
if (settings[14]) mapSizeInput.value = mapSizeOutput.value = minmax(settings[14], 1, 100);
|
||||
if (settings[15]) latitudeInput.value = latitudeOutput.value = minmax(settings[15], 0, 100);
|
||||
if (settings[16]) temperatureEquatorInput.value = temperatureEquatorOutput.value = settings[16];
|
||||
if (settings[17]) temperaturePoleInput.value = temperaturePoleOutput.value = settings[17];
|
||||
if (settings[18]) precInput.value = precOutput.value = settings[18];
|
||||
|
|
@ -142,13 +231,27 @@ function parseLoadedData(data) {
|
|||
if (settings[20]) mapName.value = settings[20];
|
||||
if (settings[21]) hideLabels.checked = +settings[21];
|
||||
if (settings[22]) stylePreset.value = settings[22];
|
||||
if (settings[23]) rescaleLabels.checked = settings[23];
|
||||
if (settings[23]) rescaleLabels.checked = +settings[23];
|
||||
if (settings[24]) urbanDensity = urbanDensityInput.value = urbanDensityOutput.value = +settings[24];
|
||||
})();
|
||||
|
||||
void (function applyOptionsToUI() {
|
||||
stateLabelsModeInput.value = options.stateLabelsMode;
|
||||
})();
|
||||
|
||||
void (function parseConfiguration() {
|
||||
if (data[2]) mapCoordinates = JSON.parse(data[2]);
|
||||
if (data[4]) notes = JSON.parse(data[4]);
|
||||
if (data[33]) rulers.fromString(data[33]);
|
||||
if (data[34]) {
|
||||
const usedFonts = JSON.parse(data[34]);
|
||||
usedFonts.forEach(usedFont => {
|
||||
const {family: usedFamily, unicodeRange: usedRange, variant: usedVariant} = usedFont;
|
||||
const defaultFont = fonts.find(({family, unicodeRange, variant}) => family === usedFamily && unicodeRange === usedRange && variant === usedVariant);
|
||||
if (!defaultFont) fonts.push(usedFont);
|
||||
declareFont(usedFont);
|
||||
});
|
||||
}
|
||||
|
||||
const biomes = data[3].split('|');
|
||||
biomesData = applyDefaultBiomesSystem();
|
||||
|
|
@ -222,8 +325,6 @@ function parseLoadedData(data) {
|
|||
burgLabels = labels.select('#burgLabels');
|
||||
})();
|
||||
|
||||
loadUsedFonts();
|
||||
|
||||
void (function parseGridData() {
|
||||
grid = JSON.parse(data[6]);
|
||||
calculateVoronoi(grid, grid.points);
|
||||
|
|
@ -245,7 +346,9 @@ function parseLoadedData(data) {
|
|||
pack.religions = data[29] ? JSON.parse(data[29]) : [{i: 0, name: 'No religion'}];
|
||||
pack.provinces = data[30] ? JSON.parse(data[30]) : [0];
|
||||
pack.rivers = data[32] ? JSON.parse(data[32]) : [];
|
||||
pack.resources = data[35] ? JSON.parse(data[35]) : [];
|
||||
// TODO: ***** both 35?
|
||||
pack.resources = data[36] ? JSON.parse(data[36]) : [];
|
||||
pack.markers = data[35] ? JSON.parse(data[35]) : [];
|
||||
|
||||
const cells = pack.cells;
|
||||
cells.biome = Uint8Array.from(data[16].split(','));
|
||||
|
|
@ -339,23 +442,27 @@ function parseLoadedData(data) {
|
|||
// 1.0 adds a legend box
|
||||
legend = svg.append('g').attr('id', 'legend');
|
||||
legend
|
||||
.attr('font-family', 'Almendra SC')
|
||||
.attr('data-font', 'Almendra+SC')
|
||||
.attr('font-size', 13)
|
||||
.attr('data-size', 13)
|
||||
.attr('data-x', 99)
|
||||
.attr('data-y', 93)
|
||||
.attr('stroke-width', 2.5)
|
||||
.attr('stroke', '#812929')
|
||||
.attr('stroke-dasharray', '0 4 10 4')
|
||||
.attr("font-family", "Almendra SC")
|
||||
.attr("font-size", 13)
|
||||
.attr("data-size", 13)
|
||||
.attr("data-x", 99)
|
||||
.attr("data-y", 93)
|
||||
.attr("stroke-width", 2.5)
|
||||
.attr("stroke", "#812929")
|
||||
.attr("stroke-dasharray", "0 4 10 4")
|
||||
.attr("stroke-linecap", "round");
|
||||
.attr('stroke-linecap', 'round');
|
||||
|
||||
// 1.0 separated drawBorders fron drawStates()
|
||||
stateBorders = borders.append('g').attr('id', 'stateBorders');
|
||||
provinceBorders = borders.append('g').attr('id', 'provinceBorders');
|
||||
borders.attr('opacity', null).attr('stroke', null).attr('stroke-width', null).attr('stroke-dasharray', null).attr('stroke-linecap', null).attr('filter', null);
|
||||
stateBorders.attr('opacity', 0.8).attr('stroke', '#56566d').attr('stroke-width', 1).attr('stroke-dasharray', '2').attr('stroke-linecap', 'butt');
|
||||
provinceBorders.attr('opacity', 0.8).attr('stroke', '#56566d').attr('stroke-width', 0.5).attr('stroke-dasharray', '1').attr('stroke-linecap', 'butt');
|
||||
borders
|
||||
.attr("opacity", null)
|
||||
.attr("stroke", null)
|
||||
.attr("stroke-width", null)
|
||||
.attr("stroke-dasharray", null)
|
||||
.attr("stroke-linecap", null)
|
||||
.attr("filter", null);
|
||||
|
||||
// 1.0 adds state relations, provinces, forms and full names
|
||||
provs = viewbox.insert('g', '#borders').attr('id', 'provs').attr('opacity', 0.6);
|
||||
|
|
@ -377,7 +484,7 @@ function parseLoadedData(data) {
|
|||
zones.attr('opacity', 0.6).attr('stroke', null).attr('stroke-width', 0).attr('stroke-dasharray', null).attr('stroke-linecap', 'butt');
|
||||
addZones();
|
||||
if (!markers.selectAll('*').size()) {
|
||||
addMarkers();
|
||||
Markers.generate();
|
||||
turnButtonOn('toggleMarkers');
|
||||
}
|
||||
|
||||
|
|
@ -729,29 +836,41 @@ function parseLoadedData(data) {
|
|||
|
||||
if (version < 1.65) {
|
||||
// v 1.65 changed rivers data
|
||||
rivers.attr('style', null); // remove style to unhide layer
|
||||
d3.select('#rivers').attr('style', null); // remove style to unhide layer
|
||||
const {cells, rivers} = pack;
|
||||
|
||||
for (const river of pack.rivers) {
|
||||
for (const river of rivers) {
|
||||
const node = document.getElementById('river' + river.i);
|
||||
if (node && !river.cells) {
|
||||
const riverCells = new Set();
|
||||
const riverCells = [];
|
||||
const riverPoints = [];
|
||||
|
||||
const length = node.getTotalLength() / 2;
|
||||
if (!length) continue;
|
||||
const segments = Math.ceil(length / 6);
|
||||
const increment = length / segments;
|
||||
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p1 = node.getPointAtLength(i);
|
||||
const p2 = node.getPointAtLength(c);
|
||||
const x = (p1.x + p2.x) / 2;
|
||||
const y = (p1.y + p2.y) / 2;
|
||||
const cell = findCell(x, y, 6);
|
||||
if (cell) riverCells.add(cell);
|
||||
|
||||
for (let i = 0; i <= segments; i++) {
|
||||
const shift = increment * i;
|
||||
const {x: x1, y: y1} = node.getPointAtLength(length + shift);
|
||||
const {x: x2, y: y2} = node.getPointAtLength(length - shift);
|
||||
const x = rn((x1 + x2) / 2, 1);
|
||||
const y = rn((y1 + y2) / 2, 1);
|
||||
|
||||
const cell = findCell(x, y);
|
||||
riverPoints.push([x, y]);
|
||||
riverCells.push(cell);
|
||||
}
|
||||
|
||||
river.cells = Array.from(riverCells);
|
||||
river.cells = riverCells;
|
||||
river.points = riverPoints;
|
||||
}
|
||||
|
||||
pack.cells.i.forEach((i) => {
|
||||
if (pack.cells.r[i] && pack.cells.h[i] < 20) pack.cells.r[i] = 0;
|
||||
river.widthFactor = 1;
|
||||
|
||||
cells.i.forEach((i) => {
|
||||
const riverInWater = cells.r[i] && cells.h[i] < 20;
|
||||
if (riverInWater) cells.r[i] = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -764,6 +883,59 @@ function parseLoadedData(data) {
|
|||
|
||||
// ecomonics:
|
||||
// calculate salesTax for all states
|
||||
if (version < 1.7) {
|
||||
// v 1.7 changed markers data
|
||||
const defs = document.getElementById("defs-markers");
|
||||
const markersGroup = document.getElementById("markers");
|
||||
const markerElements = markersGroup.querySelectorAll("use");
|
||||
const rescale = +markersGroup.getAttribute("rescale");
|
||||
|
||||
pack.markers = Array.from(markerElements).map((el, i) => {
|
||||
const id = el.getAttribute("id");
|
||||
const note = notes.find(note => note.id === id);
|
||||
if (note) note.id = `marker${i}`;
|
||||
|
||||
let x = +el.dataset.x;
|
||||
let y = +el.dataset.y;
|
||||
const transform = el.getAttribute("transform");
|
||||
if (transform) {
|
||||
const [dx, dy] = parseTransform(transform);
|
||||
if (dx) x += +dx;
|
||||
if (dy) y += +dy;
|
||||
}
|
||||
const cell = findCell(x, y);
|
||||
const size = rn(rescale ? el.dataset.size * 30 : el.getAttribute("width"), 1);
|
||||
|
||||
const href = el.href.baseVal;
|
||||
const type = href.replace("#marker_", "");
|
||||
const symbol = defs.querySelector(`symbol${href}`);
|
||||
const text = symbol.querySelector("text");
|
||||
const circle = symbol.querySelector("circle");
|
||||
|
||||
const icon = text.innerHTML;
|
||||
const px = Number(text.getAttribute("font-size")?.replace("px", ""));
|
||||
const dx = Number(text.getAttribute("x")?.replace("%", ""));
|
||||
const dy = Number(text.getAttribute("y")?.replace("%", ""));
|
||||
const fill = circle.getAttribute("fill");
|
||||
const stroke = circle.getAttribute("stroke");
|
||||
|
||||
const marker = {i, icon, type, x, y, size, cell};
|
||||
if (size && size !== 30) marker.size = size;
|
||||
if (!isNaN(px) && px !== 12) marker.px = px;
|
||||
if (!isNaN(dx) && dx !== 50) marker.dx = dx;
|
||||
if (!isNaN(dy) && dy !== 50) marker.dy = dy;
|
||||
if (fill && fill !== "#ffffff") marker.fill = fill;
|
||||
if (stroke && stroke !== "#000000") marker.stroke = stroke;
|
||||
if (circle.getAttribute("opacity") === "0") marker.pin = "no";
|
||||
|
||||
return marker;
|
||||
});
|
||||
|
||||
markersGroup.style.display = null;
|
||||
defs.remove();
|
||||
markerElements.forEach(el => el.remove());
|
||||
if (layerIsOn("markers")) drawMarkers();
|
||||
}
|
||||
})();
|
||||
|
||||
void (function checkDataIntegrity() {
|
||||
|
|
@ -891,7 +1063,7 @@ function parseLoadedData(data) {
|
|||
},
|
||||
'New map': function () {
|
||||
$(this).dialog('close');
|
||||
regenerateMap();
|
||||
regenerateMap("loading error");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
|
|
|
|||
1204
modules/load.js.orig
Normal file
1204
modules/load.js.orig
Normal file
File diff suppressed because it is too large
Load diff
803
modules/markers-generator.js
Normal file
803
modules/markers-generator.js
Normal file
|
|
@ -0,0 +1,803 @@
|
|||
'use strict';
|
||||
|
||||
window.Markers = (function () {
|
||||
let config = [];
|
||||
let occupied = [];
|
||||
|
||||
function getDefaultConfig() {
|
||||
const culturesSet = document.getElementById('culturesSet').value;
|
||||
const isFantasy = culturesSet.includes('Fantasy');
|
||||
|
||||
return [
|
||||
{type: 'volcanoes', icon: '🌋', multiplier: 1, fn: addVolcanoes},
|
||||
{type: 'hot-springs', icon: '♨️', multiplier: 1, fn: addHotSprings},
|
||||
{type: 'mines', icon: '⛏️', multiplier: 1, fn: addMines},
|
||||
{type: 'bridges', icon: '🌉', multiplier: 1, fn: addBridges},
|
||||
{type: 'inns', icon: '🍻', multiplier: 1, fn: addInns},
|
||||
{type: 'lighthouses', icon: '🚨', multiplier: 1, fn: addLighthouses},
|
||||
{type: 'waterfalls', icon: '⟱', multiplier: 1, fn: addWaterfalls},
|
||||
{type: 'battlefields', icon: '⚔️', multiplier: 1, fn: addBattlefields},
|
||||
{type: 'dungeons', icon: '🗝️', multiplier: 1, fn: addDungeons},
|
||||
{type: 'lake-monsters', icon: '🐉', multiplier: 1, fn: addLakeMonsters},
|
||||
{type: 'sea-monsters', icon: '🦑', multiplier: 1, fn: addSeaMonsters},
|
||||
{type: 'hill-monsters', icon: '👹', multiplier: 1, fn: addHillMonsters},
|
||||
{type: 'sacred-mountains', icon: '🗻', multiplier: 1, fn: addSacredMountains},
|
||||
{type: 'sacred-forests', icon: '🌳', multiplier: 1, fn: addSacredForests},
|
||||
{type: 'sacred-pineries', icon: '🌲', multiplier: 1, fn: addSacredPineries},
|
||||
{type: 'sacred-palm-groves', icon: '🌴', multiplier: 1, fn: addSacredPalmGroves},
|
||||
{type: 'brigands', icon: '💰', multiplier: 1, fn: addBrigands},
|
||||
{type: 'pirates', icon: '🏴☠️', multiplier: 1, fn: addPirates},
|
||||
{type: 'statues', icon: '🗿', multiplier: 1, fn: addStatues},
|
||||
{type: 'ruines', icon: '🏺', multiplier: 1, fn: addRuines},
|
||||
{type: 'portals', icon: '🌀', multiplier: +isFantasy, fn: addPortals}
|
||||
];
|
||||
}
|
||||
|
||||
const getConfig = () => config;
|
||||
|
||||
const setConfig = (newConfig) => {
|
||||
config = newConfig;
|
||||
};
|
||||
|
||||
const generate = function () {
|
||||
setConfig(getDefaultConfig());
|
||||
pack.markers = [];
|
||||
generateTypes();
|
||||
};
|
||||
|
||||
const regenerate = () => {
|
||||
pack.markers = pack.markers.filter(({i, lock, cell}) => {
|
||||
if (lock) {
|
||||
occupied[cell] = true;
|
||||
return true;
|
||||
}
|
||||
const id = `marker${i}`;
|
||||
document.getElementById(id)?.remove();
|
||||
const index = notes.findIndex((note) => note.id === id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
return false;
|
||||
});
|
||||
|
||||
generateTypes();
|
||||
};
|
||||
|
||||
function generateTypes() {
|
||||
TIME && console.time('addMarkers');
|
||||
|
||||
config.forEach(({type, icon, multiplier, fn}) => {
|
||||
if (multiplier === 0) return;
|
||||
fn(type, icon, multiplier);
|
||||
});
|
||||
|
||||
occupied = [];
|
||||
TIME && console.timeEnd('addMarkers');
|
||||
}
|
||||
|
||||
function getQuantity(array, min, each, multiplier) {
|
||||
if (!array.length || array.length < min / multiplier) return 0;
|
||||
const requestQty = Math.ceil((array.length / each) * multiplier);
|
||||
return array.length < requestQty ? array.length : requestQty;
|
||||
}
|
||||
|
||||
function extractAnyElement(array) {
|
||||
const index = Math.floor(Math.random() * array.length);
|
||||
return array.splice(index, 1);
|
||||
}
|
||||
|
||||
function getMarkerCoordinates(cell) {
|
||||
const {cells, burgs} = pack;
|
||||
const burgId = cells.burg[cell];
|
||||
|
||||
if (burgId) {
|
||||
const {x, y} = burgs[burgId];
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
return cells.p[cell];
|
||||
}
|
||||
|
||||
function addMarker({cell, type, icon, dx, dy, px}) {
|
||||
const i = pack.markers.length;
|
||||
const [x, y] = getMarkerCoordinates(cell);
|
||||
const marker = {i, icon, type, x, y, cell};
|
||||
if (dx) marker.dx = dx;
|
||||
if (dy) marker.dy = dy;
|
||||
if (px) marker.px = px;
|
||||
pack.markers.push(marker);
|
||||
occupied[cell] = true;
|
||||
return 'marker' + i;
|
||||
}
|
||||
|
||||
function addVolcanoes(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let mountains = Array.from(cells.i.filter((i) => !occupied[i] && cells.h[i] >= 70).sort((a, b) => cells.h[b] - cells.h[a]));
|
||||
let quantity = getQuantity(mountains, 10, 500, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(mountains);
|
||||
const id = addMarker({cell, icon, type, dx: 52, px: 13});
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const name = P(0.3) ? 'Mount ' + proper : Math.random() > 0.3 ? proper + ' Volcano' : proper;
|
||||
notes.push({id, name, legend: `Active volcano. Height: ${getFriendlyHeight(cells.p[cell])}`});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addHotSprings(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let springs = Array.from(cells.i.filter((i) => !occupied[i] && cells.h[i] > 50).sort((a, b) => cells.h[b] - cells.h[a]));
|
||||
let quantity = getQuantity(springs, 30, 1200, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(springs);
|
||||
const id = addMarker({cell, icon, type, dy: 52});
|
||||
const proper = Names.getCulture(cells.culture[cell]);
|
||||
const temp = convertTemperature(gauss(35, 15, 20, 100));
|
||||
notes.push({id, name: proper + ' Hot Springs', legend: `A hot springs area. Average temperature: ${temp}`});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addMines(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let hillyBurgs = Array.from(cells.i.filter((i) => !occupied[i] && cells.h[i] > 47 && cells.burg[i]));
|
||||
let quantity = getQuantity(hillyBurgs, 1, 15, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const resources = {salt: 5, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1};
|
||||
|
||||
while (quantity && hillyBurgs.length) {
|
||||
const [cell] = extractAnyElement(hillyBurgs);
|
||||
const id = addMarker({cell, icon, type, dx: 48, px: 13});
|
||||
const resource = rw(resources);
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const name = `${burg.name} — ${resource} mining town`;
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addBridges(type, icon, multiplier) {
|
||||
const {cells, burgs} = pack;
|
||||
|
||||
const meanFlux = d3.mean(cells.fl.filter((fl) => fl));
|
||||
let bridges = Array.from(cells.i.filter((i) => !occupied[i] && cells.burg[i] && cells.t[i] !== 1 && burgs[cells.burg[i]].population > 20 && cells.r[i] && cells.fl[i] > meanFlux));
|
||||
let quantity = getQuantity(bridges, 1, 5, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(bridges);
|
||||
const id = addMarker({cell, icon, type, px: 14});
|
||||
const burg = pack.burgs[cells.burg[cell]];
|
||||
const river = pack.rivers.find((r) => r.i === pack.cells.r[cell]);
|
||||
const riverName = river ? `${river.name} ${river.type}` : 'river';
|
||||
const name = river && P(0.2) ? river.name : burg.name;
|
||||
const weightedAdjectives = {
|
||||
stone: 10,
|
||||
wooden: 1,
|
||||
lengthy: 2,
|
||||
formidable: 2,
|
||||
rickety: 1,
|
||||
beaten: 1,
|
||||
weathered: 1
|
||||
};
|
||||
notes.push({id, name: `${name} Bridge`, legend: `A ${rw(weightedAdjectives)} bridge spans over the ${riverName} near ${burg.name}`});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addInns(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let taverns = Array.from(cells.i.filter((i) => !occupied[i] && cells.h[i] >= 20 && cells.road[i] > 4 && cells.pop[i] > 10));
|
||||
let quantity = getQuantity(taverns, 1, 100, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const colors = ['Dark', 'Light', 'Bright', 'Golden', 'White', 'Black', 'Red', 'Pink', 'Purple', 'Blue', 'Green', 'Yellow', 'Amber', 'Orange', 'Brown', 'Grey'];
|
||||
const animals = [
|
||||
'Antelope',
|
||||
'Ape',
|
||||
'Badger',
|
||||
'Bear',
|
||||
'Beaver',
|
||||
'Bison',
|
||||
'Boar',
|
||||
'Buffalo',
|
||||
'Cat',
|
||||
'Crane',
|
||||
'Crocodile',
|
||||
'Crow',
|
||||
'Deer',
|
||||
'Dog',
|
||||
'Eagle',
|
||||
'Elk',
|
||||
'Fox',
|
||||
'Goat',
|
||||
'Goose',
|
||||
'Hare',
|
||||
'Hawk',
|
||||
'Heron',
|
||||
'Horse',
|
||||
'Hyena',
|
||||
'Ibis',
|
||||
'Jackal',
|
||||
'Jaguar',
|
||||
'Lark',
|
||||
'Leopard',
|
||||
'Lion',
|
||||
'Mantis',
|
||||
'Marten',
|
||||
'Moose',
|
||||
'Mule',
|
||||
'Narwhal',
|
||||
'Owl',
|
||||
'Panther',
|
||||
'Rat',
|
||||
'Raven',
|
||||
'Rook',
|
||||
'Scorpion',
|
||||
'Shark',
|
||||
'Sheep',
|
||||
'Snake',
|
||||
'Spider',
|
||||
'Swan',
|
||||
'Tiger',
|
||||
'Turtle',
|
||||
'Wolf',
|
||||
'Wolverine',
|
||||
'Camel',
|
||||
'Falcon',
|
||||
'Hound',
|
||||
'Ox'
|
||||
];
|
||||
const adjectives = [
|
||||
'New',
|
||||
'Good',
|
||||
'High',
|
||||
'Old',
|
||||
'Great',
|
||||
'Big',
|
||||
'Major',
|
||||
'Happy',
|
||||
'Main',
|
||||
'Huge',
|
||||
'Far',
|
||||
'Beautiful',
|
||||
'Fair',
|
||||
'Prime',
|
||||
'Ancient',
|
||||
'Golden',
|
||||
'Proud',
|
||||
'Lucky',
|
||||
'Fat',
|
||||
'Honest',
|
||||
'Giant',
|
||||
'Distant',
|
||||
'Friendly',
|
||||
'Loud',
|
||||
'Hungry',
|
||||
'Magical',
|
||||
'Superior',
|
||||
'Peaceful',
|
||||
'Frozen',
|
||||
'Divine',
|
||||
'Favorable',
|
||||
'Brave',
|
||||
'Sunny',
|
||||
'Flying'
|
||||
];
|
||||
const methods = [
|
||||
'Boiled',
|
||||
'Grilled',
|
||||
'Roasted',
|
||||
'Spit-roasted',
|
||||
'Stewed',
|
||||
'Stuffed',
|
||||
'Jugged',
|
||||
'Mashed',
|
||||
'Baked',
|
||||
'Braised',
|
||||
'Poached',
|
||||
'Marinated',
|
||||
'Pickled',
|
||||
'Smoked',
|
||||
'Dried',
|
||||
'Dry-aged',
|
||||
'Corned',
|
||||
'Fried',
|
||||
'Pan-fried',
|
||||
'Deep-fried',
|
||||
'Dressed',
|
||||
'Steamed',
|
||||
'Cured',
|
||||
'Syrupped',
|
||||
'Flame-Broiled'
|
||||
];
|
||||
const courses = [
|
||||
'beef',
|
||||
'pork',
|
||||
'bacon',
|
||||
'chicken',
|
||||
'lamb',
|
||||
'chevon',
|
||||
'hare',
|
||||
'rabbit',
|
||||
'hart',
|
||||
'deer',
|
||||
'antlers',
|
||||
'bear',
|
||||
'buffalo',
|
||||
'badger',
|
||||
'beaver',
|
||||
'turkey',
|
||||
'pheasant',
|
||||
'duck',
|
||||
'goose',
|
||||
'teal',
|
||||
'quail',
|
||||
'pigeon',
|
||||
'seal',
|
||||
'carp',
|
||||
'bass',
|
||||
'pike',
|
||||
'catfish',
|
||||
'sturgeon',
|
||||
'escallop',
|
||||
'pie',
|
||||
'cake',
|
||||
'pottage',
|
||||
'pudding',
|
||||
'onions',
|
||||
'carrot',
|
||||
'potato',
|
||||
'beet',
|
||||
'garlic',
|
||||
'cabbage',
|
||||
'eggplant',
|
||||
'eggs',
|
||||
'broccoli',
|
||||
'zucchini',
|
||||
'pepper',
|
||||
'olives',
|
||||
'pumpkin',
|
||||
'spinach',
|
||||
'peas',
|
||||
'chickpea',
|
||||
'beans',
|
||||
'rice',
|
||||
'pasta',
|
||||
'bread',
|
||||
'apples',
|
||||
'peaches',
|
||||
'pears',
|
||||
'melon',
|
||||
'oranges',
|
||||
'mango',
|
||||
'tomatoes',
|
||||
'cheese',
|
||||
'corn',
|
||||
'rat tails',
|
||||
'pig ears'
|
||||
];
|
||||
const types = ['hot', 'cold', 'fire', 'ice', 'smoky', 'misty', 'shiny', 'sweet', 'bitter', 'salty', 'sour', 'sparkling', 'smelly'];
|
||||
const drinks = [
|
||||
'wine',
|
||||
'brandy',
|
||||
'jinn',
|
||||
'whisky',
|
||||
'rom',
|
||||
'beer',
|
||||
'cider',
|
||||
'mead',
|
||||
'liquor',
|
||||
'spirit',
|
||||
'vodka',
|
||||
'tequila',
|
||||
'absinthe',
|
||||
'nectar',
|
||||
'milk',
|
||||
'kvass',
|
||||
'kumis',
|
||||
'tea',
|
||||
'water',
|
||||
'juice',
|
||||
'sap'
|
||||
];
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(taverns);
|
||||
const id = addMarker({cell, icon, type, px: 14});
|
||||
const typeName = P(0.3) ? 'inn' : 'tavern';
|
||||
const isAnimalThemed = P(0.7);
|
||||
const animal = ra(animals);
|
||||
const name = isAnimalThemed ? (P(0.6) ? ra(colors) + ' ' + animal : ra(adjectives) + ' ' + animal) : ra(adjectives) + ' ' + capitalize(type);
|
||||
const meal = isAnimalThemed && P(0.3) ? animal : ra(courses);
|
||||
const course = `${ra(methods)} ${meal}`.toLowerCase();
|
||||
const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.toLowerCase();
|
||||
const legend = `A big and famous roadside ${typeName}. Delicious ${course} with ${drink} is served here`;
|
||||
notes.push({id, name: 'The ' + name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addLighthouses(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
const lighthouses = Array.from(cells.i.filter((i) => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some((c) => cells.h[c] < 20 && cells.road[c])));
|
||||
let quantity = getQuantity(lighthouses, 1, 2, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(lighthouses);
|
||||
const id = addMarker({cell, icon, type, px: 14});
|
||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||
notes.push({id, name: getAdjective(proper) + ' Lighthouse' + name, legend: `A lighthouse to serve as a beacon for ships in the open sea`});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addWaterfalls(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
const waterfalls = Array.from(cells.i.filter((i) => cells.r[i] && !occupied[i] && cells.h[i] >= 50 && cells.c[i].some((c) => cells.h[c] < 40 && cells.r[c])));
|
||||
const quantity = getQuantity(waterfalls, 1, 5, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const descriptions = [
|
||||
'A gorgeous waterfall flows here',
|
||||
'The rapids of an exceptionally beautiful waterfall',
|
||||
'An impressive waterfall has cut through the land',
|
||||
'The cascades of a stunning waterfall',
|
||||
'A river drops down from a great height forming a wonderous waterfall',
|
||||
'A breathtaking waterfall cuts through the landscape'
|
||||
];
|
||||
for (let i = 0; i < waterfalls.length && i < quantity; i++) {
|
||||
const cell = waterfalls[i];
|
||||
const id = addMarker({cell, icon, type, dy: 54, px: 16});
|
||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||
notes.push({id, name: getAdjective(proper) + ' Waterfall' + name, legend: `${ra(descriptions)}`});
|
||||
}
|
||||
}
|
||||
|
||||
function addBattlefields(type, icon, multiplier) {
|
||||
const {cells, states} = pack;
|
||||
|
||||
let battlefields = Array.from(cells.i.filter((i) => !occupied[i] && cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25));
|
||||
let quantity = getQuantity(battlefields, 50, 700, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity && battlefields.length) {
|
||||
const [cell] = extractAnyElement(battlefields);
|
||||
const id = addMarker({cell, icon, type, dy: 52});
|
||||
const state = states[cells.state[cell]];
|
||||
if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state);
|
||||
const campaign = ra(state.campaigns);
|
||||
const date = generateDate(campaign.start, campaign.end);
|
||||
const name = Names.getCulture(cells.culture[cell]) + ' Battlefield';
|
||||
const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addDungeons(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let dungeons = Array.from(cells.i.filter((i) => !occupied[i] && cells.pop[i] && cells.pop[i] < 3));
|
||||
let quantity = getQuantity(dungeons, 30, 200, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(dungeons);
|
||||
const id = addMarker({cell, icon, type, dy: 51, px: 13});
|
||||
|
||||
const dungeonSeed = `${seed}${cell}`;
|
||||
const name = 'Dungeon';
|
||||
const legend = `<div>Undiscovered dungeon. See <a href="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" target="_blank">One page dungeon</a></div><iframe style="height: 33vh" src="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" sandbox="allow-scripts allow-same-origin"></iframe>`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addLakeMonsters(type, icon, multiplier) {
|
||||
const {features} = pack;
|
||||
|
||||
const lakes = features.filter((feature) => feature.type === 'lake' && feature.group === 'freshwater' && !occupied[feature.firstCell]);
|
||||
let quantity = getQuantity(lakes, 2, 10, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [lake] = extractAnyElement(lakes);
|
||||
const cell = lake.firstCell;
|
||||
const id = addMarker({cell, icon, type, dy: 48});
|
||||
const name = `${lake.name} Monster`;
|
||||
const length = gauss(10, 5, 5, 100);
|
||||
const legend = `Rumors say a relic monster of ${length} ${heightUnit.value} long inhabits ${lake.name} Lake. Truth or lie, folks are afraid to fish in the lake`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addSeaMonsters(type, icon, multiplier) {
|
||||
const {cells, features} = pack;
|
||||
|
||||
const sea = Array.from(cells.i.filter((i) => !occupied[i] && cells.h[i] < 20 && cells.road[i] && features[cells.f[i]].type === 'ocean'));
|
||||
let quantity = getQuantity(sea, 50, 700, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(sea);
|
||||
const id = addMarker({cell, icon, type});
|
||||
const name = `${Names.getCultureShort(0)} Monster`;
|
||||
const length = gauss(25, 10, 10, 100);
|
||||
const legend = `Old sailors tell stories of a gigantic sea monster inhabiting these dangerous waters. Rumors say it can be ${length} ${heightUnit.value} long`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addHillMonsters(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
const hills = Array.from(cells.i.filter((i) => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]));
|
||||
let quantity = getQuantity(hills, 30, 600, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const adjectives = ['great', 'big', 'huge', 'prime', 'golden', 'proud', 'lucky', 'fat', 'giant', 'hungry', 'magical', 'superior', 'terrifying', 'horrifying', 'feared'];
|
||||
const subjects = ['Locals', 'Elders', 'Inscriptions', 'Tipplers', 'Legends', 'Whispers', 'Rumors', 'Journeying folk', 'Tales'];
|
||||
const species = ['Ogre', 'Troll', 'Cyclops', 'Giant', 'Monster', 'Beast', 'Dragon', 'Undead', 'Ghoul', 'Vampire', 'Hag', 'Banshee', 'Bearded Devil', 'Roc', 'Hydra', 'Warg'];
|
||||
const modusOperandi = [
|
||||
'steals cattle at night',
|
||||
'prefers eating children',
|
||||
"doesn't mind of human flesh",
|
||||
'keeps the region at bay',
|
||||
'eats kids whole',
|
||||
'abducts young women',
|
||||
'terrorizes the region',
|
||||
'harasses travelers in the area',
|
||||
'snatches people from homes',
|
||||
'attacks anyone who dares to approach its lair',
|
||||
'attacks unsuspecting victims'
|
||||
];
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(hills);
|
||||
const id = addMarker({cell, icon, type, dy: 54, px: 13});
|
||||
const monster = ra(species);
|
||||
const toponym = Names.getCulture(cells.culture[cell]);
|
||||
const name = `${toponym} ${monster}`;
|
||||
const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${monster} who inhabits ${toponym} hills and ${ra(modusOperandi)}`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addSacredMountains(type, icon, multiplier) {
|
||||
const {cells, cultures} = pack;
|
||||
|
||||
let lonelyMountains = Array.from(cells.i.filter((i) => !occupied[i] && cells.h[i] >= 70 && cells.c[i].some((c) => cells.culture[c]) && cells.c[i].every((c) => cells.h[c] < 60)));
|
||||
let quantity = getQuantity(lonelyMountains, 1, 5, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(lonelyMountains);
|
||||
const id = addMarker({cell, icon, type, dy: 48});
|
||||
const culture = cells.c[cell].map((c) => cells.culture[c]).find((c) => c);
|
||||
const name = `${Names.getCulture(culture)} Mountain`;
|
||||
const height = getFriendlyHeight(cells.p[cell]);
|
||||
const legend = `A sacred mountain of ${cultures[culture].name} culture. Height: ${height}`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addSacredForests(type, icon, multiplier) {
|
||||
const {cells, cultures} = pack;
|
||||
|
||||
let temperateForests = Array.from(cells.i.filter((i) => !occupied[i] && cells.culture[i] && [6, 8].includes(cells.biome[i])));
|
||||
let quantity = getQuantity(temperateForests, 30, 1000, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(temperateForests);
|
||||
const id = addMarker({cell, icon, type});
|
||||
const culture = cells.culture[cell];
|
||||
const name = `${Names.getCulture(culture)} Forest`;
|
||||
const legend = `A sacred forest of ${cultures[culture].name} culture`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addSacredPineries(type, icon, multiplier) {
|
||||
const {cells, cultures} = pack;
|
||||
|
||||
let borealForests = Array.from(cells.i.filter((i) => !occupied[i] && cells.culture[i] && cells.biome[i] === 9));
|
||||
let quantity = getQuantity(borealForests, 30, 800, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(borealForests);
|
||||
const id = addMarker({cell, icon, type, px: 13});
|
||||
const culture = cells.culture[cell];
|
||||
const name = `${Names.getCulture(culture)} Pinery`;
|
||||
const legend = `A sacred pinery of ${cultures[culture].name} culture`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addSacredPalmGroves(type, icon, multiplier) {
|
||||
const {cells, cultures} = pack;
|
||||
|
||||
let oasises = Array.from(cells.i.filter((i) => !occupied[i] && cells.culture[i] && cells.biome[i] === 1 && cells.pop[i] > 1 && cells.road[i]));
|
||||
let quantity = getQuantity(oasises, 1, 100, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(oasises);
|
||||
const id = addMarker({cell, icon, type, px: 13});
|
||||
const culture = cells.culture[cell];
|
||||
const name = `${Names.getCulture(culture)} Palm Grove`;
|
||||
const legend = `A sacred palm grove of ${cultures[culture].name} culture`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addBrigands(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let roads = Array.from(cells.i.filter((i) => !occupied[i] && cells.culture[i] && cells.road[i] > 4));
|
||||
let quantity = getQuantity(roads, 50, 100, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const animals = [
|
||||
'Apes',
|
||||
'Badgers',
|
||||
'Bears',
|
||||
'Beavers',
|
||||
'Bisons',
|
||||
'Boars',
|
||||
'Cats',
|
||||
'Crows',
|
||||
'Dogs',
|
||||
'Foxes',
|
||||
'Hares',
|
||||
'Hawks',
|
||||
'Hyenas',
|
||||
'Jackals',
|
||||
'Jaguars',
|
||||
'Leopards',
|
||||
'Lions',
|
||||
'Owls',
|
||||
'Panthers',
|
||||
'Rats',
|
||||
'Ravens',
|
||||
'Rooks',
|
||||
'Scorpions',
|
||||
'Sharks',
|
||||
'Snakes',
|
||||
'Spiders',
|
||||
'Tigers',
|
||||
'Wolfs',
|
||||
'Wolverines',
|
||||
'Falcons'
|
||||
];
|
||||
const types = {brigands: 4, bandits: 3, robbers: 1, highwaymen: 1};
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(roads);
|
||||
const id = addMarker({cell, icon, type, px: 13});
|
||||
const culture = cells.culture[cell];
|
||||
const biome = cells.biome[cell];
|
||||
const height = cells.p[cell];
|
||||
const locality =
|
||||
height >= 70 ? 'highlander' : [1, 2].includes(biome) ? 'desert' : [3, 4].includes(biome) ? 'mounted' : [5, 6, 7, 8, 9].includes(biome) ? 'forest' : biome === 12 ? 'swamp' : 'angry';
|
||||
const name = `${Names.getCulture(culture)} ${ra(animals)}`;
|
||||
const legend = `A gang of ${locality} ${rw(types)}`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addPirates(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
|
||||
let searoutes = Array.from(cells.i.filter((i) => !occupied[i] && cells.h[i] < 20 && cells.road[i]));
|
||||
let quantity = getQuantity(searoutes, 40, 300, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(searoutes);
|
||||
const id = addMarker({cell, icon, type, dx: 51});
|
||||
const name = `Pirates`;
|
||||
const legend = `Pirate ships have been spotted in these waters`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addStatues(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
let statues = Array.from(cells.i.filter((i) => !occupied[i] && cells.h[i] >= 20 && cells.h[i] < 40));
|
||||
let quantity = getQuantity(statues, 80, 1200, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const variants = ['Statue', 'Obelisk', 'Monument', 'Column', 'Monolith', 'Pillar', 'Megalith', 'Stele', 'Runestone', 'Sculpture', 'Effigy', 'Idol'];
|
||||
const scripts = {
|
||||
cypriot: '𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿 ',
|
||||
geez: 'ሀለሐመሠረሰቀበተኀነአከወዐዘየደገጠጰጸፀፈፐ ',
|
||||
coptic: 'ⲲⲴⲶⲸⲺⲼⲾⳀⳁⳂⳃⳄⳆⳈⳊⳌⳎⳐⳒⳔⳖⳘⳚⳜⳞⳠⳢⳤ⳥⳧⳩⳪ⳫⳬⳭⳲ⳹⳾ ',
|
||||
tibetan: 'ༀ༁༂༃༄༅༆༇༈༉༊་༌༐༑༒༓༔༕༖༗༘༙༚༛༜༠༡༢༣༤༥༦༧༨༩༪༫༬༭༮༯༰༱༲༳༴༵༶༷༸༹༺༻༼༽༾༿',
|
||||
mongolian: '᠀᠐᠑᠒ᠠᠡᠦᠧᠨᠩᠪᠭᠮᠯᠰᠱᠲᠳᠵᠻᠼᠽᠾᠿᡀᡁᡆᡍᡎᡏᡐᡑᡒᡓᡔᡕᡖᡗᡙᡜᡝᡞᡟᡠᡡᡭᡮᡯᡰᡱᡲᡳᡴᢀᢁᢂᢋᢏᢐᢑᢒᢓᢛᢜᢞᢟᢠᢡᢢᢤᢥᢦ'
|
||||
};
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(statues);
|
||||
const id = addMarker({cell, icon, type});
|
||||
const culture = cells.culture[cell];
|
||||
|
||||
const variant = ra(variants);
|
||||
const name = `${Names.getCulture(culture)} ${variant}`;
|
||||
const script = scripts[ra(Object.keys(scripts))];
|
||||
const inscription = Array(rand(40, 100))
|
||||
.fill(null)
|
||||
.map(() => ra(script))
|
||||
.join('');
|
||||
const legend = `An ancient ${variant.toLowerCase()}. It has an inscription, but no one can translate it:
|
||||
<div style="font-size: 1.8em; line-break: anywhere;">${inscription}</div>`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addRuines(type, icon, multiplier) {
|
||||
const {cells} = pack;
|
||||
let ruins = Array.from(cells.i.filter((i) => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && cells.h[i] < 60));
|
||||
let quantity = getQuantity(ruins, 80, 1200, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
const types = ['City', 'Town', 'Settlement', 'Pyramid', 'Fort', 'Stronghold', 'Temple', 'Sacred site', 'Mausoleum', 'Outpost', 'Fortification', 'Fortress', 'Castle'];
|
||||
|
||||
while (quantity) {
|
||||
const [cell] = extractAnyElement(ruins);
|
||||
const id = addMarker({cell, icon, type});
|
||||
|
||||
const ruinType = ra(types);
|
||||
const name = `Ruined ${ruinType}`;
|
||||
const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. Untold riches may lie within.`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
function addPortals(type, icon, multiplier) {
|
||||
const {burgs} = pack;
|
||||
let portals = burgs
|
||||
.slice(1, Math.ceil(burgs.length / 10) + 1)
|
||||
.filter(({cell}) => !occupied[cell])
|
||||
.map((burg) => [burg.name, burg.cell]);
|
||||
let quantity = getQuantity(portals, 16, 8, multiplier);
|
||||
if (!quantity) return;
|
||||
|
||||
while (quantity) {
|
||||
const [portal] = extractAnyElement(portals);
|
||||
const [burgName, cell] = portal;
|
||||
const id = addMarker({cell, icon, type, px: 14});
|
||||
const name = `${burgName} Portal`;
|
||||
const legend = `An element of the magic portal system connecting major cities. Portals installed centuries ago, but still work fine`;
|
||||
notes.push({id, name, legend});
|
||||
quantity--;
|
||||
}
|
||||
}
|
||||
|
||||
return {generate, regenerate, getConfig, setConfig};
|
||||
})();
|
||||
|
|
@ -1,16 +1,15 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.Military = (function () {
|
||||
const generate = function () {
|
||||
TIME && console.time("generateMilitaryForces");
|
||||
const cells = pack.cells,
|
||||
p = cells.p,
|
||||
states = pack.states;
|
||||
const valid = states.filter(s => s.i && !s.removed); // valid states
|
||||
TIME && console.time('generateMilitaryForces');
|
||||
const {cells, states} = pack;
|
||||
const {p} = cells;
|
||||
const valid = states.filter((s) => s.i && !s.removed); // valid states
|
||||
if (!options.military) options.military = getDefaultOptions();
|
||||
|
||||
const expn = d3.sum(valid.map(s => s.expansionism)); // total expansion
|
||||
const area = d3.sum(valid.map(s => s.area)); // total area
|
||||
const expn = d3.sum(valid.map((s) => s.expansionism)); // total expansion
|
||||
const area = d3.sum(valid.map((s) => s.area)); // total area
|
||||
const rate = {x: 0, Ally: -0.2, Friendly: -0.1, Neutral: 0, Suspicion: 0.1, Enemy: 1, Unknown: 0, Rival: 0.5, Vassal: 0.5, Suzerain: -0.5};
|
||||
|
||||
const stateModifier = {
|
||||
|
|
@ -19,7 +18,6 @@ window.Military = (function () {
|
|||
mounted: {Nomadic: 2.3, Highland: 0.6, Lake: 0.7, Naval: 0.3, Hunting: 0.7, River: 0.8},
|
||||
machinery: {Nomadic: 0.8, Highland: 1.4, Lake: 1.1, Naval: 1.4, Hunting: 0.4, River: 1.1},
|
||||
naval: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.8, Hunting: 0.7, River: 1.2},
|
||||
// non-default generic:
|
||||
armored: {Nomadic: 1, Highland: 0.5, Lake: 1, Naval: 1, Hunting: 0.7, River: 1.1},
|
||||
aviation: {Nomadic: 0.5, Highland: 0.5, Lake: 1.2, Naval: 1.2, Hunting: 0.6, River: 1.2},
|
||||
magical: {Nomadic: 1, Highland: 2, Lake: 1, Naval: 1, Hunting: 1, River: 1}
|
||||
|
|
@ -37,114 +35,136 @@ window.Military = (function () {
|
|||
highland: {melee: 1.2, ranged: 2, mounted: 0.3, machinery: 3, naval: 1.0, armored: 0.8, aviation: 0.3, magical: 2}
|
||||
};
|
||||
|
||||
valid.forEach(s => {
|
||||
const temp = (s.temp = {}),
|
||||
d = s.diplomacy;
|
||||
const expansionRate = Math.min(Math.max(s.expansionism / expn / (s.area / area), 0.25), 4); // how much state expansionism is realized
|
||||
const diplomacyRate = d.some(d => d === "Enemy") ? 1 : d.some(d => d === "Rival") ? 0.8 : d.some(d => d === "Suspicion") ? 0.5 : 0.1; // peacefulness
|
||||
const neighborsRate = Math.min(
|
||||
Math.max(
|
||||
s.neighbors.map(n => (n ? pack.states[n].diplomacy[s.i] : "Suspicion")).reduce((s, r) => (s += rate[r]), 0.5),
|
||||
0.3
|
||||
),
|
||||
3
|
||||
); // neighbors rate
|
||||
s.alert = Math.min(Math.max(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1), 5); // war alert rate (army modifier)
|
||||
temp.platoons = [];
|
||||
valid.forEach((s) => {
|
||||
s.temp = {};
|
||||
const d = s.diplomacy;
|
||||
|
||||
const expansionRate = minmax(s.expansionism / expn / (s.area / area), 0.25, 4); // how much state expansionism is realized
|
||||
const diplomacyRate = d.some((d) => d === 'Enemy') ? 1 : d.some((d) => d === 'Rival') ? 0.8 : d.some((d) => d === 'Suspicion') ? 0.5 : 0.1; // peacefulness
|
||||
const neighborsRateRaw = s.neighbors.map((n) => (n ? pack.states[n].diplomacy[s.i] : 'Suspicion')).reduce((s, r) => (s += rate[r]), 0.5);
|
||||
const neighborsRate = minmax(neighborsRateRaw, 0.3, 3); // neighbors rate
|
||||
s.alert = minmax(rn(expansionRate * diplomacyRate * neighborsRate, 2), 0.1, 5); // alert rate (area modifier)
|
||||
s.temp.platoons = [];
|
||||
|
||||
// apply overall state modifiers for unit types based on state features
|
||||
for (const unit of options.military) {
|
||||
if (!stateModifier[unit.type]) continue;
|
||||
|
||||
let modifier = stateModifier[unit.type][s.type] || 1;
|
||||
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;
|
||||
if (unit.type === 'mounted' && s.formName.includes('Horde')) modifier *= 2;
|
||||
else if (unit.type === 'naval' && s.form === 'Republic') modifier *= 1.2;
|
||||
s.temp[unit.name] = modifier * s.alert;
|
||||
}
|
||||
});
|
||||
|
||||
const getType = cell => {
|
||||
if ([1, 2, 3, 4].includes(cells.biome[cell])) return "nomadic";
|
||||
if ([7, 8, 9, 12].includes(cells.biome[cell])) return "wetland";
|
||||
if (cells.h[cell] >= 70) return "highland";
|
||||
return "generic";
|
||||
const getType = (cell) => {
|
||||
if ([1, 2, 3, 4].includes(cells.biome[cell])) return 'nomadic';
|
||||
if ([7, 8, 9, 12].includes(cells.biome[cell])) return 'wetland';
|
||||
if (cells.h[cell] >= 70) return 'highland';
|
||||
return 'generic';
|
||||
};
|
||||
|
||||
function passUnitLimits(unit, biome, state, culture, religion) {
|
||||
if (unit.biomes && !unit.biomes.includes(biome)) return false;
|
||||
if (unit.states && !unit.states.includes(state)) return false;
|
||||
if (unit.cultures && !unit.cultures.includes(culture)) return false;
|
||||
if (unit.religions && !unit.religions.includes(religion)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (!cells.pop[i]) continue;
|
||||
const s = states[cells.state[i]]; // cell state
|
||||
if (!s.i || s.removed) continue;
|
||||
|
||||
let m = cells.pop[i] / 100; // basic rural army in percentages
|
||||
if (cells.culture[i] !== s.culture) m = s.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture
|
||||
if (cells.religion[i] !== cells.religion[s.center]) m = s.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion
|
||||
if (cells.f[i] !== cells.f[s.center]) m = s.type === "Naval" ? m / 1.2 : m / 1.8; // different landmass
|
||||
const biome = cells.biome[i];
|
||||
const state = cells.state[i];
|
||||
const culture = cells.culture[i];
|
||||
const religion = cells.religion[i];
|
||||
|
||||
const stateObj = states[state];
|
||||
if (!state || stateObj.removed) continue;
|
||||
|
||||
let modifier = cells.pop[i] / 100; // basic rural army in percentages
|
||||
if (culture !== stateObj.culture) modifier = stateObj.form === 'Union' ? modifier / 1.2 : modifier / 2; // non-dominant culture
|
||||
if (religion !== cells.religion[stateObj.center]) modifier = stateObj.form === 'Theocracy' ? modifier / 2.2 : modifier / 1.4; // non-dominant religion
|
||||
if (cells.f[i] !== cells.f[stateObj.center]) modifier = stateObj.type === 'Naval' ? modifier / 1.2 : modifier / 1.8; // different landmass
|
||||
const type = getType(i);
|
||||
|
||||
for (const u of options.military) {
|
||||
const perc = +u.rural;
|
||||
if (isNaN(perc) || perc <= 0 || !s.temp[u.name]) continue;
|
||||
for (const unit of options.military) {
|
||||
const perc = +unit.rural;
|
||||
if (isNaN(perc) || perc <= 0 || !stateObj.temp[unit.name]) continue;
|
||||
if (!passUnitLimits(unit, biome, state, culture, religion)) continue;
|
||||
|
||||
const mod = type === "generic" ? 1 : cellTypeModifier[type][u.type]; // cell specific modifier
|
||||
const army = m * perc * mod; // rural cell army
|
||||
const t = rn(army * s.temp[u.name] * populationRate); // total troops
|
||||
if (!t) continue;
|
||||
let x = p[i][0],
|
||||
y = p[i][1],
|
||||
n = 0;
|
||||
if (u.type === "naval") {
|
||||
const cellTypeMod = type === 'generic' ? 1 : cellTypeModifier[type][unit.type]; // cell specific modifier
|
||||
const army = modifier * perc * cellTypeMod; // rural cell army
|
||||
const total = rn(army * stateObj.temp[unit.name] * populationRate); // total troops
|
||||
if (!total) continue;
|
||||
|
||||
let [x, y] = p[i];
|
||||
let n = 0;
|
||||
|
||||
if (unit.type === 'naval') {
|
||||
let haven = cells.haven[i];
|
||||
(x = p[haven][0]), (y = p[haven][1]);
|
||||
[x, y] = p[haven];
|
||||
n = 1;
|
||||
} // place naval to sea
|
||||
s.temp.platoons.push({cell: i, a: t, t, x, y, u: u.name, n, s: u.separate, type: u.type});
|
||||
|
||||
stateObj.temp.platoons.push({cell: i, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type});
|
||||
}
|
||||
}
|
||||
|
||||
for (const b of pack.burgs) {
|
||||
if (!b.i || b.removed || !b.state || !b.population) continue;
|
||||
const s = states[b.state]; // burg state
|
||||
|
||||
const biome = cells.biome[b.cell];
|
||||
const state = b.state;
|
||||
const culture = b.culture;
|
||||
const religion = cells.religion[b.cell];
|
||||
|
||||
const stateObj = states[state];
|
||||
let m = (b.population * urbanization) / 100; // basic urban army in percentages
|
||||
if (b.capital) m *= 1.2; // capital has household troops
|
||||
if (b.culture !== s.culture) m = s.form === "Union" ? m / 1.2 : m / 2; // non-dominant culture
|
||||
if (cells.religion[b.cell] !== cells.religion[s.center]) m = s.form === "Theocracy" ? m / 2.2 : m / 1.4; // non-dominant religion
|
||||
if (cells.f[b.cell] !== cells.f[s.center]) m = s.type === "Naval" ? m / 1.2 : m / 1.8; // different landmass
|
||||
if (culture !== stateObj.culture) m = stateObj.form === 'Union' ? m / 1.2 : m / 2; // non-dominant culture
|
||||
if (religion !== cells.religion[stateObj.center]) m = stateObj.form === 'Theocracy' ? m / 2.2 : m / 1.4; // non-dominant religion
|
||||
if (cells.f[b.cell] !== cells.f[stateObj.center]) m = stateObj.type === 'Naval' ? m / 1.2 : m / 1.8; // different landmass
|
||||
const type = getType(b.cell);
|
||||
|
||||
for (const u of options.military) {
|
||||
if (u.type === "naval" && !b.port) continue; // only ports produce naval units
|
||||
const perc = +u.urban;
|
||||
if (isNaN(perc) || perc <= 0 || !s.temp[u.name]) continue;
|
||||
for (const unit of options.military) {
|
||||
if (unit.type === 'naval' && !b.port) continue; // only ports produce naval units
|
||||
const perc = +unit.urban;
|
||||
if (isNaN(perc) || perc <= 0 || !stateObj.temp[unit.name]) continue;
|
||||
if (!passUnitLimits(unit, biome, state, culture, religion)) continue;
|
||||
|
||||
const mod = type === "generic" ? 1 : burgTypeModifier[type][u.type]; // cell specific modifier
|
||||
const mod = type === 'generic' ? 1 : burgTypeModifier[type][unit.type]; // cell specific modifier
|
||||
const army = m * perc * mod; // urban cell army
|
||||
const t = rn(army * s.temp[u.name] * populationRate); // total troops
|
||||
if (!t) continue;
|
||||
let x = p[b.cell][0],
|
||||
y = p[b.cell][1],
|
||||
n = 0;
|
||||
if (u.type === "naval") {
|
||||
const total = rn(army * stateObj.temp[unit.name] * populationRate); // total troops
|
||||
if (!total) continue;
|
||||
|
||||
let [x, y] = p[b.cell];
|
||||
let n = 0;
|
||||
|
||||
if (unit.type === 'naval') {
|
||||
let haven = cells.haven[b.cell];
|
||||
(x = p[haven][0]), (y = p[haven][1]);
|
||||
[x, y] = p[haven];
|
||||
n = 1;
|
||||
} // place naval in sea cell
|
||||
s.temp.platoons.push({cell: b.cell, a: t, t, x, y, u: u.name, n, s: u.separate, type: u.type});
|
||||
} // place naval to sea
|
||||
|
||||
stateObj.temp.platoons.push({cell: b.cell, a: total, t: total, x, y, u: unit.name, n, s: unit.separate, type: unit.type});
|
||||
}
|
||||
}
|
||||
|
||||
void (function removeExistingRegiments() {
|
||||
armies.selectAll("g > g").each(function () {
|
||||
const index = notes.findIndex(n => n.id === this.id);
|
||||
armies.selectAll('g > g').each(function () {
|
||||
const index = notes.findIndex((n) => n.id === this.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
});
|
||||
armies.selectAll("g").remove();
|
||||
armies.selectAll('g').remove();
|
||||
})();
|
||||
|
||||
const expected = 3 * populationRate; // expected regiment size
|
||||
const mergeable = (n0, n1) => (!n0.s && !n1.s) || n0.type === n1.type; // check if regiments can be merged
|
||||
|
||||
// get regiments for each state
|
||||
valid.forEach(s => {
|
||||
valid.forEach((s) => {
|
||||
s.military = createRegiments(s.temp.platoons, s);
|
||||
delete s.temp; // do not store temp data
|
||||
drawRegiments(s.military, s.i);
|
||||
|
|
@ -155,10 +175,10 @@ window.Military = (function () {
|
|||
nodes.sort((a, b) => a.a - b.a); // form regiments in cells with most troops
|
||||
const tree = d3.quadtree(
|
||||
nodes,
|
||||
d => d.x,
|
||||
d => d.y
|
||||
(d) => d.x,
|
||||
(d) => d.y
|
||||
);
|
||||
nodes.forEach(n => {
|
||||
nodes.forEach((n) => {
|
||||
tree.remove(n);
|
||||
const overlap = tree.find(n.x, n.y, 20);
|
||||
if (overlap && overlap.t && mergeable(n, overlap)) {
|
||||
|
|
@ -180,24 +200,24 @@ window.Military = (function () {
|
|||
function merge(n0, n1) {
|
||||
if (!n1.childen) n1.childen = [n0];
|
||||
else n1.childen.push(n0);
|
||||
if (n0.childen) n0.childen.forEach(n => n1.childen.push(n));
|
||||
if (n0.childen) n0.childen.forEach((n) => n1.childen.push(n));
|
||||
n1.t += n0.t;
|
||||
n0.t = 0;
|
||||
}
|
||||
|
||||
// parse regiments data
|
||||
const regiments = nodes
|
||||
.filter(n => n.t)
|
||||
.filter((n) => n.t)
|
||||
.sort((a, b) => b.t - a.t)
|
||||
.map((r, i) => {
|
||||
const u = {};
|
||||
u[r.u] = r.a;
|
||||
(r.childen || []).forEach(n => (u[n.u] = u[n.u] ? (u[n.u] += n.a) : n.a));
|
||||
(r.childen || []).forEach((n) => (u[n.u] = u[n.u] ? (u[n.u] += n.a) : n.a));
|
||||
return {i, a: r.t, cell: r.cell, x: r.x, y: r.y, bx: r.x, by: r.y, u, n: r.n, name, state: s.i};
|
||||
});
|
||||
|
||||
// generate name for regiments
|
||||
regiments.forEach(r => {
|
||||
regiments.forEach((r) => {
|
||||
r.name = getName(r, regiments);
|
||||
r.icon = getEmblem(r);
|
||||
generateNote(r, s);
|
||||
|
|
@ -206,164 +226,175 @@ window.Military = (function () {
|
|||
return regiments;
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("generateMilitaryForces");
|
||||
TIME && console.timeEnd('generateMilitaryForces');
|
||||
};
|
||||
|
||||
const getDefaultOptions = function () {
|
||||
return [
|
||||
{icon: "⚔️", name: "infantry", rural: 0.25, urban: 0.2, crew: 1, power: 1, type: "melee", separate: 0},
|
||||
{icon: "🏹", name: "archers", rural: 0.12, urban: 0.2, crew: 1, power: 1, type: "ranged", separate: 0},
|
||||
{icon: "🐴", name: "cavalry", rural: 0.12, urban: 0.03, crew: 2, power: 2, type: "mounted", separate: 0},
|
||||
{icon: "💣", name: "artillery", rural: 0, urban: 0.03, crew: 8, power: 12, type: "machinery", separate: 0},
|
||||
{icon: "🌊", name: "fleet", rural: 0, urban: 0.015, crew: 100, power: 50, type: "naval", separate: 1}
|
||||
{icon: '⚔️', name: 'infantry', rural: 0.25, urban: 0.2, crew: 1, power: 1, type: 'melee', separate: 0},
|
||||
{icon: '🏹', name: 'archers', rural: 0.12, urban: 0.2, crew: 1, power: 1, type: 'ranged', separate: 0},
|
||||
{icon: '🐴', name: 'cavalry', rural: 0.12, urban: 0.03, crew: 2, power: 2, type: 'mounted', separate: 0},
|
||||
{icon: '💣', name: 'artillery', rural: 0, urban: 0.03, crew: 8, power: 12, type: 'machinery', separate: 0},
|
||||
{icon: '🌊', name: 'fleet', rural: 0, urban: 0.015, crew: 100, power: 50, type: 'naval', separate: 1}
|
||||
];
|
||||
};
|
||||
|
||||
const drawRegiments = function (regiments, s) {
|
||||
const size = +armies.attr("box-size");
|
||||
const w = d => (d.n ? size * 4 : size * 6);
|
||||
const size = +armies.attr('box-size');
|
||||
const w = (d) => (d.n ? size * 4 : size * 6);
|
||||
const h = size * 2;
|
||||
const x = d => rn(d.x - w(d) / 2, 2);
|
||||
const y = d => rn(d.y - size, 2);
|
||||
const x = (d) => rn(d.x - w(d) / 2, 2);
|
||||
const y = (d) => rn(d.y - size, 2);
|
||||
|
||||
const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999";
|
||||
const baseColor = pack.states[s].color[0] === '#' ? pack.states[s].color : '#999';
|
||||
const darkerColor = d3.color(baseColor).darker().hex();
|
||||
const army = armies
|
||||
.append("g")
|
||||
.attr("id", "army" + s)
|
||||
.attr("fill", baseColor);
|
||||
.append('g')
|
||||
.attr('id', 'army' + s)
|
||||
.attr('fill', baseColor);
|
||||
|
||||
const g = army
|
||||
.selectAll("g")
|
||||
.selectAll('g')
|
||||
.data(regiments)
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("id", d => "regiment" + s + "-" + d.i)
|
||||
.attr("data-name", d => d.name)
|
||||
.attr("data-state", s)
|
||||
.attr("data-id", d => d.i);
|
||||
g.append("rect")
|
||||
.attr("x", d => x(d))
|
||||
.attr("y", d => y(d))
|
||||
.attr("width", d => w(d))
|
||||
.attr("height", h);
|
||||
g.append("text")
|
||||
.attr("x", d => d.x)
|
||||
.attr("y", d => d.y)
|
||||
.text(d => getTotal(d));
|
||||
g.append("rect")
|
||||
.attr("fill", darkerColor)
|
||||
.attr("x", d => x(d) - h)
|
||||
.attr("y", d => y(d))
|
||||
.attr("width", h)
|
||||
.attr("height", h);
|
||||
g.append("text")
|
||||
.attr("class", "regimentIcon")
|
||||
.attr("x", d => x(d) - size)
|
||||
.attr("y", d => d.y)
|
||||
.text(d => d.icon);
|
||||
.append('g')
|
||||
.attr('id', (d) => 'regiment' + s + '-' + d.i)
|
||||
.attr('data-name', (d) => d.name)
|
||||
.attr('data-state', s)
|
||||
.attr('data-id', (d) => d.i);
|
||||
g.append('rect')
|
||||
.attr('x', (d) => x(d))
|
||||
.attr('y', (d) => y(d))
|
||||
.attr('width', (d) => w(d))
|
||||
.attr('height', h);
|
||||
g.append('text')
|
||||
.attr('x', (d) => d.x)
|
||||
.attr('y', (d) => d.y)
|
||||
.text((d) => getTotal(d));
|
||||
g.append('rect')
|
||||
.attr('fill', darkerColor)
|
||||
.attr('x', (d) => x(d) - h)
|
||||
.attr('y', (d) => y(d))
|
||||
.attr('width', h)
|
||||
.attr('height', h);
|
||||
g.append('text')
|
||||
.attr('class', 'regimentIcon')
|
||||
.attr('x', (d) => x(d) - size)
|
||||
.attr('y', (d) => d.y)
|
||||
.text((d) => d.icon);
|
||||
};
|
||||
|
||||
const drawRegiment = function (reg, s) {
|
||||
const size = +armies.attr("box-size");
|
||||
const size = +armies.attr('box-size');
|
||||
const w = reg.n ? size * 4 : size * 6;
|
||||
const h = size * 2;
|
||||
const x1 = rn(reg.x - w / 2, 2);
|
||||
const y1 = rn(reg.y - size, 2);
|
||||
|
||||
let army = armies.select("g#army" + s);
|
||||
let army = armies.select('g#army' + s);
|
||||
if (!army.size()) {
|
||||
const baseColor = pack.states[s].color[0] === "#" ? pack.states[s].color : "#999";
|
||||
const baseColor = pack.states[s].color[0] === '#' ? pack.states[s].color : '#999';
|
||||
army = armies
|
||||
.append("g")
|
||||
.attr("id", "army" + s)
|
||||
.attr("fill", baseColor);
|
||||
.append('g')
|
||||
.attr('id', 'army' + s)
|
||||
.attr('fill', baseColor);
|
||||
}
|
||||
const darkerColor = d3.color(army.attr("fill")).darker().hex();
|
||||
const darkerColor = d3.color(army.attr('fill')).darker().hex();
|
||||
|
||||
const g = army
|
||||
.append("g")
|
||||
.attr("id", "regiment" + s + "-" + reg.i)
|
||||
.attr("data-name", reg.name)
|
||||
.attr("data-state", s)
|
||||
.attr("data-id", reg.i);
|
||||
g.append("rect").attr("x", x1).attr("y", y1).attr("width", w).attr("height", h);
|
||||
g.append("text").attr("x", reg.x).attr("y", reg.y).text(getTotal(reg));
|
||||
g.append("rect")
|
||||
.attr("fill", darkerColor)
|
||||
.attr("x", x1 - h)
|
||||
.attr("y", y1)
|
||||
.attr("width", h)
|
||||
.attr("height", h);
|
||||
g.append("text")
|
||||
.attr("class", "regimentIcon")
|
||||
.attr("x", x1 - size)
|
||||
.attr("y", reg.y)
|
||||
.append('g')
|
||||
.attr('id', 'regiment' + s + '-' + reg.i)
|
||||
.attr('data-name', reg.name)
|
||||
.attr('data-state', s)
|
||||
.attr('data-id', reg.i);
|
||||
g.append('rect').attr('x', x1).attr('y', y1).attr('width', w).attr('height', h);
|
||||
g.append('text').attr('x', reg.x).attr('y', reg.y).text(getTotal(reg));
|
||||
g.append('rect')
|
||||
.attr('fill', darkerColor)
|
||||
.attr('x', x1 - h)
|
||||
.attr('y', y1)
|
||||
.attr('width', h)
|
||||
.attr('height', h);
|
||||
g.append('text')
|
||||
.attr('class', 'regimentIcon')
|
||||
.attr('x', x1 - size)
|
||||
.attr('y', reg.y)
|
||||
.text(reg.icon);
|
||||
};
|
||||
|
||||
// move one regiment to another
|
||||
const moveRegiment = function (reg, x, y) {
|
||||
const el = armies.select("g#army" + reg.state).select("g#regiment" + reg.state + "-" + reg.i);
|
||||
const el = armies.select('g#army' + reg.state).select('g#regiment' + reg.state + '-' + reg.i);
|
||||
if (!el.size()) return;
|
||||
|
||||
const duration = Math.hypot(reg.x - x, reg.y - y) * 8;
|
||||
reg.x = x;
|
||||
reg.y = y;
|
||||
const size = +armies.attr("box-size");
|
||||
const size = +armies.attr('box-size');
|
||||
const w = reg.n ? size * 4 : size * 6;
|
||||
const h = size * 2;
|
||||
const x1 = x => rn(x - w / 2, 2);
|
||||
const y1 = y => rn(y - size, 2);
|
||||
const x1 = (x) => rn(x - w / 2, 2);
|
||||
const y1 = (y) => rn(y - size, 2);
|
||||
|
||||
const move = d3.transition().duration(duration).ease(d3.easeSinInOut);
|
||||
el.select("rect").transition(move).attr("x", x1(x)).attr("y", y1(y));
|
||||
el.select("text").transition(move).attr("x", x).attr("y", y);
|
||||
el.selectAll("rect:nth-of-type(2)")
|
||||
el.select('rect').transition(move).attr('x', x1(x)).attr('y', y1(y));
|
||||
el.select('text').transition(move).attr('x', x).attr('y', y);
|
||||
el.selectAll('rect:nth-of-type(2)')
|
||||
.transition(move)
|
||||
.attr("x", x1(x) - h)
|
||||
.attr("y", y1(y));
|
||||
el.select(".regimentIcon")
|
||||
.attr('x', x1(x) - h)
|
||||
.attr('y', y1(y));
|
||||
el.select('.regimentIcon')
|
||||
.transition(move)
|
||||
.attr("x", x1(x) - size)
|
||||
.attr("y", y);
|
||||
.attr('x', x1(x) - size)
|
||||
.attr('y', y);
|
||||
};
|
||||
|
||||
// utilize si function to make regiment total text fit regiment box
|
||||
const getTotal = reg => (reg.a > (reg.n ? 999 : 99999) ? si(reg.a) : reg.a);
|
||||
const getTotal = (reg) => (reg.a > (reg.n ? 999 : 99999) ? si(reg.a) : reg.a);
|
||||
|
||||
const getName = function (r, regiments) {
|
||||
const cells = pack.cells;
|
||||
const proper = r.n ? null : cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].name : cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name : null;
|
||||
const number = nth(regiments.filter(reg => reg.n === r.n && reg.i < r.i).length + 1);
|
||||
const form = r.n ? "Fleet" : "Regiment";
|
||||
const proper = r.n
|
||||
? null
|
||||
: cells.province[r.cell] && pack.provinces[cells.province[r.cell]]
|
||||
? pack.provinces[cells.province[r.cell]].name
|
||||
: cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]]
|
||||
? pack.burgs[cells.burg[r.cell]].name
|
||||
: null;
|
||||
const number = nth(regiments.filter((reg) => reg.n === r.n && reg.i < r.i).length + 1);
|
||||
const form = r.n ? 'Fleet' : 'Regiment';
|
||||
return `${number}${proper ? ` (${proper}) ` : ` `}${form}`;
|
||||
};
|
||||
|
||||
// get default regiment emblem
|
||||
const getEmblem = function (r) {
|
||||
if (!r.n && !Object.values(r.u).length) return "🔰"; // "Newbie" regiment without troops
|
||||
if (!r.n && pack.states[r.state].form === "Monarchy" && pack.cells.burg[r.cell] && pack.burgs[pack.cells.burg[r.cell]].capital) return "👑"; // "Royal" regiment based in capital
|
||||
if (!r.n && !Object.values(r.u).length) return '🔰'; // "Newbie" regiment without troops
|
||||
if (!r.n && pack.states[r.state].form === 'Monarchy' && pack.cells.burg[r.cell] && pack.burgs[pack.cells.burg[r.cell]].capital) return '👑'; // "Royal" regiment based in capital
|
||||
const mainUnit = Object.entries(r.u).sort((a, b) => b[1] - a[1])[0][0]; // unit with more troops in regiment
|
||||
const unit = options.military.find(u => u.name === mainUnit);
|
||||
const unit = options.military.find((u) => u.name === mainUnit);
|
||||
return unit.icon;
|
||||
};
|
||||
|
||||
const generateNote = function (r, s) {
|
||||
const cells = pack.cells;
|
||||
const base = cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]] ? pack.burgs[cells.burg[r.cell]].name : cells.province[r.cell] && pack.provinces[cells.province[r.cell]] ? pack.provinces[cells.province[r.cell]].fullName : null;
|
||||
const station = base ? `${r.name} is ${r.n ? "based" : "stationed"} in ${base}. ` : "";
|
||||
const base =
|
||||
cells.burg[r.cell] && pack.burgs[cells.burg[r.cell]]
|
||||
? pack.burgs[cells.burg[r.cell]].name
|
||||
: cells.province[r.cell] && pack.provinces[cells.province[r.cell]]
|
||||
? pack.provinces[cells.province[r.cell]].fullName
|
||||
: null;
|
||||
const station = base ? `${r.name} is ${r.n ? 'based' : 'stationed'} in ${base}. ` : '';
|
||||
|
||||
const composition = r.a
|
||||
? Object.keys(r.u)
|
||||
.map(t => `— ${t}: ${r.u[t]}`)
|
||||
.join("\r\n")
|
||||
.map((t) => `— ${t}: ${r.u[t]}`)
|
||||
.join('\r\n')
|
||||
: null;
|
||||
const troops = composition ? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.` : "";
|
||||
const troops = composition ? `\r\n\r\nRegiment composition in ${options.year} ${options.eraShort}:\r\n${composition}.` : '';
|
||||
|
||||
const campaign = s.campaigns ? ra(s.campaigns) : null;
|
||||
const year = campaign ? rand(campaign.start, campaign.end) : gauss(options.year - 100, 150, 1, options.year - 6);
|
||||
const conflict = campaign ? ` during the ${campaign.name}` : "";
|
||||
const conflict = campaign ? ` during the ${campaign.name}` : '';
|
||||
const legend = `Regiment was formed in ${year} ${options.era}${conflict}. ${station}${troops}`;
|
||||
notes.push({id: `regiment${s.i}-${r.i}`, name: `${r.icon} ${r.name}`, legend});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.Names = (function () {
|
||||
let chains = [];
|
||||
|
|
@ -6,34 +6,34 @@ window.Names = (function () {
|
|||
// calculate Markov chain for a namesbase
|
||||
const calculateChain = function (string) {
|
||||
const chain = [];
|
||||
const array = string.split(",");
|
||||
const array = string.split(',');
|
||||
|
||||
for (const n of array) {
|
||||
let name = n.trim().toLowerCase();
|
||||
const basic = !/[^\u0000-\u007f]/.test(name); // basic chars and English rules can be applied
|
||||
|
||||
// split word into pseudo-syllables
|
||||
for (let i = -1, syllable = ""; i < name.length; i += syllable.length || 1, syllable = "") {
|
||||
let prev = name[i] || ""; // pre-onset letter
|
||||
for (let i = -1, syllable = ''; i < name.length; i += syllable.length || 1, syllable = '') {
|
||||
let prev = name[i] || ''; // pre-onset letter
|
||||
let v = 0; // 0 if no vowels in syllable
|
||||
|
||||
for (let c = i + 1; name[c] && syllable.length < 5; c++) {
|
||||
const that = name[c],
|
||||
next = name[c + 1]; // next char
|
||||
syllable += that;
|
||||
if (syllable === " " || syllable === "-") break; // syllable starts with space or hyphen
|
||||
if (!next || next === " " || next === "-") break; // no need to check
|
||||
if (syllable === ' ' || syllable === '-') break; // syllable starts with space or hyphen
|
||||
if (!next || next === ' ' || next === '-') break; // no need to check
|
||||
|
||||
if (vowel(that)) v = 1; // check if letter is vowel
|
||||
|
||||
// do not split some diphthongs
|
||||
if (that === "y" && next === "e") continue; // 'ye'
|
||||
if (that === 'y' && next === 'e') continue; // 'ye'
|
||||
if (basic) {
|
||||
// English-like
|
||||
if (that === "o" && next === "o") continue; // 'oo'
|
||||
if (that === "e" && next === "e") continue; // 'ee'
|
||||
if (that === "a" && next === "e") continue; // 'ae'
|
||||
if (that === "c" && next === "h") continue; // 'ch'
|
||||
if (that === 'o' && next === 'o') continue; // 'oo'
|
||||
if (that === 'e' && next === 'e') continue; // 'ee'
|
||||
if (that === 'a' && next === 'e') continue; // 'ae'
|
||||
if (that === 'c' && next === 'h') continue; // 'ch'
|
||||
}
|
||||
|
||||
if (vowel(that) === next) break; // two same vowels in a row
|
||||
|
|
@ -49,7 +49,7 @@ window.Names = (function () {
|
|||
};
|
||||
|
||||
// update chain for specific base
|
||||
const updateChain = i => (chains[i] = nameBases[i] || nameBases[i].b ? calculateChain(nameBases[i].b) : null);
|
||||
const updateChain = (i) => (chains[i] = nameBases[i] || nameBases[i].b ? calculateChain(nameBases[i].b) : null);
|
||||
|
||||
// update chains for all used bases
|
||||
const clearChains = () => (chains = []);
|
||||
|
|
@ -57,39 +57,39 @@ window.Names = (function () {
|
|||
// generate name using Markov's chain
|
||||
const getBase = function (base, min, max, dupl) {
|
||||
if (base === undefined) {
|
||||
ERROR && console.error("Please define a base");
|
||||
ERROR && console.error('Please define a base');
|
||||
return;
|
||||
}
|
||||
if (!chains[base]) updateChain(base);
|
||||
|
||||
const data = chains[base];
|
||||
if (!data || data[""] === undefined) {
|
||||
tip("Namesbase " + base + " is incorrect. Please check in namesbase editor", false, "error");
|
||||
ERROR && console.error("Namebase " + base + " is incorrect!");
|
||||
return "ERROR";
|
||||
if (!data || data[''] === undefined) {
|
||||
tip('Namesbase ' + base + ' is incorrect. Please check in namesbase editor', false, 'error');
|
||||
ERROR && console.error('Namebase ' + base + ' is incorrect!');
|
||||
return 'ERROR';
|
||||
}
|
||||
|
||||
if (!min) min = nameBases[base].min;
|
||||
if (!max) max = nameBases[base].max;
|
||||
if (dupl !== "") dupl = nameBases[base].d;
|
||||
if (dupl !== '') dupl = nameBases[base].d;
|
||||
|
||||
let v = data[""],
|
||||
let v = data[''],
|
||||
cur = ra(v),
|
||||
w = "";
|
||||
w = '';
|
||||
for (let i = 0; i < 20; i++) {
|
||||
if (cur === "") {
|
||||
if (cur === '') {
|
||||
// end of word
|
||||
if (w.length < min) {
|
||||
cur = "";
|
||||
w = "";
|
||||
v = data[""];
|
||||
cur = '';
|
||||
w = '';
|
||||
v = data[''];
|
||||
} else break;
|
||||
} else {
|
||||
if (w.length + cur.length > max) {
|
||||
// word too long
|
||||
if (w.length < min) w += cur;
|
||||
break;
|
||||
} else v = data[last(cur)] || data[""];
|
||||
} else v = data[last(cur)] || data[''];
|
||||
}
|
||||
|
||||
w += cur;
|
||||
|
|
@ -98,31 +98,29 @@ window.Names = (function () {
|
|||
|
||||
// parse word to get a final name
|
||||
const l = last(w); // last letter
|
||||
if (l === "'" || l === " " || l === "-") w = w.slice(0, -1); // not allow some characters at the end
|
||||
const basic = !/[^\u0000-\u007f]/.test(w); // true if word has only basic characters
|
||||
if (l === "'" || l === ' ' || l === '-') w = w.slice(0, -1); // not allow some characters at the end
|
||||
|
||||
let name = [...w].reduce(function (r, c, i, d) {
|
||||
if (c === d[i + 1] && !dupl.includes(c)) return r; // duplication is not allowed
|
||||
if (!r.length) return c.toUpperCase();
|
||||
if (r.slice(-1) === "-" && c === " ") return r; // remove space after hyphen
|
||||
if (r.slice(-1) === " ") return r + c.toUpperCase(); // capitalize letter after space
|
||||
if (r.slice(-1) === "-") return r + c.toUpperCase(); // capitalize letter after hyphen
|
||||
if (c === "a" && d[i + 1] === "e") return r; // "ae" => "e"
|
||||
if (basic && i + 1 < d.length && !vowel(c) && !vowel(d[i - 1]) && !vowel(d[i + 1])) return r; // remove consonant between 2 consonants
|
||||
if (r.slice(-1) === '-' && c === ' ') return r; // remove space after hyphen
|
||||
if (r.slice(-1) === ' ') return r + c.toUpperCase(); // capitalize letter after space
|
||||
if (r.slice(-1) === '-') return r + c.toUpperCase(); // capitalize letter after hyphen
|
||||
if (c === 'a' && d[i + 1] === 'e') return r; // "ae" => "e"
|
||||
if (i + 2 < d.length && c === d[i + 1] && c === d[i + 2]) return r; // remove three same letters in a row
|
||||
return r + c;
|
||||
}, "");
|
||||
}, '');
|
||||
|
||||
// join the word if any part has only 1 letter
|
||||
if (name.split(" ").some(part => part.length < 2))
|
||||
if (name.split(' ').some((part) => part.length < 2))
|
||||
name = name
|
||||
.split(" ")
|
||||
.split(' ')
|
||||
.map((p, i) => (i ? p.toLowerCase() : p))
|
||||
.join("");
|
||||
.join('');
|
||||
|
||||
if (name.length < 2) {
|
||||
ERROR && console.error("Name is too short! Random name will be selected");
|
||||
name = ra(nameBases[base].b.split(","));
|
||||
ERROR && console.error('Name is too short! Random name will be selected');
|
||||
name = ra(nameBases[base].b.split(','));
|
||||
}
|
||||
|
||||
return name;
|
||||
|
|
@ -130,84 +128,98 @@ window.Names = (function () {
|
|||
|
||||
// generate name for culture
|
||||
const getCulture = function (culture, min, max, dupl) {
|
||||
if (culture === undefined) {
|
||||
ERROR && console.error("Please define a culture");
|
||||
return;
|
||||
}
|
||||
if (culture === undefined) return ERROR && console.error('Please define a culture');
|
||||
const base = pack.cultures[culture].base;
|
||||
return getBase(base, min, max, dupl);
|
||||
};
|
||||
|
||||
// generate short name for culture
|
||||
const getCultureShort = function (culture) {
|
||||
if (culture === undefined) {
|
||||
ERROR && console.error("Please define a culture");
|
||||
return;
|
||||
}
|
||||
if (culture === undefined) return ERROR && console.error('Please define a culture');
|
||||
return getBaseShort(pack.cultures[culture].base);
|
||||
};
|
||||
|
||||
// generate short name for base
|
||||
const getBaseShort = function (base) {
|
||||
if (nameBases[base] === undefined) {
|
||||
tip(`Namebase ${base} does not exist. Please upload custom namebases of change the base in Cultures Editor`, false, "error");
|
||||
tip(`Namebase ${base} does not exist. Please upload custom namebases of change the base in Cultures Editor`, false, 'error');
|
||||
base = 1;
|
||||
}
|
||||
const min = nameBases[base].min - 1;
|
||||
const max = Math.max(nameBases[base].max - 2, min);
|
||||
return getBase(base, min, max, "", 0);
|
||||
return getBase(base, min, max, '', 0);
|
||||
};
|
||||
|
||||
// generate state name based on capital or random name and culture-specific suffix
|
||||
// prettier-ignore
|
||||
const getState = function(name, culture, base) {
|
||||
if (name === undefined) {ERROR && console.error("Please define a base name"); return;}
|
||||
if (culture === undefined && base === undefined) {ERROR && console.error("Please define a culture"); return;}
|
||||
const getState = function (name, culture, base) {
|
||||
if (name === undefined) return ERROR && console.error('Please define a base name');
|
||||
if (culture === undefined && base === undefined) return ERROR && console.error('Please define a culture');
|
||||
if (base === undefined) base = pack.cultures[culture].base;
|
||||
|
||||
// exclude endings inappropriate for states name
|
||||
if (name.includes(" ")) name = capitalize(name.replace(/ /g, "").toLowerCase()); // don't allow multiword state names
|
||||
if (name.length > 6 && name.slice(-4) === "berg") name = name.slice(0,-4); // remove -berg for any
|
||||
if (name.length > 5 && name.slice(-3) === "ton") name = name.slice(0,-3); // remove -ton for any
|
||||
if (name.includes(' ')) name = capitalize(name.replace(/ /g, '').toLowerCase()); // don't allow multiword state names
|
||||
if (name.length > 6 && name.slice(-4) === 'berg') name = name.slice(0, -4); // remove -berg for any
|
||||
if (name.length > 5 && name.slice(-3) === 'ton') name = name.slice(0, -3); // remove -ton for any
|
||||
|
||||
if (base === 5 && ["sk", "ev", "ov"].includes(name.slice(-2))) name = name.slice(0,-2); // remove -sk/-ev/-ov for Ruthenian
|
||||
else if (base === 12) return vowel(name.slice(-1)) ? name : name + "u"; // Japanese ends on any vowel or -u
|
||||
else if (base === 18 && P(.4)) name = vowel(name.slice(0,1).toLowerCase()) ? "Al" + name.toLowerCase() : "Al " + name; // Arabic starts with -Al
|
||||
if (base === 5 && ['sk', 'ev', 'ov'].includes(name.slice(-2))) name = name.slice(0, -2);
|
||||
// remove -sk/-ev/-ov for Ruthenian
|
||||
else if (base === 12) return vowel(name.slice(-1)) ? name : name + 'u';
|
||||
// Japanese ends on any vowel or -u
|
||||
else if (base === 18 && P(0.4)) name = vowel(name.slice(0, 1).toLowerCase()) ? 'Al' + name.toLowerCase() : 'Al ' + name; // Arabic starts with -Al
|
||||
|
||||
// no suffix for fantasy bases
|
||||
if (base > 32 && base < 42) return name;
|
||||
|
||||
// define if suffix should be used
|
||||
if (name.length > 3 && vowel(name.slice(-1))) {
|
||||
if (vowel(name.slice(-2,-1)) && P(.85)) name = name.slice(0,-2); // 85% for vv
|
||||
else if (P(.7)) name = name.slice(0,-1); // ~60% for cv
|
||||
if (vowel(name.slice(-2, -1)) && P(0.85)) name = name.slice(0, -2);
|
||||
// 85% for vv
|
||||
else if (P(0.7)) name = name.slice(0, -1);
|
||||
// ~60% for cv
|
||||
else return name;
|
||||
} else if (P(.4)) return name; // 60% for cc and vc
|
||||
} else if (P(0.4)) return name; // 60% for cc and vc
|
||||
|
||||
// define suffix
|
||||
let suffix = "ia"; // standard suffix
|
||||
let suffix = 'ia'; // standard suffix
|
||||
|
||||
const rnd = Math.random(), l = name.length;
|
||||
if (base === 3 && rnd < .03 && l < 7) suffix = "terra"; // Italian
|
||||
else if (base === 4 && rnd < .03 && l < 7) suffix = "terra"; // Spanish
|
||||
else if (base === 13 && rnd < .03 && l < 7) suffix = "terra"; // Portuguese
|
||||
else if (base === 2 && rnd < .03 && l < 7) suffix = "terre"; // French
|
||||
else if (base === 0 && rnd < .5 && l < 7) suffix = "land"; // German
|
||||
else if (base === 1 && rnd < .4 && l < 7 ) suffix = "land"; // English
|
||||
else if (base === 6 && rnd < .3 && l < 7) suffix = "land"; // Nordic
|
||||
else if (base === 32 && rnd < .1 && l < 7) suffix = "land"; // generic Human
|
||||
else if (base === 7 && rnd < .1) suffix = "eia"; // Greek
|
||||
else if (base === 9 && rnd < .35) suffix = "maa"; // Finnic
|
||||
else if (base === 15 && rnd < .4 && l < 6) suffix = "orszag"; // Hungarian
|
||||
else if (base === 16) suffix = rnd < .6 ? "stan" : "ya"; // Turkish
|
||||
else if (base === 10) suffix = "guk"; // Korean
|
||||
else if (base === 11) suffix = " Guo"; // Chinese
|
||||
else if (base === 14) suffix = rnd < .5 && l < 6 ? "tlan" : "co"; // Nahuatl
|
||||
else if (base === 17 && rnd < .8) suffix = "a"; // Berber
|
||||
else if (base === 18 && rnd < .8) suffix = "a"; // Arabic
|
||||
const rnd = Math.random(),
|
||||
l = name.length;
|
||||
if (base === 3 && rnd < 0.03 && l < 7) suffix = 'terra';
|
||||
// Italian
|
||||
else if (base === 4 && rnd < 0.03 && l < 7) suffix = 'terra';
|
||||
// Spanish
|
||||
else if (base === 13 && rnd < 0.03 && l < 7) suffix = 'terra';
|
||||
// Portuguese
|
||||
else if (base === 2 && rnd < 0.03 && l < 7) suffix = 'terre';
|
||||
// French
|
||||
else if (base === 0 && rnd < 0.5 && l < 7) suffix = 'land';
|
||||
// German
|
||||
else if (base === 1 && rnd < 0.4 && l < 7) suffix = 'land';
|
||||
// English
|
||||
else if (base === 6 && rnd < 0.3 && l < 7) suffix = 'land';
|
||||
// Nordic
|
||||
else if (base === 32 && rnd < 0.1 && l < 7) suffix = 'land';
|
||||
// generic Human
|
||||
else if (base === 7 && rnd < 0.1) suffix = 'eia';
|
||||
// Greek
|
||||
else if (base === 9 && rnd < 0.35) suffix = 'maa';
|
||||
// Finnic
|
||||
else if (base === 15 && rnd < 0.4 && l < 6) suffix = 'orszag';
|
||||
// Hungarian
|
||||
else if (base === 16) suffix = rnd < 0.6 ? 'stan' : 'ya';
|
||||
// Turkish
|
||||
else if (base === 10) suffix = 'guk';
|
||||
// Korean
|
||||
else if (base === 11) suffix = ' Guo';
|
||||
// Chinese
|
||||
else if (base === 14) suffix = rnd < 0.5 && l < 6 ? 'tlan' : 'co';
|
||||
// Nahuatl
|
||||
else if (base === 17 && rnd < 0.8) suffix = 'a';
|
||||
// Berber
|
||||
else if (base === 18 && rnd < 0.8) suffix = 'a'; // Arabic
|
||||
|
||||
return validateSuffix(name, suffix);
|
||||
}
|
||||
};
|
||||
|
||||
function validateSuffix(name, suffix) {
|
||||
if (name.slice(-1 * suffix.length) === suffix) return name; // no suffix if name already ends with it
|
||||
|
|
@ -220,24 +232,24 @@ window.Names = (function () {
|
|||
|
||||
// generato name for the map
|
||||
const getMapName = function (force) {
|
||||
if (!force && locked("mapName")) return;
|
||||
if (force && locked("mapName")) unlock("mapName");
|
||||
if (!force && locked('mapName')) return;
|
||||
if (force && locked('mapName')) unlock('mapName');
|
||||
const base = P(0.7) ? 2 : P(0.5) ? rand(0, 6) : rand(0, 31);
|
||||
if (!nameBases[base]) {
|
||||
tip("Namebase is not found", false, "error");
|
||||
return "";
|
||||
tip('Namebase is not found', false, 'error');
|
||||
return '';
|
||||
}
|
||||
const min = nameBases[base].min - 1;
|
||||
const max = Math.max(nameBases[base].max - 3, min);
|
||||
const baseName = getBase(base, min, max, "", 0);
|
||||
const baseName = getBase(base, min, max, '', 0);
|
||||
const name = P(0.7) ? addSuffix(baseName) : baseName;
|
||||
mapName.value = name;
|
||||
};
|
||||
|
||||
function addSuffix(name) {
|
||||
const suffix = P(0.8) ? "ia" : "land";
|
||||
if (suffix === "ia" && name.length > 6) name = name.slice(0, -(name.length - 3));
|
||||
else if (suffix === "land" && name.length > 6) name = name.slice(0, -(name.length - 5));
|
||||
const suffix = P(0.8) ? 'ia' : 'land';
|
||||
if (suffix === 'ia' && name.length > 6) name = name.slice(0, -(name.length - 3));
|
||||
else if (suffix === 'land' && name.length > 6) name = name.slice(0, -(name.length - 5));
|
||||
return validateSuffix(name, suffix);
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +262,7 @@ window.Names = (function () {
|
|||
{name: "English", i: 1, min: 6, max: 11, d: "", m: .1, b: "Abingdon,Albrighton,Alcester,Almondbury,Altrincham,Amersham,Andover,Appleby,Ashboume,Atherstone,Aveton,Axbridge,Aylesbury,Baldock,Bamburgh,Barton,Basingstoke,Berden,Bere,Berkeley,Berwick,Betley,Bideford,Bingley,Birmingham,Blandford,Blechingley,Bodmin,Bolton,Bootham,Boroughbridge,Boscastle,Bossinney,Bramber,Brampton,Brasted,Bretford,Bridgetown,Bridlington,Bromyard,Bruton,Buckingham,Bungay,Burton,Calne,Cambridge,Canterbury,Carlisle,Castleton,Caus,Charmouth,Chawleigh,Chichester,Chillington,Chinnor,Chipping,Chisbury,Cleobury,Clifford,Clifton,Clitheroe,Cockermouth,Coleshill,Combe,Congleton,Crafthole,Crediton,Cuddenbeck,Dalton,Darlington,Dodbrooke,Drax,Dudley,Dunstable,Dunster,Dunwich,Durham,Dymock,Exeter,Exning,Faringdon,Felton,Fenny,Finedon,Flookburgh,Fowey,Frampton,Gateshead,Gatton,Godmanchester,Grampound,Grantham,Guildford,Halesowen,Halton,Harbottle,Harlow,Hatfield,Hatherleigh,Haydon,Helston,Henley,Hertford,Heytesbury,Hinckley,Hitchin,Holme,Hornby,Horsham,Kendal,Kenilworth,Kilkhampton,Kineton,Kington,Kinver,Kirby,Knaresborough,Knutsford,Launceston,Leighton,Lewes,Linton,Louth,Luton,Lyme,Lympstone,Macclesfield,Madeley,Malborough,Maldon,Manchester,Manningtree,Marazion,Marlborough,Marshfield,Mere,Merryfield,Middlewich,Midhurst,Milborne,Mitford,Modbury,Montacute,Mousehole,Newbiggin,Newborough,Newbury,Newenden,Newent,Norham,Northleach,Noss,Oakham,Olney,Orford,Ormskirk,Oswestry,Padstow,Paignton,Penkneth,Penrith,Penzance,Pershore,Petersfield,Pevensey,Pickering,Pilton,Pontefract,Portsmouth,Preston,Quatford,Reading,Redcliff,Retford,Rockingham,Romney,Rothbury,Rothwell,Salisbury,Saltash,Seaford,Seasalter,Sherston,Shifnal,Shoreham,Sidmouth,Skipsea,Skipton,Solihull,Somerton,Southam,Southwark,Standon,Stansted,Stapleton,Stottesdon,Sudbury,Swavesey,Tamerton,Tarporley,Tetbury,Thatcham,Thaxted,Thetford,Thornbury,Tintagel,Tiverton,Torksey,Totnes,Towcester,Tregoney,Trematon,Tutbury,Uxbridge,Wallingford,Wareham,Warenmouth,Wargrave,Warton,Watchet,Watford,Wendover,Westbury,Westcheap,Weymouth,Whitford,Wickwar,Wigan,Wigmore,Winchelsea,Winkleigh,Wiscombe,Witham,Witheridge,Wiveliscombe,Woodbury,Yeovil"},
|
||||
{name: "French", i: 2, min: 5, max: 13, d: "nlrs", m: .1, b: "Adon,Aillant,Amilly,Andonville,Ardon,Artenay,Ascheres,Ascoux,Attray,Aubin,Audeville,Aulnay,Autruy,Auvilliers,Auxy,Aveyron,Baccon,Bardon,Barville,Batilly,Baule,Bazoches,Beauchamps,Beaugency,Beaulieu,Beaune,Bellegarde,Boesses,Boigny,Boiscommun,Boismorand,Boisseaux,Bondaroy,Bonnee,Bonny,Bordes,Bou,Bougy,Bouilly,Boulay,Bouzonville,Bouzy,Boynes,Bray,Breteau,Briare,Briarres,Bricy,Bromeilles,Bucy,Cepoy,Cercottes,Cerdon,Cernoy,Cesarville,Chailly,Chaingy,Chalette,Chambon,Champoulet,Chanteau,Chantecoq,Chapell,Charme,Charmont,Charsonville,Chateau,Chateauneuf,Chatel,Chatenoy,Chatillon,Chaussy,Checy,Chevannes,Chevillon,Chevilly,Chevry,Chilleurs,Choux,Chuelles,Clery,Coinces,Coligny,Combleux,Combreux,Conflans,Corbeilles,Corquilleroy,Cortrat,Coudroy,Coullons,Coulmiers,Courcelles,Courcy,Courtemaux,Courtempierre,Courtenay,Cravant,Crottes,Dadonville,Dammarie,Dampierre,Darvoy,Desmonts,Dimancheville,Donnery,Dordives,Dossainville,Douchy,Dry,Echilleuses,Egry,Engenville,Epieds,Erceville,Ervauville,Escrennes,Escrignelles,Estouy,Faverelles,Fay,Feins,Ferolles,Ferrieres,Fleury,Fontenay,Foret,Foucherolles,Freville,Gatinais,Gaubertin,Gemigny,Germigny,Gidy,Gien,Girolles,Givraines,Gondreville,Grangermont,Greneville,Griselles,Guigneville,Guilly,Gyleslonains,Huetre,Huisseau,Ingrannes,Ingre,Intville,Isdes,Jargeau,Jouy,Juranville,Bussiere,Laas,Ladon,Lailly,Langesse,Leouville,Ligny,Lombreuil,Lorcy,Lorris,Loury,Louzouer,Malesherbois,Marcilly,Mardie,Mareau,Marigny,Marsainvilliers,Melleroy,Menestreau,Merinville,Messas,Meung,Mezieres,Migneres,Mignerette,Mirabeau,Montargis,Montbarrois,Montbouy,Montcresson,Montereau,Montigny,Montliard,Mormant,Morville,Moulinet,Moulon,Nancray,Nargis,Nesploy,Neuville,Neuvy,Nevoy,Nibelle,Nogent,Noyers,Ocre,Oison,Olivet,Ondreville,Onzerain,Orleans,Ormes,Orville,Oussoy,Outarville,Ouzouer,Pannecieres,Pannes,Patay,Paucourt,Pers,Pierrefitte,Pithiverais,Pithiviers,Poilly,Potier,Prefontaines,Presnoy,Pressigny,Puiseaux,Quiers,Ramoulu,Rebrechien,Rouvray,Rozieres,Rozoy,Ruan,Sandillon,Santeau,Saran,Sceaux,Seichebrieres,Semoy,Sennely,Sermaises,Sigloy,Solterre,Sougy,Sully,Sury,Tavers,Thignonville,Thimory,Thorailles,Thou,Tigy,Tivernon,Tournoisis,Trainou,Treilles,Trigueres,Trinay,Vannes,Varennes,Vennecy,Vieilles,Vienne,Viglain,Vignes,Villamblain,Villemandeur,Villemoutiers,Villemurlin,Villeneuve,Villereau,Villevoques,Villorceau,Vimory,Vitry,Vrigny,Ivre"},
|
||||
{name: "Italian", i: 3, min: 5, max: 12, d: "cltr", m: .1, b: "Accumoli,Acquafondata,Acquapendente,Acuto,Affile,Agosta,Alatri,Albano,Allumiere,Alvito,Amaseno,Amatrice,Anagni,Anguillara,Anticoli,Antrodoco,Anzio,Aprilia,Aquino,Arce,Arcinazzo,Ardea,Ariccia,Arlena,Arnara,Arpino,Arsoli,Artena,Ascrea,Atina,Ausonia,Bagnoregio,Barbarano,Bassano,Bassiano,Bellegra,Belmonte,Blera,Bolsena,Bomarzo,Borbona,Borgo,Borgorose,Boville,Bracciano,Broccostella,Calcata,Camerata,Campagnano,Campodimele,Campoli,Canale,Canepina,Canino,Cantalice,Cantalupo,Canterano,Capena,Capodimonte,Capranica,Caprarola,Carbognano,Casalattico,Casalvieri,Casape,Casaprota,Casperia,Cassino,Castelforte,Castelliri,Castello,Castelnuovo,Castiglione,Castro,Castrocielo,Cave,Ceccano,Celleno,Cellere,Ceprano,Cerreto,Cervara,Cervaro,Cerveteri,Ciampino,Ciciliano,Cineto,Cisterna,Cittaducale,Cittareale,Civita,Civitavecchia,Civitella,Colfelice,Collalto,Colle,Colleferro,Collegiove,Collepardo,Collevecchio,Colli,Colonna,Concerviano,Configni,Contigliano,Corchiano,Coreno,Cori,Cottanello,Esperia,Fabrica,Faleria,Fara,Farnese,Ferentino,Fiamignano,Fiano,Filacciano,Filettino,Fiuggi,Fiumicino,Fondi,Fontana,Fonte,Fontechiari,Forano,Formello,Formia,Frascati,Frasso,Frosinone,Fumone,Gaeta,Gallese,Gallicano,Gallinaro,Gavignano,Genazzano,Genzano,Gerano,Giuliano,Gorga,Gradoli,Graffignano,Greccio,Grottaferrata,Grotte,Guarcino,Guidonia,Ischia,Isola,Itri,Jenne,Labico,Labro,Ladispoli,Lanuvio,Lariano,Latera,Lenola,Leonessa,Licenza,Longone,Lubriano,Maenza,Magliano,Mandela,Manziana,Marano,Marcellina,Marcetelli,Marino,Marta,Mazzano,Mentana,Micigliano,Minturno,Mompeo,Montalto,Montasola,Monte,Montebuono,Montefiascone,Monteflavio,Montelanico,Monteleone,Montelibretti,Montenero,Monterosi,Monterotondo,Montopoli,Montorio,Moricone,Morlupo,Morolo,Morro,Nazzano,Nemi,Nepi,Nerola,Nespolo,Nettuno,Norma,Olevano,Onano,Oriolo,Orte,Orvinio,Paganico,Palestrina,Paliano,Palombara,Pastena,Patrica,Percile,Pescorocchiano,Pescosolido,Petrella,Piansano,Picinisco,Pico,Piedimonte,Piglio,Pignataro,Pisoniano,Pofi,Poggio,Poli,Pomezia,Pontecorvo,Pontinia,Ponza,Ponzano,Posta,Pozzaglia,Priverno,Proceno,Prossedi,Riano,Rieti,Rignano,Riofreddo,Ripi,Rivodutri,Rocca,Roccagiovine,Roccagorga,Roccantica,Roccasecca,Roiate,Ronciglione,Roviano,Sabaudia,Sacrofano,Salisano,Sambuci,Santa,Santi,Santopadre,Saracinesco,Scandriglia,Segni,Selci,Sermoneta,Serrone,Settefrati,Sezze,Sgurgola,Sonnino,Sora,Soriano,Sperlonga,Spigno,Stimigliano,Strangolagalli,Subiaco,Supino,Sutri,Tarano,Tarquinia,Terelle,Terracina,Tessennano,Tivoli,Toffia,Tolfa,Torre,Torri,Torrice,Torricella,Torrita,Trevi,Trevignano,Trivigliano,Turania,Tuscania,Vacone,Valentano,Vallecorsa,Vallemaio,Vallepietra,Vallerano,Vallerotonda,Vallinfreda,Valmontone,Varco,Vasanello,Vejano,Velletri,Ventotene,Veroli,Vetralla,Vicalvi,Vico,Vicovaro,Vignanello,Viterbo,Viticuso,Vitorchiano,Vivaro,Zagarolo"},
|
||||
{name: "Castillian", i: 4, min: 5, max: 11, d: "lr", m: 0, b: "Abanades,Ablanque,Adobes,Ajofrin,Alameda,Alaminos,Alarilla,Albalate,Albares,Albarreal,Albendiego,Alcabon,Alcanizo,Alcaudete,Alcocer,Alcolea,Alcoroches,Aldea,Aldeanueva,Algar,Algora,Alhondiga,Alique,Almadrones,Almendral,Almoguera,Almonacid,Almorox,Alocen,Alovera,Alustante,Angon,Anguita,Anover,Anquela,Arbancon,Arbeteta,Arcicollar,Argecilla,Arges,Armallones,Armuna,Arroyo,Atanzon,Atienza,Aunon,Azuqueca,Azutan,Baides,Banos,Banuelos,Barcience,Bargas,Barriopedro,Belvis,Berninches,Borox,Brihuega,Budia,Buenaventura,Bujalaro,Burguillos,Burujon,Bustares,Cabanas,Cabanillas,Calera,Caleruela,Calzada,Camarena,Campillo,Camunas,Canizar,Canredondo,Cantalojas,Cardiel,Carmena,Carranque,Carriches,Casa,Casarrubios,Casas,Casasbuenas,Caspuenas,Castejon,Castellar,Castilforte,Castillo,Castilnuevo,Cazalegas,Cebolla,Cedillo,Cendejas,Centenera,Cervera,Checa,Chequilla,Chillaron,Chiloeches,Chozas,Chueca,Cifuentes,Cincovillas,Ciruelas,Ciruelos,Cobeja,Cobeta,Cobisa,Cogollor,Cogolludo,Condemios,Congostrina,Consuegra,Copernal,Corduente,Corral,Cuerva,Domingo,Dosbarrios,Driebes,Duron,El,Embid,Erustes,Escalona,Escalonilla,Escamilla,Escariche,Escopete,Espinosa,Espinoso,Esplegares,Esquivias,Estables,Estriegana,Fontanar,Fuembellida,Fuensalida,Fuentelsaz,Gajanejos,Galve,Galvez,Garciotum,Gascuena,Gerindote,Guadamur,Henche,Heras,Herreria,Herreruela,Hijes,Hinojosa,Hita,Hombrados,Hontanar,Hontoba,Horche,Hormigos,Huecas,Huermeces,Huerta,Hueva,Humanes,Illan,Illana,Illescas,Iniestola,Irueste,Jadraque,Jirueque,Lagartera,Las,Layos,Ledanca,Lillo,Lominchar,Loranca,Los,Lucillos,Lupiana,Luzaga,Luzon,Madridejos,Magan,Majaelrayo,Malaga,Malaguilla,Malpica,Mandayona,Mantiel,Manzaneque,Maqueda,Maranchon,Marchamalo,Marjaliza,Marrupe,Mascaraque,Masegoso,Matarrubia,Matillas,Mazarete,Mazuecos,Medranda,Megina,Mejorada,Mentrida,Mesegar,Miedes,Miguel,Millana,Milmarcos,Mirabueno,Miralrio,Mocejon,Mochales,Mohedas,Molina,Monasterio,Mondejar,Montarron,Mora,Moratilla,Morenilla,Muduex,Nambroca,Navalcan,Negredo,Noblejas,Noez,Nombela,Noves,Numancia,Nuno,Ocana,Ocentejo,Olias,Olmeda,Ontigola,Orea,Orgaz,Oropesa,Otero,Palmaces,Palomeque,Pantoja,Pardos,Paredes,Pareja,Parrillas,Pastrana,Pelahustan,Penalen,Penalver,Pepino,Peralejos,Peralveche,Pinilla,Pioz,Piqueras,Polan,Portillo,Poveda,Pozo,Pradena,Prados,Puebla,Puerto,Pulgar,Quer,Quero,Quintanar,Quismondo,Rebollosa,Recas,Renera,Retamoso,Retiendas,Riba,Rielves,Rillo,Riofrio,Robledillo,Robledo,Romanillos,Romanones,Rueda,Sacecorbo,Sacedon,Saelices,Salmeron,San,Santa,Santiuste,Santo,Sartajada,Sauca,Sayaton,Segurilla,Selas,Semillas,Sesena,Setiles,Sevilleja,Sienes,Siguenza,Solanillos,Somolinos,Sonseca,Sotillo,Sotodosos,Talavera,Tamajon,Taragudo,Taravilla,Tartanedo,Tembleque,Tendilla,Terzaga,Tierzo,Tordellego,Tordelrabano,Tordesilos,Torija,Torralba,Torre,Torrecilla,Torrecuadrada,Torrejon,Torremocha,Torrico,Torrijos,Torrubia,Tortola,Tortuera,Tortuero,Totanes,Traid,Trijueque,Trillo,Turleque,Uceda,Ugena,Ujados,Urda,Utande,Valdarachas,Valdesotos,Valhermoso,Valtablado,Valverde,Velada,Viana,Vinuelas,Yebes,Yebra,Yelamos,Yeles,Yepes,Yuncler,Yunclillos,Yuncos,Yunquera,Zaorejas,Zarzuela,Zorita"},
|
||||
{name: "Castillian", i: 4, min: 5, max: 11, d: "lr", m: 0, b: "Abanades,Ablanque,Adobes,Ajofrin,Alameda,Alaminos,Alarilla,Albalate,Albares,Albarreal,Albendiego,Alcabon,Alcanizo,Alcaudete,Alcocer,Alcolea,Alcoroches,Aldea,Aldeanueva,Algar,Algora,Alhondiga,Alique,Almadrones,Almendral,Almoguera,Almonacid,Almorox,Alocen,Alovera,Alustante,Angon,Anguita,Anover,Anquela,Arbancon,Arbeteta,Arcicollar,Argecilla,Arges,Armallones,Armuna,Arroyo,Atanzon,Atienza,Aunon,Azuqueca,Azutan,Baides,Banos,Banuelos,Barcience,Bargas,Barriopedro,Belvis,Berninches,Borox,Brihuega,Budia,Buenaventura,Bujalaro,Burguillos,Burujon,Bustares,Cabanas,Cabanillas,Calera,Caleruela,Calzada,Camarena,Campillo,Camunas,Canizar,Canredondo,Cantalojas,Cardiel,Carmena,Carranque,Carriches,Casa,Casarrubios,Casas,Casasbuenas,Caspuenas,Castejon,Castellar,Castilforte,Castillo,Castilnuevo,Cazalegas,Cebolla,Cedillo,Cendejas,Centenera,Cervera,Checa,Chequilla,Chillaron,Chiloeches,Chozas,Chueca,Cifuentes,Cincovillas,Ciruelas,Ciruelos,Cobeja,Cobeta,Cobisa,Cogollor,Cogolludo,Condemios,Congostrina,Consuegra,Copernal,Corduente,Corral,Cuerva,Domingo,Dosbarrios,Driebes,Duron,El,Embid,Erustes,Escalona,Escalonilla,Escamilla,Escariche,Escopete,Espinosa,Espinoso,Esplegares,Esquivias,Estables,Estriegana,Fontanar,Fuembellida,Fuensalida,Fuentelsaz,Gajanejos,Galve,Galvez,Garciotum,Gascuena,Gerindote,Guadamur,Henche,Heras,Herreria,Herreruela,Hijes,Hinojosa,Hita,Hombrados,Hontanar,Hontoba,Horche,Hormigos,Huecas,Huermeces,Huerta,Hueva,Humanes,Illan,Illana,Illescas,Iniestola,Irueste,Jadraque,Jirueque,Lagartera,Las,Layos,Ledanca,Lillo,Lominchar,Loranca,Los,Lucillos,Lupiana,Luzaga,Luzon,Madridejos,Magan,Majaelrayo,Malaga,Malaguilla,Malpica,Mandayona,Mantiel,Manzaneque,Maqueda,Maranchon,Marchamalo,Marjaliza,Marrupe,Mascaraque,Masegoso,Matarrubia,Matillas,Mazarete,Mazuecos,Medranda,Megina,Mejorada,Mentrida,Mesegar,Miedes,Miguel,Millana,Milmarcos,Mirabueno,Miralrio,Mocejon,Mochales,Mohedas,Molina,Monasterio,Mondejar,Montarron,Mora,Moratilla,Morenilla,Muduex,Nambroca,Navalcan,Negredo,Noblejas,Noez,Nombela,Noves,Numancia,Nuno,Ocana,Ocentejo,Olias,Olmeda,Ontigola,Orea,Orgaz,Oropesa,Otero,Palmaces,Palomeque,Pantoja,Pardos,Paredes,Pareja,Parrillas,Pastrana,Pelahustan,Penalen,Penalver,Pepino,Peralejos,Peralveche,Pinilla,Pioz,Piqueras,Polan,Portillo,Poveda,Pozo,Pradena,Prados,Puebla,Puerto,Pulgar,Quer,Quero,Quintanar,Quismondo,Rebollosa,Recas,Renera,Retamoso,Retiendas,Riba,Rielves,Rillo,Riofrio,Robledillo,Robledo,Romanillos,Romanones,Rueda,Sacecorbo,Sacedon,Saelices,Salmeron,San,Santa,Santiuste,Santo,Sartajada,Sauca,Sayaton,Segurilla,Selas,Semillas,Sesena,Setiles,Sevilleja,Sienes,Siguenza,Solanillos,Somolinos,Sonseca,Sotillo,Sotodasos,Talavera,Tamajon,Taragudo,Taravilla,Tartanedo,Tembleque,Tendilla,Terzaga,Tierzo,Tordellego,Tordelrabano,Tordesilos,Torija,Torralba,Torre,Torrecilla,Torrecuadrada,Torrejon,Torremocha,Torrico,Torrijos,Torrubia,Tortola,Tortuera,Tortuero,Totanes,Traid,Trijueque,Trillo,Turleque,Uceda,Ugena,Ujados,Urda,Utande,Valdarachas,Valdesotos,Valhermoso,Valtablado,Valverde,Velada,Viana,Vinuelas,Yebes,Yebra,Yelamos,Yeles,Yepes,Yuncler,Yunclillos,Yuncos,Yunquera,Zaorejas,Zarzuela,Zorita"},
|
||||
{name: "Ruthenian", i: 5, min: 5, max: 10, d: "", m: 0, b: "Belgorod,Beloberezhye,Belyi,Belz,Berestiy,Berezhets,Berezovets,Berezutsk,Bobruisk,Bolonets,Borisov,Borovsk,Bozhesk,Bratslav,Bryansk,Brynsk,Buryn,Byhov,Chechersk,Chemesov,Cheremosh,Cherlen,Chern,Chernigov,Chernitsa,Chernobyl,Chernogorod,Chertoryesk,Chetvertnia,Demyansk,Derevesk,Devyagoresk,Dichin,Dmitrov,Dorogobuch,Dorogobuzh,Drestvin,Drokov,Drutsk,Dubechin,Dubichi,Dubki,Dubkov,Dveren,Galich,Glebovo,Glinsk,Goloty,Gomiy,Gorodets,Gorodische,Gorodno,Gorohovets,Goroshin,Gorval,Goryshon,Holm,Horobor,Hoten,Hotin,Hotmyzhsk,Ilovech,Ivan,Izborsk,Izheslavl,Kamenets,Kanev,Karachev,Karna,Kavarna,Klechesk,Klyapech,Kolomyya,Kolyvan,Kopyl,Korec,Kornik,Korochunov,Korshev,Korsun,Koshkin,Kotelno,Kovyla,Kozelsk,Kozelsk,Kremenets,Krichev,Krylatsk,Ksniatin,Kulatsk,Kursk,Kursk,Lebedev,Lida,Logosko,Lomihvost,Loshesk,Loshichi,Lubech,Lubno,Lubutsk,Lutsk,Luchin,Luki,Lukoml,Luzha,Lvov,Mtsensk,Mdin,Medniki,Melecha,Merech,Meretsk,Mescherskoe,Meshkovsk,Metlitsk,Mezetsk,Mglin,Mihailov,Mikitin,Mikulino,Miloslavichi,Mogilev,Mologa,Moreva,Mosalsk,Moschiny,Mozyr,Mstislav,Mstislavets,Muravin,Nemech,Nemiza,Nerinsk,Nichan,Novgorod,Novogorodok,Obolichi,Obolensk,Obolensk,Oleshsk,Olgov,Omelnik,Opoka,Opoki,Oreshek,Orlets,Osechen,Oster,Ostrog,Ostrov,Perelai,Peremil,Peremyshl,Pererov,Peresechen,Perevitsk,Pereyaslav,Pinsk,Ples,Polotsk,Pronsk,Proposhesk,Punia,Putivl,Rechitsa,Rodno,Rogachev,Romanov,Romny,Roslavl,Rostislavl,Rostovets,Rsha,Ruza,Rybchesk,Rylsk,Rzhavesk,Rzhev,Rzhischev,Sambor,Serensk,Serensk,Serpeysk,Shilov,Shuya,Sinech,Sizhka,Skala,Slovensk,Slutsk,Smedin,Sneporod,Snitin,Snovsk,Sochevo,Sokolec,Starica,Starodub,Stepan,Sterzh,Streshin,Sutesk,Svinetsk,Svisloch,Terebovl,Ternov,Teshilov,Teterin,Tiversk,Torchevsk,Toropets,Torzhok,Tripolye,Trubchevsk,Tur,Turov,Usvyaty,Uteshkov,Vasilkov,Velil,Velye,Venev,Venicha,Verderev,Vereya,Veveresk,Viazma,Vidbesk,Vidychev,Voino,Volodimer,Volok,Volyn,Vorobesk,Voronich,Voronok,Vorotynsk,Vrev,Vruchiy,Vselug,Vyatichsk,Vyatka,Vyshegorod,Vyshgorod,Vysokoe,Yagniatin,Yaropolch,Yasenets,Yuryev,Yuryevets,Zaraysk,Zhitomel,Zholvazh,Zizhech,Zubkov,Zudechev,Zvenigorod"},
|
||||
{name: "Nordic", i: 6, min: 6, max: 10, d: "kln", m: .1, b: "Akureyri,Aldra,Alftanes,Andenes,Austbo,Auvog,Bakkafjordur,Ballangen,Bardal,Beisfjord,Bifrost,Bildudalur,Bjerka,Bjerkvik,Bjorkosen,Bliksvaer,Blokken,Blonduos,Bolga,Bolungarvik,Borg,Borgarnes,Bosmoen,Bostad,Bostrand,Botsvika,Brautarholt,Breiddalsvik,Bringsli,Brunahlid,Budardalur,Byggdakjarni,Dalvik,Djupivogur,Donnes,Drageid,Drangsnes,Egilsstadir,Eiteroga,Elvenes,Engavogen,Ertenvog,Eskifjordur,Evenes,Eyrarbakki,Fagernes,Fallmoen,Fellabaer,Fenes,Finnoya,Fjaer,Fjelldal,Flakstad,Flateyri,Flostrand,Fludir,Gardaber,Gardur,Gimstad,Givaer,Gjeroy,Gladstad,Godoya,Godoynes,Granmoen,Gravdal,Grenivik,Grimsey,Grindavik,Grytting,Hafnir,Halsa,Hauganes,Haugland,Hauknes,Hella,Helland,Hellissandur,Hestad,Higrav,Hnifsdalur,Hofn,Hofsos,Holand,Holar,Holen,Holkestad,Holmavik,Hopen,Hovden,Hrafnagil,Hrisey,Husavik,Husvik,Hvammstangi,Hvanneyri,Hveragerdi,Hvolsvollur,Igeroy,Indre,Inndyr,Innhavet,Innes,Isafjordur,Jarklaustur,Jarnsreykir,Junkerdal,Kaldvog,Kanstad,Karlsoy,Kavosen,Keflavik,Kjelde,Kjerstad,Klakk,Kopasker,Kopavogur,Korgen,Kristnes,Krutoga,Krystad,Kvina,Lande,Laugar,Laugaras,Laugarbakki,Laugarvatn,Laupstad,Leines,Leira,Leiren,Leland,Lenvika,Loding,Lodingen,Lonsbakki,Lopsmarka,Lovund,Luroy,Maela,Melahverfi,Meloy,Mevik,Misvaer,Mornes,Mosfellsber,Moskenes,Myken,Naurstad,Nesberg,Nesjahverfi,Nesset,Nevernes,Obygda,Ofoten,Ogskardet,Okervika,Oknes,Olafsfjordur,Oldervika,Olstad,Onstad,Oppeid,Oresvika,Orsnes,Orsvog,Osmyra,Overdal,Prestoya,Raudalaekur,Raufarhofn,Reipo,Reykholar,Reykholt,Reykjahlid,Rif,Rinoya,Rodoy,Rognan,Rosvika,Rovika,Salhus,Sanden,Sandgerdi,Sandoker,Sandset,Sandvika,Saudarkrokur,Selfoss,Selsoya,Sennesvik,Setso,Siglufjordur,Silvalen,Skagastrond,Skjerstad,Skonland,Skorvogen,Skrova,Sleneset,Snubba,Softing,Solheim,Solheimar,Sorarnoy,Sorfugloy,Sorland,Sormela,Sorvaer,Sovika,Stamsund,Stamsvika,Stave,Stokka,Stokkseyri,Storjord,Storo,Storvika,Strand,Straumen,Strendene,Sudavik,Sudureyri,Sundoya,Sydalen,Thingeyri,Thorlakshofn,Thorshofn,Tjarnabyggd,Tjotta,Tosbotn,Traelnes,Trofors,Trones,Tverro,Ulvsvog,Unnstad,Utskor,Valla,Vandved,Varmahlid,Vassos,Vevelstad,Vidrek,Vik,Vikholmen,Vogar,Vogehamn,Vopnafjordur"},
|
||||
{name: "Greek", i: 7, min: 5, max: 11, d: "s", m: .1, b: "Abdera,Abila,Abydos,Acanthus,Acharnae,Actium,Adramyttium,Aegae,Aegina,Aegium,Aenus,Agrinion,Aigosthena,Akragas,Akrai,Akrillai,Akroinon,Akrotiri,Alalia,Alexandreia,Alexandretta,Alexandria,Alinda,Amarynthos,Amaseia,Ambracia,Amida,Amisos,Amnisos,Amphicaea,Amphigeneia,Amphipolis,Amphissa,Ankon,Antigona,Antipatrea,Antioch,Antioch,Antiochia,Andros,Apamea,Aphidnae,Apollonia,Argos,Arsuf,Artanes,Artemita,Argyroupoli,Asine,Asklepios,Aspendos,Assus,Astacus,Athenai,Athmonia,Aytos,Ancient,Baris,Bhrytos,Borysthenes,Berge,Boura,Bouthroton,Brauron,Byblos,Byllis,Byzantium,Bythinion,Callipolis,Cebrene,Chalcedon,Calydon,Carystus,Chamaizi,Chalcis,Chersonesos,Chios,Chytri,Clazomenae,Cleonae,Cnidus,Colosse,Corcyra,Croton,Cyme,Cyrene,Cythera,Decelea,Delos,Delphi,Demetrias,Dicaearchia,Dimale,Didyma,Dion,Dioscurias,Dodona,Dorylaion,Dyme,Edessa,Elateia,Eleusis,Eleutherna,Emporion,Ephesus,Ephyra,Epidamnos,Epidauros,Eresos,Eretria,Erythrae,Eubea,Gangra,Gaza,Gela,Golgi,Gonnos,Gorgippia,Gournia,Gortyn,Gythium,Hagios,Hagia,Halicarnassus,Halieis,Helike,Heliopolis,Hellespontos,Helorus,Hemeroskopeion,Heraclea,Hermione,Hermonassa,Hierapetra,Hierapolis,Himera,Histria,Hubla,Hyele,Ialysos,Iasus,Idalium,Imbros,Iolcus,Itanos,Ithaca,Juktas,Kallipolis,Kamares,Kameiros,Kannia,Kamarina,Kasmenai,Katane,Kerkinitida,Kepoi,Kimmerikon,Kios,Klazomenai,Knidos,Knossos,Korinthos,Kos,Kourion,Kume,Kydonia,Kynos,Kyrenia,Lamia,Lampsacus,Laodicea,Lapithos,Larissa,Lato,Laus,Lebena,Lefkada,Lekhaion,Leibethra,Leontinoi,Lepreum,Lessa,Lilaea,Lindus,Lissus,Epizephyrian,Madytos,Magnesia,Mallia,Mantineia,Marathon,Marmara,Maroneia,Masis,Massalia,Megalopolis,Megara,Mesembria,Messene,Metapontum,Methana,Methone,Methumna,Miletos,Misenum,Mochlos,Monastiraki,Morgantina,Mulai,Mukenai,Mylasa,Myndus,Myonia,Myra,Myrmekion,Mutilene,Myos,Nauplios,Naucratis,Naupactus,Naxos,Neapoli,Neapolis,Nemea,Nicaea,Nicopolis,Nirou,Nymphaion,Nysa,Oenoe,Oenus,Odessos,Olbia,Olous,Olympia,Olynthus,Opus,Orchomenus,Oricos,Orestias,Oreus,Oropus,Onchesmos,Pactye,Pagasae,Palaikastro,Pandosia,Panticapaeum,Paphos,Parium,Paros,Parthenope,Patrae,Pavlopetri,Pegai,Pelion,Peiraies,Pella,Percote,Pergamum,Petsofa,Phaistos,Phaleron,Phanagoria,Pharae,Pharnacia,Pharos,Phaselis,Philippi,Pithekussa,Philippopolis,Platanos,Phlius,Pherae,Phocaea,Pinara,Pisa,Pitane,Pitiunt,Pixous,Plataea,Poseidonia,Potidaea,Priapus,Priene,Prousa,Pseira,Psychro,Pteleum,Pydna,Pylos,Pyrgos,Rhamnus,Rhegion,Rhithymna,Rhodes,Rhypes,Rizinia,Salamis,Same,Samos,Scyllaeum,Selinus,Seleucia,Semasus,Sestos,Scidrus,Sicyon,Side,Sidon,Siteia,Sinope,Siris,Sklavokampos,Smyrna,Soli,Sozopolis,Sparta,Stagirus,Stratos,Stymphalos,Sybaris,Surakousai,Taras,Tanagra,Tanais,Tauromenion,Tegea,Temnos,Tenedos,Tenea,Teos,Thapsos,Thassos,Thebai,Theodosia,Therma,Thespiae,Thronion,Thoricus,Thurii,Thyreum,Thyria,Tiruns,Tithoraea,Tomis,Tragurion,Trapeze,Trapezus,Tripolis,Troizen,Troliton,Troy,Tylissos,Tyras,Tyros,Tyritake,Vasiliki,Vathypetros,Zakynthos,Zakros,Zankle"},
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.OceanLayers = (function () {
|
||||
let cells, vertices, pointsN, used;
|
||||
|
||||
const OceanLayers = function OceanLayers() {
|
||||
const outline = oceanLayers.attr("layers");
|
||||
if (outline === "none") return;
|
||||
TIME && console.time("drawOceanLayers");
|
||||
const outline = oceanLayers.attr('layers');
|
||||
if (outline === 'none') return;
|
||||
TIME && console.time('drawOceanLayers');
|
||||
|
||||
lineGen.curve(d3.curveBasisClosed);
|
||||
(cells = grid.cells), (pointsN = grid.cells.i.length), (vertices = grid.vertices);
|
||||
const limits = outline === "random" ? randomizeOutline() : outline.split(",").map(s => +s);
|
||||
const limits = outline === 'random' ? randomizeOutline() : outline.split(',').map((s) => +s);
|
||||
|
||||
const chains = [];
|
||||
const opacity = rn(0.4 / limits.length, 2);
|
||||
|
|
@ -26,28 +26,28 @@ window.OceanLayers = (function () {
|
|||
const chain = connectVertices(start, t); // vertices chain to form a path
|
||||
if (chain.length < 4) continue;
|
||||
const relax = 1 + t * -2; // select only n-th point
|
||||
const relaxed = chain.filter((v, i) => !(i % relax) || vertices.c[v].some(c => c >= pointsN));
|
||||
const relaxed = chain.filter((v, i) => !(i % relax) || vertices.c[v].some((c) => c >= pointsN));
|
||||
if (relaxed.length < 4) continue;
|
||||
const points = clipPoly(
|
||||
relaxed.map(v => vertices.p[v]),
|
||||
relaxed.map((v) => vertices.p[v]),
|
||||
1
|
||||
);
|
||||
chains.push([t, points]);
|
||||
}
|
||||
|
||||
for (const t of limits) {
|
||||
const layer = chains.filter(c => c[0] === t);
|
||||
let path = layer.map(c => round(lineGen(c[1]))).join("");
|
||||
if (path) oceanLayers.append("path").attr("d", path).attr("fill", "#ecf2f9").style("opacity", opacity);
|
||||
const layer = chains.filter((c) => c[0] === t);
|
||||
let path = layer.map((c) => round(lineGen(c[1]))).join('');
|
||||
if (path) oceanLayers.append('path').attr('d', path).attr('fill', '#ecf2f9').style('opacity', opacity);
|
||||
}
|
||||
|
||||
// find eligible cell vertex to start path detection
|
||||
function findStart(i, t) {
|
||||
if (cells.b[i]) return cells.v[i].find(v => vertices.c[v].some(c => c >= pointsN)); // map border cell
|
||||
return cells.v[i][cells.c[i].findIndex(c => cells.t[c] < t || !cells.t[c])];
|
||||
if (cells.b[i]) return cells.v[i].find((v) => vertices.c[v].some((c) => c >= pointsN)); // map border cell
|
||||
return cells.v[i][cells.c[i].findIndex((c) => cells.t[c] < t || !cells.t[c])];
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("drawOceanLayers");
|
||||
TIME && console.timeEnd('drawOceanLayers');
|
||||
};
|
||||
|
||||
function randomizeOutline() {
|
||||
|
|
@ -71,7 +71,7 @@ window.OceanLayers = (function () {
|
|||
const prev = chain[chain.length - 1]; // previous vertex in chain
|
||||
chain.push(current); // add current vertex to sequence
|
||||
const c = vertices.c[current]; // cells adjacent to vertex
|
||||
c.filter(c => cells.t[c] === t).forEach(c => (used[c] = 1));
|
||||
c.filter((c) => cells.t[c] === t).forEach((c) => (used[c] = 1));
|
||||
const v = vertices.v[current]; // neighboring vertices
|
||||
const c0 = !cells.t[c[0]] || cells.t[c[0]] === t - 1;
|
||||
const c1 = !cells.t[c[1]] || cells.t[c[1]] === t - 1;
|
||||
|
|
@ -80,7 +80,7 @@ window.OceanLayers = (function () {
|
|||
else if (v[1] !== undefined && v[1] !== prev && c1 !== c2) current = v[1];
|
||||
else if (v[2] !== undefined && v[2] !== prev && c0 !== c2) current = v[2];
|
||||
if (current === chain[chain.length - 1]) {
|
||||
ERROR && console.error("Next vertex is not found");
|
||||
ERROR && console.error('Next vertex is not found');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.ReliefIcons = (function () {
|
||||
const ReliefIcons = function () {
|
||||
TIME && console.time("drawRelief");
|
||||
terrain.selectAll("*").remove();
|
||||
TIME && console.time('drawRelief');
|
||||
terrain.selectAll('*').remove();
|
||||
|
||||
const cells = pack.cells;
|
||||
const density = terrain.attr("density") || 0.4;
|
||||
const size = 2 * (terrain.attr("size") || 1);
|
||||
const density = terrain.attr('density') || 0.4;
|
||||
const size = 2 * (terrain.attr('size') || 1);
|
||||
const mod = 0.2 * size; // size modifier
|
||||
const relief = [];
|
||||
|
||||
|
|
@ -19,8 +19,8 @@ window.ReliefIcons = (function () {
|
|||
if (height < 50 && biomesData.iconsDensity[biome] === 0) continue; // no icons for this biome
|
||||
|
||||
const polygon = getPackPolygon(i);
|
||||
const [minX, maxX] = d3.extent(polygon, p => p[0]);
|
||||
const [minY, maxY] = d3.extent(polygon, p => p[1]);
|
||||
const [minX, maxX] = d3.extent(polygon, (p) => p[0]);
|
||||
const [minY, maxY] = d3.extent(polygon, (p) => p[1]);
|
||||
|
||||
if (height < 50) placeBiomeIcons(i, biome);
|
||||
else placeReliefIcons(i);
|
||||
|
|
@ -34,7 +34,7 @@ window.ReliefIcons = (function () {
|
|||
if (!d3.polygonContains(polygon, [cx, cy])) continue;
|
||||
let h = (4 + Math.random()) * size;
|
||||
const icon = getBiomeIcon(i, biomesData.icons[biome]);
|
||||
if (icon === "#relief-grass-1") h *= 1.2;
|
||||
if (icon === '#relief-grass-1') h *= 1.2;
|
||||
relief.push({i: icon, x: rn(cx - h, 2), y: rn(cy - h, 2), s: rn(h * 2, 2)});
|
||||
}
|
||||
}
|
||||
|
|
@ -51,8 +51,8 @@ window.ReliefIcons = (function () {
|
|||
|
||||
function getReliefIcon(i, h) {
|
||||
const temp = grid.cells.temp[pack.cells.g[i]];
|
||||
const type = h > 70 && temp < 0 ? "mountSnow" : h > 70 ? "mount" : "hill";
|
||||
const size = h > 70 ? (h - 45) * mod : Math.min(Math.max((h - 40) * mod, 3), 6);
|
||||
const type = h > 70 && temp < 0 ? 'mountSnow' : h > 70 ? 'mount' : 'hill';
|
||||
const size = h > 70 ? (h - 45) * mod : minmax((h - 40) * mod, 3, 6);
|
||||
return [getIcon(type), size];
|
||||
}
|
||||
}
|
||||
|
|
@ -60,39 +60,39 @@ window.ReliefIcons = (function () {
|
|||
// sort relief icons by y+size
|
||||
relief.sort((a, b) => a.y + a.s - (b.y + b.s));
|
||||
|
||||
let reliefHTML = "";
|
||||
let reliefHTML = '';
|
||||
for (const r of relief) {
|
||||
reliefHTML += `<use href="${r.i}" x="${r.x}" y="${r.y}" width="${r.s}" height="${r.s}"/>`;
|
||||
}
|
||||
terrain.html(reliefHTML);
|
||||
|
||||
TIME && console.timeEnd("drawRelief");
|
||||
TIME && console.timeEnd('drawRelief');
|
||||
};
|
||||
|
||||
function getBiomeIcon(i, b) {
|
||||
let type = b[Math.floor(Math.random() * b.length)];
|
||||
const temp = grid.cells.temp[pack.cells.g[i]];
|
||||
if (type === "conifer" && temp < 0) type = "coniferSnow";
|
||||
if (type === 'conifer' && temp < 0) type = 'coniferSnow';
|
||||
return getIcon(type);
|
||||
}
|
||||
|
||||
function getVariant(type) {
|
||||
switch (type) {
|
||||
case "mount":
|
||||
case 'mount':
|
||||
return rand(2, 7);
|
||||
case "mountSnow":
|
||||
case 'mountSnow':
|
||||
return rand(1, 6);
|
||||
case "hill":
|
||||
case 'hill':
|
||||
return rand(2, 5);
|
||||
case "conifer":
|
||||
case 'conifer':
|
||||
return 2;
|
||||
case "coniferSnow":
|
||||
case 'coniferSnow':
|
||||
return 1;
|
||||
case "swamp":
|
||||
case 'swamp':
|
||||
return rand(2, 3);
|
||||
case "cactus":
|
||||
case 'cactus':
|
||||
return rand(1, 3);
|
||||
case "deadTree":
|
||||
case 'deadTree':
|
||||
return rand(1, 2);
|
||||
default:
|
||||
return 2;
|
||||
|
|
@ -101,27 +101,27 @@ window.ReliefIcons = (function () {
|
|||
|
||||
function getOldIcon(type) {
|
||||
switch (type) {
|
||||
case "mountSnow":
|
||||
return "mount";
|
||||
case "vulcan":
|
||||
return "mount";
|
||||
case "coniferSnow":
|
||||
return "conifer";
|
||||
case "cactus":
|
||||
return "dune";
|
||||
case "deadTree":
|
||||
return "dune";
|
||||
case 'mountSnow':
|
||||
return 'mount';
|
||||
case 'vulcan':
|
||||
return 'mount';
|
||||
case 'coniferSnow':
|
||||
return 'conifer';
|
||||
case 'cactus':
|
||||
return 'dune';
|
||||
case 'deadTree':
|
||||
return 'dune';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
function getIcon(type) {
|
||||
const set = terrain.attr("set") || "simple";
|
||||
if (set === "simple") return "#relief-" + getOldIcon(type) + "-1";
|
||||
if (set === "colored") return "#relief-" + type + "-" + getVariant(type);
|
||||
if (set === "gray") return "#relief-" + type + "-" + getVariant(type) + "-bw";
|
||||
return "#relief-" + getOldIcon(type) + "-1"; // simple
|
||||
const set = terrain.attr('set') || 'simple';
|
||||
if (set === 'simple') return '#relief-' + getOldIcon(type) + '-1';
|
||||
if (set === 'colored') return '#relief-' + type + '-' + getVariant(type);
|
||||
if (set === 'gray') return '#relief-' + type + '-' + getVariant(type) + '-bw';
|
||||
return '#relief-' + getOldIcon(type) + '-1'; // simple
|
||||
}
|
||||
|
||||
return ReliefIcons;
|
||||
|
|
|
|||
|
|
@ -287,7 +287,16 @@ window.Religions = (function () {
|
|||
Heresy: {Heresy: 1}
|
||||
};
|
||||
|
||||
const methods = {'Random + type': 3, 'Random + ism': 1, 'Supreme + ism': 5, 'Faith of + Supreme': 5, 'Place + ism': 1, 'Culture + ism': 2, 'Place + ian + type': 6, 'Culture + type': 4};
|
||||
const methods = {
|
||||
'Random + type': 3,
|
||||
'Random + ism': 1,
|
||||
'Supreme + ism': 5,
|
||||
'Faith of + Supreme': 5,
|
||||
'Place + ism': 1,
|
||||
'Culture + ism': 2,
|
||||
'Place + ian + type': 6,
|
||||
'Culture + type': 4
|
||||
};
|
||||
|
||||
const types = {
|
||||
Shamanism: {Beliefs: 3, Shamanism: 2, Spirits: 1},
|
||||
|
|
@ -419,9 +428,20 @@ window.Religions = (function () {
|
|||
const name = getCultName('Heresy', center);
|
||||
const expansionism = gauss(1.2, 0.5, 0, 5);
|
||||
const color = getMixedColor(r.color, 0.4, 0.2); // "url(#hatch6)";
|
||||
religions.push({i: religions.length, name, color, culture, type: 'Heresy', form: r.form, deity: r.deity, expansion: 'global', expansionism, center, origin: r.i});
|
||||
religions.push({
|
||||
i: religions.length,
|
||||
name,
|
||||
color,
|
||||
culture,
|
||||
type: 'Heresy',
|
||||
form: r.form,
|
||||
deity: r.deity,
|
||||
expansion: 'global',
|
||||
expansionism,
|
||||
center,
|
||||
origin: r.i
|
||||
});
|
||||
religionsTree.add([x, y]);
|
||||
//debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).attr("fill", "green");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -454,7 +474,24 @@ window.Religions = (function () {
|
|||
name,
|
||||
religions.map((r) => r.code)
|
||||
);
|
||||
religions.push({i, name, color, culture, type, form: formName, deity, expansion, expansionism: 0, center, cells: 0, area: 0, rural: 0, urban: 0, origin: r, code});
|
||||
religions.push({
|
||||
i,
|
||||
name,
|
||||
color,
|
||||
culture,
|
||||
type,
|
||||
form: formName,
|
||||
deity,
|
||||
expansion,
|
||||
expansionism: 0,
|
||||
center,
|
||||
cells: 0,
|
||||
area: 0,
|
||||
rural: 0,
|
||||
urban: 0,
|
||||
origin: r,
|
||||
code
|
||||
});
|
||||
cells.religion[center] = i;
|
||||
};
|
||||
|
||||
|
|
@ -551,21 +588,19 @@ window.Religions = (function () {
|
|||
};
|
||||
|
||||
function checkCenters() {
|
||||
const cells = pack.cells,
|
||||
religions = pack.religions;
|
||||
const {cells, religions} = pack;
|
||||
|
||||
const codes = religions.map((r) => r.code);
|
||||
religions
|
||||
.filter((r) => r.i)
|
||||
.forEach((r) => {
|
||||
r.code = abbreviate(r.name, codes);
|
||||
religions.forEach((r) => {
|
||||
if (!r.i) return;
|
||||
r.code = abbreviate(r.name, codes);
|
||||
|
||||
// move religion center if it's not within religion area after expansion
|
||||
if (cells.religion[r.center] === r.i) return; // in area
|
||||
const religCells = cells.i.filter((i) => cells.religion[i] === r.i);
|
||||
if (!religCells.length) return; // extinct religion
|
||||
r.center = religCells.sort((a, b) => b.pop - a.pop)[0];
|
||||
});
|
||||
// move religion center if it's not within religion area after expansion
|
||||
if (cells.religion[r.center] === r.i) return; // in area
|
||||
const religCells = cells.i.filter((i) => cells.religion[i] === r.i);
|
||||
if (!religCells.length) return; // extinct religion
|
||||
r.center = religCells.sort((a, b) => cells.pop[b] - cells.pop[a])[0];
|
||||
});
|
||||
}
|
||||
|
||||
function updateCultures() {
|
||||
|
|
|
|||
993
modules/religions-generator.js.orig
Normal file
993
modules/religions-generator.js.orig
Normal file
|
|
@ -0,0 +1,993 @@
|
|||
'use strict';
|
||||
|
||||
window.Religions = (function () {
|
||||
// name generation approach and relative chance to be selected
|
||||
const approach = {
|
||||
Number: 1,
|
||||
Being: 3,
|
||||
Adjective: 5,
|
||||
<<<<<<< HEAD
|
||||
'Color + Animal': 5,
|
||||
'Adjective + Animal': 5,
|
||||
'Adjective + Being': 5,
|
||||
'Adjective + Genitive': 1,
|
||||
'Color + Being': 3,
|
||||
'Color + Genitive': 3,
|
||||
'Being + of + Genitive': 2,
|
||||
'Being + of the + Genitive': 1,
|
||||
'Animal + of + Genitive': 1,
|
||||
'Adjective + Being + of + Genitive': 2,
|
||||
'Adjective + Animal + of + Genitive': 2
|
||||
=======
|
||||
"Color + Animal": 5,
|
||||
"Adjective + Animal": 5,
|
||||
"Adjective + Being": 5,
|
||||
"Adjective + Genitive": 1,
|
||||
"Color + Being": 3,
|
||||
"Color + Genitive": 3,
|
||||
"Being + of + Genitive": 2,
|
||||
"Being + of the + Genitive": 1,
|
||||
"Animal + of + Genitive": 1,
|
||||
"Adjective + Being + of + Genitive": 2,
|
||||
"Adjective + Animal + of + Genitive": 2
|
||||
>>>>>>> master
|
||||
};
|
||||
|
||||
// turn weighted array into simple array
|
||||
const approaches = [];
|
||||
for (const a in approach) {
|
||||
for (let j = 0; j < approach[a]; j++) {
|
||||
approaches.push(a);
|
||||
}
|
||||
}
|
||||
|
||||
const base = {
|
||||
<<<<<<< HEAD
|
||||
number: ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve'],
|
||||
being: [
|
||||
'God',
|
||||
'Goddess',
|
||||
'Lord',
|
||||
'Lady',
|
||||
'Deity',
|
||||
'Creator',
|
||||
'Maker',
|
||||
'Overlord',
|
||||
'Ruler',
|
||||
'Chief',
|
||||
'Master',
|
||||
'Spirit',
|
||||
'Ancestor',
|
||||
'Father',
|
||||
'Forebear',
|
||||
'Forefather',
|
||||
'Mother',
|
||||
'Brother',
|
||||
'Sister',
|
||||
'Elder',
|
||||
'Numen',
|
||||
'Ancient',
|
||||
'Virgin',
|
||||
'Giver',
|
||||
'Council',
|
||||
'Guardian',
|
||||
'Reaper'
|
||||
],
|
||||
animal: [
|
||||
'Dragon',
|
||||
'Wyvern',
|
||||
'Phoenix',
|
||||
'Unicorn',
|
||||
'Sphinx',
|
||||
'Centaur',
|
||||
'Pegasus',
|
||||
'Kraken',
|
||||
'Basilisk',
|
||||
'Chimera',
|
||||
'Cyclope',
|
||||
'Antelope',
|
||||
'Ape',
|
||||
'Badger',
|
||||
'Bear',
|
||||
'Beaver',
|
||||
'Bison',
|
||||
'Boar',
|
||||
'Buffalo',
|
||||
'Cat',
|
||||
'Cobra',
|
||||
'Crane',
|
||||
'Crocodile',
|
||||
'Crow',
|
||||
'Deer',
|
||||
'Dog',
|
||||
'Eagle',
|
||||
'Elk',
|
||||
'Fox',
|
||||
'Goat',
|
||||
'Goose',
|
||||
'Hare',
|
||||
'Hawk',
|
||||
'Heron',
|
||||
'Horse',
|
||||
'Hyena',
|
||||
'Ibis',
|
||||
'Jackal',
|
||||
'Jaguar',
|
||||
'Lark',
|
||||
'Leopard',
|
||||
'Lion',
|
||||
'Mantis',
|
||||
'Marten',
|
||||
'Moose',
|
||||
'Mule',
|
||||
'Narwhal',
|
||||
'Owl',
|
||||
'Panther',
|
||||
'Rat',
|
||||
'Raven',
|
||||
'Rook',
|
||||
'Scorpion',
|
||||
'Shark',
|
||||
'Sheep',
|
||||
'Snake',
|
||||
'Spider',
|
||||
'Swan',
|
||||
'Tiger',
|
||||
'Turtle',
|
||||
'Viper',
|
||||
'Vulture',
|
||||
'Walrus',
|
||||
'Wolf',
|
||||
'Wolverine',
|
||||
'Worm',
|
||||
'Camel',
|
||||
'Falcon',
|
||||
'Hound',
|
||||
'Ox',
|
||||
'Serpent'
|
||||
],
|
||||
adjective: [
|
||||
'New',
|
||||
'Good',
|
||||
'High',
|
||||
'Old',
|
||||
'Great',
|
||||
'Big',
|
||||
'Young',
|
||||
'Major',
|
||||
'Strong',
|
||||
'Happy',
|
||||
'Last',
|
||||
'Main',
|
||||
'Huge',
|
||||
'Far',
|
||||
'Beautiful',
|
||||
'Wild',
|
||||
'Fair',
|
||||
'Prime',
|
||||
'Crazy',
|
||||
'Ancient',
|
||||
'Proud',
|
||||
'Secret',
|
||||
'Lucky',
|
||||
'Sad',
|
||||
'Silent',
|
||||
'Latter',
|
||||
'Severe',
|
||||
'Fat',
|
||||
'Holy',
|
||||
'Pure',
|
||||
'Aggressive',
|
||||
'Honest',
|
||||
'Giant',
|
||||
'Mad',
|
||||
'Pregnant',
|
||||
'Distant',
|
||||
'Lost',
|
||||
'Broken',
|
||||
'Blind',
|
||||
'Friendly',
|
||||
'Unknown',
|
||||
'Sleeping',
|
||||
'Slumbering',
|
||||
'Loud',
|
||||
'Hungry',
|
||||
'Wise',
|
||||
'Worried',
|
||||
'Sacred',
|
||||
'Magical',
|
||||
'Superior',
|
||||
'Patient',
|
||||
'Dead',
|
||||
'Deadly',
|
||||
'Peaceful',
|
||||
'Grateful',
|
||||
'Frozen',
|
||||
'Evil',
|
||||
'Scary',
|
||||
'Burning',
|
||||
'Divine',
|
||||
'Bloody',
|
||||
'Dying',
|
||||
'Waking',
|
||||
'Brutal',
|
||||
'Unhappy',
|
||||
'Calm',
|
||||
'Cruel',
|
||||
'Favorable',
|
||||
'Blond',
|
||||
'Explicit',
|
||||
'Disturbing',
|
||||
'Devastating',
|
||||
'Brave',
|
||||
'Sunny',
|
||||
'Troubled',
|
||||
'Flying',
|
||||
'Sustainable',
|
||||
'Marine',
|
||||
'Fatal',
|
||||
'Inherent',
|
||||
'Selected',
|
||||
'Naval',
|
||||
'Cheerful',
|
||||
'Almighty',
|
||||
'Benevolent',
|
||||
'Eternal',
|
||||
'Immutable',
|
||||
'Infallible'
|
||||
],
|
||||
genitive: [
|
||||
'Day',
|
||||
'Life',
|
||||
'Death',
|
||||
'Night',
|
||||
'Home',
|
||||
'Fog',
|
||||
'Snow',
|
||||
'Winter',
|
||||
'Summer',
|
||||
'Cold',
|
||||
'Springs',
|
||||
'Gates',
|
||||
'Nature',
|
||||
'Thunder',
|
||||
'Lightning',
|
||||
'War',
|
||||
'Ice',
|
||||
'Frost',
|
||||
'Fire',
|
||||
'Doom',
|
||||
'Fate',
|
||||
'Pain',
|
||||
'Heaven',
|
||||
'Justice',
|
||||
'Light',
|
||||
'Love',
|
||||
'Time',
|
||||
'Victory'
|
||||
],
|
||||
theGenitive: [
|
||||
'World',
|
||||
'Word',
|
||||
'South',
|
||||
'West',
|
||||
'North',
|
||||
'East',
|
||||
'Sun',
|
||||
'Moon',
|
||||
'Peak',
|
||||
'Fall',
|
||||
'Dawn',
|
||||
'Eclipse',
|
||||
'Abyss',
|
||||
'Blood',
|
||||
'Tree',
|
||||
'Earth',
|
||||
'Harvest',
|
||||
'Rainbow',
|
||||
'Sea',
|
||||
'Sky',
|
||||
'Stars',
|
||||
'Storm',
|
||||
'Underworld',
|
||||
'Wild'
|
||||
],
|
||||
color: ['Dark', 'Light', 'Bright', 'Golden', 'White', 'Black', 'Red', 'Pink', 'Purple', 'Blue', 'Green', 'Yellow', 'Amber', 'Orange', 'Brown', 'Grey']
|
||||
=======
|
||||
number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"],
|
||||
being: [
|
||||
"God",
|
||||
"Goddess",
|
||||
"Lord",
|
||||
"Lady",
|
||||
"Deity",
|
||||
"Creator",
|
||||
"Maker",
|
||||
"Overlord",
|
||||
"Ruler",
|
||||
"Chief",
|
||||
"Master",
|
||||
"Spirit",
|
||||
"Ancestor",
|
||||
"Father",
|
||||
"Forebear",
|
||||
"Forefather",
|
||||
"Mother",
|
||||
"Brother",
|
||||
"Sister",
|
||||
"Elder",
|
||||
"Numen",
|
||||
"Ancient",
|
||||
"Virgin",
|
||||
"Giver",
|
||||
"Council",
|
||||
"Guardian",
|
||||
"Reaper"
|
||||
],
|
||||
animal: [
|
||||
"Dragon",
|
||||
"Wyvern",
|
||||
"Phoenix",
|
||||
"Unicorn",
|
||||
"Sphinx",
|
||||
"Centaur",
|
||||
"Pegasus",
|
||||
"Kraken",
|
||||
"Basilisk",
|
||||
"Chimera",
|
||||
"Cyclope",
|
||||
"Antelope",
|
||||
"Ape",
|
||||
"Badger",
|
||||
"Bear",
|
||||
"Beaver",
|
||||
"Bison",
|
||||
"Boar",
|
||||
"Buffalo",
|
||||
"Cat",
|
||||
"Cobra",
|
||||
"Crane",
|
||||
"Crocodile",
|
||||
"Crow",
|
||||
"Deer",
|
||||
"Dog",
|
||||
"Eagle",
|
||||
"Elk",
|
||||
"Fox",
|
||||
"Goat",
|
||||
"Goose",
|
||||
"Hare",
|
||||
"Hawk",
|
||||
"Heron",
|
||||
"Horse",
|
||||
"Hyena",
|
||||
"Ibis",
|
||||
"Jackal",
|
||||
"Jaguar",
|
||||
"Lark",
|
||||
"Leopard",
|
||||
"Lion",
|
||||
"Mantis",
|
||||
"Marten",
|
||||
"Moose",
|
||||
"Mule",
|
||||
"Narwhal",
|
||||
"Owl",
|
||||
"Panther",
|
||||
"Rat",
|
||||
"Raven",
|
||||
"Rook",
|
||||
"Scorpion",
|
||||
"Shark",
|
||||
"Sheep",
|
||||
"Snake",
|
||||
"Spider",
|
||||
"Swan",
|
||||
"Tiger",
|
||||
"Turtle",
|
||||
"Viper",
|
||||
"Vulture",
|
||||
"Walrus",
|
||||
"Wolf",
|
||||
"Wolverine",
|
||||
"Worm",
|
||||
"Camel",
|
||||
"Falcon",
|
||||
"Hound",
|
||||
"Ox",
|
||||
"Serpent"
|
||||
],
|
||||
adjective: [
|
||||
"New",
|
||||
"Good",
|
||||
"High",
|
||||
"Old",
|
||||
"Great",
|
||||
"Big",
|
||||
"Young",
|
||||
"Major",
|
||||
"Strong",
|
||||
"Happy",
|
||||
"Last",
|
||||
"Main",
|
||||
"Huge",
|
||||
"Far",
|
||||
"Beautiful",
|
||||
"Wild",
|
||||
"Fair",
|
||||
"Prime",
|
||||
"Crazy",
|
||||
"Ancient",
|
||||
"Proud",
|
||||
"Secret",
|
||||
"Lucky",
|
||||
"Sad",
|
||||
"Silent",
|
||||
"Latter",
|
||||
"Severe",
|
||||
"Fat",
|
||||
"Holy",
|
||||
"Pure",
|
||||
"Aggressive",
|
||||
"Honest",
|
||||
"Giant",
|
||||
"Mad",
|
||||
"Pregnant",
|
||||
"Distant",
|
||||
"Lost",
|
||||
"Broken",
|
||||
"Blind",
|
||||
"Friendly",
|
||||
"Unknown",
|
||||
"Sleeping",
|
||||
"Slumbering",
|
||||
"Loud",
|
||||
"Hungry",
|
||||
"Wise",
|
||||
"Worried",
|
||||
"Sacred",
|
||||
"Magical",
|
||||
"Superior",
|
||||
"Patient",
|
||||
"Dead",
|
||||
"Deadly",
|
||||
"Peaceful",
|
||||
"Grateful",
|
||||
"Frozen",
|
||||
"Evil",
|
||||
"Scary",
|
||||
"Burning",
|
||||
"Divine",
|
||||
"Bloody",
|
||||
"Dying",
|
||||
"Waking",
|
||||
"Brutal",
|
||||
"Unhappy",
|
||||
"Calm",
|
||||
"Cruel",
|
||||
"Favorable",
|
||||
"Blond",
|
||||
"Explicit",
|
||||
"Disturbing",
|
||||
"Devastating",
|
||||
"Brave",
|
||||
"Sunny",
|
||||
"Troubled",
|
||||
"Flying",
|
||||
"Sustainable",
|
||||
"Marine",
|
||||
"Fatal",
|
||||
"Inherent",
|
||||
"Selected",
|
||||
"Naval",
|
||||
"Cheerful",
|
||||
"Almighty",
|
||||
"Benevolent",
|
||||
"Eternal",
|
||||
"Immutable",
|
||||
"Infallible"
|
||||
],
|
||||
genitive: [
|
||||
"Day",
|
||||
"Life",
|
||||
"Death",
|
||||
"Night",
|
||||
"Home",
|
||||
"Fog",
|
||||
"Snow",
|
||||
"Winter",
|
||||
"Summer",
|
||||
"Cold",
|
||||
"Springs",
|
||||
"Gates",
|
||||
"Nature",
|
||||
"Thunder",
|
||||
"Lightning",
|
||||
"War",
|
||||
"Ice",
|
||||
"Frost",
|
||||
"Fire",
|
||||
"Doom",
|
||||
"Fate",
|
||||
"Pain",
|
||||
"Heaven",
|
||||
"Justice",
|
||||
"Light",
|
||||
"Love",
|
||||
"Time",
|
||||
"Victory"
|
||||
],
|
||||
theGenitive: [
|
||||
"World",
|
||||
"Word",
|
||||
"South",
|
||||
"West",
|
||||
"North",
|
||||
"East",
|
||||
"Sun",
|
||||
"Moon",
|
||||
"Peak",
|
||||
"Fall",
|
||||
"Dawn",
|
||||
"Eclipse",
|
||||
"Abyss",
|
||||
"Blood",
|
||||
"Tree",
|
||||
"Earth",
|
||||
"Harvest",
|
||||
"Rainbow",
|
||||
"Sea",
|
||||
"Sky",
|
||||
"Stars",
|
||||
"Storm",
|
||||
"Underworld",
|
||||
"Wild"
|
||||
],
|
||||
color: ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]
|
||||
>>>>>>> master
|
||||
};
|
||||
|
||||
const forms = {
|
||||
Folk: {Shamanism: 2, Animism: 2, 'Ancestor worship': 1, Polytheism: 2},
|
||||
Organized: {Polytheism: 5, Dualism: 1, Monotheism: 4, 'Non-theism': 1},
|
||||
Cult: {Cult: 1, 'Dark Cult': 1},
|
||||
Heresy: {Heresy: 1}
|
||||
};
|
||||
|
||||
<<<<<<< HEAD
|
||||
const methods = {'Random + type': 3, 'Random + ism': 1, 'Supreme + ism': 5, 'Faith of + Supreme': 5, 'Place + ism': 1, 'Culture + ism': 2, 'Place + ian + type': 6, 'Culture + type': 4};
|
||||
=======
|
||||
const methods = {
|
||||
"Random + type": 3,
|
||||
"Random + ism": 1,
|
||||
"Supreme + ism": 5,
|
||||
"Faith of + Supreme": 5,
|
||||
"Place + ism": 1,
|
||||
"Culture + ism": 2,
|
||||
"Place + ian + type": 6,
|
||||
"Culture + type": 4
|
||||
};
|
||||
>>>>>>> master
|
||||
|
||||
const types = {
|
||||
Shamanism: {Beliefs: 3, Shamanism: 2, Spirits: 1},
|
||||
Animism: {Spirits: 1, Beliefs: 1},
|
||||
'Ancestor worship': {Beliefs: 1, Forefathers: 2, Ancestors: 2},
|
||||
Polytheism: {Deities: 3, Faith: 1, Gods: 1, Pantheon: 1},
|
||||
|
||||
Dualism: {Religion: 3, Faith: 1, Cult: 1},
|
||||
Monotheism: {Religion: 1, Church: 1},
|
||||
'Non-theism': {Beliefs: 3, Spirits: 1},
|
||||
|
||||
Cult: {Cult: 4, Sect: 4, Worship: 1, Orden: 1, Coterie: 1, Arcanum: 1},
|
||||
'Dark Cult': {Cult: 2, Sect: 2, Occultism: 1, Idols: 1, Coven: 1, Circle: 1, Blasphemy: 1},
|
||||
|
||||
Heresy: {Heresy: 3, Sect: 2, Schism: 1, Dissenters: 1, Circle: 1, Brotherhood: 1, Society: 1, Iconoclasm: 1, Dissent: 1, Apostates: 1}
|
||||
};
|
||||
|
||||
const generate = function () {
|
||||
TIME && console.time('generateReligions');
|
||||
const cells = pack.cells,
|
||||
states = pack.states,
|
||||
cultures = pack.cultures;
|
||||
const religions = (pack.religions = []);
|
||||
cells.religion = new Uint16Array(cells.culture); // cell religion; initially based on culture
|
||||
|
||||
// add folk religions
|
||||
pack.cultures.forEach((c) => {
|
||||
if (!c.i) {
|
||||
religions.push({i: 0, name: 'No religion'});
|
||||
return;
|
||||
}
|
||||
if (c.removed) {
|
||||
religions.push({i: c.i, name: 'Extinct religion for ' + c.name, color: getMixedColor(c.color, 0.1, 0), removed: true});
|
||||
return;
|
||||
}
|
||||
const form = rw(forms.Folk);
|
||||
const name = c.name + ' ' + rw(types[form]);
|
||||
const deity = form === 'Animism' ? null : getDeityName(c.i);
|
||||
const color = getMixedColor(c.color, 0.1, 0); // `url(#hatch${rand(8,13)})`;
|
||||
religions.push({i: c.i, name, color, culture: c.i, type: 'Folk', form, deity, center: c.center, origin: 0});
|
||||
});
|
||||
|
||||
if (religionsInput.value == 0 || pack.cultures.length < 2) {
|
||||
religions.filter((r) => r.i).forEach((r) => (r.code = abbreviate(r.name)));
|
||||
return;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
const sorted =
|
||||
burgs.length > +religionsInput.value ? burgs.sort((a, b) => b.population - a.population).map((b) => b.cell) : cells.i.filter((i) => cells.s[i] > 2).sort((a, b) => cells.s[b] - cells.s[a]);
|
||||
=======
|
||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
const sorted =
|
||||
burgs.length > +religionsInput.value
|
||||
? burgs.sort((a, b) => b.population - a.population).map(b => b.cell)
|
||||
: cells.i.filter(i => cells.s[i] > 2).sort((a, b) => cells.s[b] - cells.s[a]);
|
||||
>>>>>>> master
|
||||
const religionsTree = d3.quadtree();
|
||||
const spacing = (graphWidth + graphHeight) / 6 / religionsInput.value; // base min distance between towns
|
||||
const cultsCount = Math.floor((rand(10, 40) / 100) * religionsInput.value);
|
||||
const count = +religionsInput.value - cultsCount + religions.length;
|
||||
|
||||
// generate organized religions
|
||||
for (let i = 0; religions.length < count && i < 1000; i++) {
|
||||
let center = sorted[biased(0, sorted.length - 1, 5)]; // religion center
|
||||
const form = rw(forms.Organized);
|
||||
const state = cells.state[center];
|
||||
const culture = cells.culture[center];
|
||||
|
||||
const deity = form === 'Non-theism' ? null : getDeityName(culture);
|
||||
let [name, expansion] = getReligionName(form, deity, center);
|
||||
if (expansion === 'state' && !state) expansion = 'global';
|
||||
if (expansion === 'culture' && !culture) expansion = 'global';
|
||||
|
||||
if (expansion === 'state' && Math.random() > 0.5) center = states[state].center;
|
||||
if (expansion === 'culture' && Math.random() > 0.5) center = cultures[culture].center;
|
||||
|
||||
if (!cells.burg[center] && cells.c[center].some((c) => cells.burg[c])) center = cells.c[center].find((c) => cells.burg[c]);
|
||||
const x = cells.p[center][0],
|
||||
y = cells.p[center][1];
|
||||
|
||||
const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform
|
||||
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
|
||||
|
||||
// add "Old" to name of the folk religion on this culture
|
||||
const folk = religions.find((r) => r.culture === culture && r.type === 'Folk');
|
||||
if (folk && expansion === 'culture' && folk.name.slice(0, 3) !== 'Old') folk.name = 'Old ' + folk.name;
|
||||
const origin = folk ? folk.i : 0;
|
||||
|
||||
const expansionism = rand(3, 8);
|
||||
const color = getMixedColor(religions[origin].color, 0.3, 0); // `url(#hatch${rand(0,5)})`;
|
||||
religions.push({i: religions.length, name, color, culture, type: 'Organized', form, deity, expansion, expansionism, center, origin});
|
||||
religionsTree.add([x, y]);
|
||||
}
|
||||
|
||||
// generate cults
|
||||
for (let i = 0; religions.length < count + cultsCount && i < 1000; i++) {
|
||||
const form = rw(forms.Cult);
|
||||
let center = sorted[biased(0, sorted.length - 1, 1)]; // religion center
|
||||
if (!cells.burg[center] && cells.c[center].some((c) => cells.burg[c])) center = cells.c[center].find((c) => cells.burg[c]);
|
||||
const x = cells.p[center][0],
|
||||
y = cells.p[center][1];
|
||||
|
||||
const s = spacing * gauss(2, 0.3, 1, 3, 2); // randomize to make the placement not uniform
|
||||
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
|
||||
|
||||
const culture = cells.culture[center];
|
||||
const folk = religions.find((r) => r.culture === culture && r.type === 'Folk');
|
||||
const origin = folk ? folk.i : 0;
|
||||
const deity = getDeityName(culture);
|
||||
const name = getCultName(form, center);
|
||||
const expansionism = gauss(1.1, 0.5, 0, 5);
|
||||
const color = getMixedColor(cultures[culture].color, 0.5, 0); // "url(#hatch7)";
|
||||
religions.push({i: religions.length, name, color, culture, type: 'Cult', form, deity, expansion: 'global', expansionism, center, origin});
|
||||
religionsTree.add([x, y]);
|
||||
//debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).attr("fill", "red");
|
||||
}
|
||||
|
||||
expandReligions();
|
||||
|
||||
// generate heresies
|
||||
religions
|
||||
.filter((r) => r.type === 'Organized')
|
||||
.forEach((r) => {
|
||||
if (r.expansionism < 3) return;
|
||||
const count = gauss(0, 1, 0, 3);
|
||||
for (let i = 0; i < count; i++) {
|
||||
let center = ra(cells.i.filter((i) => cells.religion[i] === r.i && cells.c[i].some((c) => cells.religion[c] !== r.i)));
|
||||
if (!center) continue;
|
||||
if (!cells.burg[center] && cells.c[center].some((c) => cells.burg[c])) center = cells.c[center].find((c) => cells.burg[c]);
|
||||
const x = cells.p[center][0],
|
||||
y = cells.p[center][1];
|
||||
if (religionsTree.find(x, y, spacing / 10) !== undefined) continue; // to close to other
|
||||
|
||||
const culture = cells.culture[center];
|
||||
const name = getCultName('Heresy', center);
|
||||
const expansionism = gauss(1.2, 0.5, 0, 5);
|
||||
const color = getMixedColor(r.color, 0.4, 0.2); // "url(#hatch6)";
|
||||
<<<<<<< HEAD
|
||||
religions.push({i: religions.length, name, color, culture, type: 'Heresy', form: r.form, deity: r.deity, expansion: 'global', expansionism, center, origin: r.i});
|
||||
=======
|
||||
religions.push({
|
||||
i: religions.length,
|
||||
name,
|
||||
color,
|
||||
culture,
|
||||
type: "Heresy",
|
||||
form: r.form,
|
||||
deity: r.deity,
|
||||
expansion: "global",
|
||||
expansionism,
|
||||
center,
|
||||
origin: r.i
|
||||
});
|
||||
>>>>>>> master
|
||||
religionsTree.add([x, y]);
|
||||
}
|
||||
});
|
||||
|
||||
expandHeresies();
|
||||
checkCenters();
|
||||
|
||||
TIME && console.timeEnd('generateReligions');
|
||||
};
|
||||
|
||||
const add = function (center) {
|
||||
const cells = pack.cells,
|
||||
religions = pack.religions;
|
||||
const r = cells.religion[center];
|
||||
const i = religions.length;
|
||||
const culture = cells.culture[center];
|
||||
const color = getMixedColor(religions[r].color, 0.3, 0);
|
||||
|
||||
const type = religions[r].type === 'Organized' ? rw({Organized: 4, Cult: 1, Heresy: 2}) : rw({Organized: 5, Cult: 2});
|
||||
const form = rw(forms[type]);
|
||||
const deity = type === 'Heresy' ? religions[r].deity : form === 'Non-theism' ? null : getDeityName(culture);
|
||||
|
||||
let name, expansion;
|
||||
if (type === 'Organized') [name, expansion] = getReligionName(form, deity, center);
|
||||
else {
|
||||
name = getCultName(form, center);
|
||||
expansion = 'global';
|
||||
}
|
||||
const formName = type === 'Heresy' ? religions[r].form : form;
|
||||
const code = abbreviate(
|
||||
name,
|
||||
religions.map((r) => r.code)
|
||||
);
|
||||
religions.push({
|
||||
i,
|
||||
name,
|
||||
color,
|
||||
culture,
|
||||
type,
|
||||
form: formName,
|
||||
deity,
|
||||
expansion,
|
||||
expansionism: 0,
|
||||
center,
|
||||
cells: 0,
|
||||
area: 0,
|
||||
rural: 0,
|
||||
urban: 0,
|
||||
origin: r,
|
||||
code
|
||||
});
|
||||
cells.religion[center] = i;
|
||||
};
|
||||
|
||||
// growth algorithm to assign cells to religions
|
||||
const expandReligions = function () {
|
||||
const cells = pack.cells,
|
||||
religions = pack.religions;
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
const cost = [];
|
||||
|
||||
religions
|
||||
.filter((r) => r.type === 'Organized' || r.type === 'Cult')
|
||||
.forEach((r) => {
|
||||
cells.religion[r.center] = r.i;
|
||||
queue.queue({e: r.center, p: 0, r: r.i, s: cells.state[r.center], c: r.culture});
|
||||
cost[r.center] = 1;
|
||||
});
|
||||
|
||||
const neutral = (cells.i.length / 5000) * 200 * gauss(1, 0.3, 0.2, 2, 2) * neutralInput.value; // limit cost for organized religions growth
|
||||
const popCost = d3.max(cells.pop) / 3; // enougth population to spered religion without penalty
|
||||
|
||||
while (queue.length) {
|
||||
const next = queue.dequeue(),
|
||||
n = next.e,
|
||||
p = next.p,
|
||||
r = next.r,
|
||||
c = next.c,
|
||||
s = next.s;
|
||||
const expansion = religions[r].expansion;
|
||||
|
||||
cells.c[n].forEach(function (e) {
|
||||
if (expansion === 'culture' && c !== cells.culture[e]) return;
|
||||
if (expansion === 'state' && s !== cells.state[e]) return;
|
||||
|
||||
const cultureCost = c !== cells.culture[e] ? 10 : 0;
|
||||
const stateCost = s !== cells.state[e] ? 10 : 0;
|
||||
const biomeCost = biomesData.cost[cells.biome[e]];
|
||||
const populationCost = Math.max(rn(popCost - cells.pop[e]), 0);
|
||||
const heightCost = Math.max(cells.h[e], 20) - 20;
|
||||
const waterCost = cells.h[e] < 20 ? 500 : 0;
|
||||
const totalCost = p + (cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism;
|
||||
if (totalCost > neutral) return;
|
||||
|
||||
if (!cost[e] || totalCost < cost[e]) {
|
||||
if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell
|
||||
cost[e] = totalCost;
|
||||
queue.queue({e, p: totalCost, r, c, s});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// growth algorithm to assign cells to heresies
|
||||
const expandHeresies = function () {
|
||||
const cells = pack.cells,
|
||||
religions = pack.religions;
|
||||
const queue = new PriorityQueue({comparator: (a, b) => a.p - b.p});
|
||||
const cost = [];
|
||||
|
||||
religions
|
||||
.filter((r) => r.type === 'Heresy')
|
||||
.forEach((r) => {
|
||||
const b = cells.religion[r.center]; // "base" religion id
|
||||
cells.religion[r.center] = r.i; // heresy id
|
||||
queue.queue({e: r.center, p: 0, r: r.i, b});
|
||||
cost[r.center] = 1;
|
||||
});
|
||||
|
||||
const neutral = (cells.i.length / 5000) * 500 * neutralInput.value; // limit cost for heresies growth
|
||||
|
||||
while (queue.length) {
|
||||
const next = queue.dequeue(),
|
||||
n = next.e,
|
||||
p = next.p,
|
||||
r = next.r,
|
||||
b = next.b;
|
||||
|
||||
cells.c[n].forEach(function (e) {
|
||||
const religionCost = cells.religion[e] === b ? 0 : 2000;
|
||||
const biomeCost = biomesData.cost[cells.biome[e]];
|
||||
const heightCost = Math.max(cells.h[e], 20) - 20;
|
||||
const waterCost = cells.h[e] < 20 ? 500 : 0;
|
||||
const totalCost = p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, 0.1);
|
||||
|
||||
if (totalCost > neutral) return;
|
||||
|
||||
if (!cost[e] || totalCost < cost[e]) {
|
||||
if (cells.h[e] >= 20 && cells.culture[e]) cells.religion[e] = r; // assign religion to cell
|
||||
cost[e] = totalCost;
|
||||
queue.queue({e, p: totalCost, r});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function checkCenters() {
|
||||
const {cells, religions} = pack;
|
||||
|
||||
<<<<<<< HEAD
|
||||
const codes = religions.map((r) => r.code);
|
||||
religions
|
||||
.filter((r) => r.i)
|
||||
.forEach((r) => {
|
||||
r.code = abbreviate(r.name, codes);
|
||||
|
||||
// move religion center if it's not within religion area after expansion
|
||||
if (cells.religion[r.center] === r.i) return; // in area
|
||||
const religCells = cells.i.filter((i) => cells.religion[i] === r.i);
|
||||
if (!religCells.length) return; // extinct religion
|
||||
r.center = religCells.sort((a, b) => b.pop - a.pop)[0];
|
||||
});
|
||||
=======
|
||||
const codes = religions.map(r => r.code);
|
||||
religions.forEach(r => {
|
||||
if (!r.i) return;
|
||||
r.code = abbreviate(r.name, codes);
|
||||
|
||||
// move religion center if it's not within religion area after expansion
|
||||
if (cells.religion[r.center] === r.i) return; // in area
|
||||
const religCells = cells.i.filter(i => cells.religion[i] === r.i);
|
||||
if (!religCells.length) return; // extinct religion
|
||||
r.center = religCells.sort((a, b) => cells.pop[b] - cells.pop[a])[0];
|
||||
});
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function updateCultures() {
|
||||
TIME && console.time('updateCulturesForReligions');
|
||||
pack.religions = pack.religions.map((religion, index) => {
|
||||
if (index === 0) {
|
||||
return religion;
|
||||
}
|
||||
return {...religion, culture: pack.cells.culture[religion.center]};
|
||||
});
|
||||
TIME && console.timeEnd('updateCulturesForReligions');
|
||||
}
|
||||
|
||||
// get supreme deity name
|
||||
const getDeityName = function (culture) {
|
||||
if (culture === undefined) {
|
||||
ERROR && console.error('Please define a culture');
|
||||
return;
|
||||
}
|
||||
const meaning = generateMeaning();
|
||||
const cultureName = Names.getCulture(culture, null, null, '', 0.8);
|
||||
return cultureName + ', The ' + meaning;
|
||||
};
|
||||
|
||||
function generateMeaning() {
|
||||
const a = ra(approaches); // select generation approach
|
||||
if (a === 'Number') return ra(base.number);
|
||||
if (a === 'Being') return ra(base.being);
|
||||
if (a === 'Adjective') return ra(base.adjective);
|
||||
if (a === 'Color + Animal') return ra(base.color) + ' ' + ra(base.animal);
|
||||
if (a === 'Adjective + Animal') return ra(base.adjective) + ' ' + ra(base.animal);
|
||||
if (a === 'Adjective + Being') return ra(base.adjective) + ' ' + ra(base.being);
|
||||
if (a === 'Adjective + Genitive') return ra(base.adjective) + ' ' + ra(base.genitive);
|
||||
if (a === 'Color + Being') return ra(base.color) + ' ' + ra(base.being);
|
||||
if (a === 'Color + Genitive') return ra(base.color) + ' ' + ra(base.genitive);
|
||||
if (a === 'Being + of + Genitive') return ra(base.being) + ' of ' + ra(base.genitive);
|
||||
if (a === 'Being + of the + Genitive') return ra(base.being) + ' of the ' + ra(base.theGenitive);
|
||||
if (a === 'Animal + of + Genitive') return ra(base.animal) + ' of ' + ra(base.genitive);
|
||||
if (a === 'Adjective + Being + of + Genitive') return ra(base.adjective) + ' ' + ra(base.being) + ' of ' + ra(base.genitive);
|
||||
if (a === 'Adjective + Animal + of + Genitive') return ra(base.adjective) + ' ' + ra(base.animal) + ' of ' + ra(base.genitive);
|
||||
}
|
||||
|
||||
function getReligionName(form, deity, center) {
|
||||
const cells = pack.cells;
|
||||
const random = function () {
|
||||
return Names.getCulture(cells.culture[center], null, null, '', 0);
|
||||
};
|
||||
const type = function () {
|
||||
return rw(types[form]);
|
||||
};
|
||||
const supreme = function () {
|
||||
return deity.split(/[ ,]+/)[0];
|
||||
};
|
||||
const place = function (adj) {
|
||||
const base = cells.burg[center] ? pack.burgs[cells.burg[center]].name : pack.states[cells.state[center]].name;
|
||||
let name = trimVowels(base.split(/[ ,]+/)[0]);
|
||||
return adj ? getAdjective(name) : name;
|
||||
};
|
||||
const culture = function () {
|
||||
return pack.cultures[cells.culture[center]].name;
|
||||
};
|
||||
|
||||
const m = rw(methods);
|
||||
if (m === 'Random + type') return [random() + ' ' + type(), 'global'];
|
||||
if (m === 'Random + ism') return [trimVowels(random()) + 'ism', 'global'];
|
||||
if (m === 'Supreme + ism' && deity) return [trimVowels(supreme()) + 'ism', 'global'];
|
||||
if (m === 'Faith of + Supreme' && deity) return [ra(['Faith', 'Way', 'Path', 'Word', 'Witnesses']) + ' of ' + supreme(), 'global'];
|
||||
if (m === 'Place + ism') return [place() + 'ism', 'state'];
|
||||
if (m === 'Culture + ism') return [trimVowels(culture()) + 'ism', 'culture'];
|
||||
if (m === 'Place + ian + type') return [place('adj') + ' ' + type(), 'state'];
|
||||
if (m === 'Culture + type') return [culture() + ' ' + type(), 'culture'];
|
||||
return [trimVowels(random()) + 'ism', 'global']; // else
|
||||
}
|
||||
|
||||
function getCultName(form, center) {
|
||||
const cells = pack.cells;
|
||||
const type = function () {
|
||||
return rw(types[form]);
|
||||
};
|
||||
const random = function () {
|
||||
return trimVowels(Names.getCulture(cells.culture[center], null, null, '', 0).split(/[ ,]+/)[0]);
|
||||
};
|
||||
const burg = function () {
|
||||
return trimVowels(pack.burgs[cells.burg[center]].name.split(/[ ,]+/)[0]);
|
||||
};
|
||||
if (cells.burg[center]) return burg() + 'ian ' + type();
|
||||
if (Math.random() > 0.5) return random() + 'ian ' + type();
|
||||
return type() + ' of the ' + generateMeaning();
|
||||
}
|
||||
|
||||
return {generate, add, getDeityName, expandReligions, updateCultures};
|
||||
})();
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
window.Rivers = (function () {
|
||||
const generate = function (allowErosion = true) {
|
||||
TIME && console.time("generateRivers");
|
||||
TIME && console.time('generateRivers');
|
||||
Math.random = aleaPRNG(seed);
|
||||
const {cells, features} = pack;
|
||||
|
||||
|
|
@ -28,26 +28,27 @@ window.Rivers = (function () {
|
|||
|
||||
if (allowErosion) cells.h = Uint8Array.from(h); // apply changed heights as basic one
|
||||
|
||||
TIME && console.timeEnd("generateRivers");
|
||||
TIME && console.timeEnd('generateRivers');
|
||||
|
||||
function drainWater() {
|
||||
const MIN_FLUX_TO_FORM_RIVER = 30;
|
||||
const prec = grid.cells.prec;
|
||||
const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]);
|
||||
const area = pack.cells.area;
|
||||
const land = cells.i.filter((i) => h[i] >= 20).sort((a, b) => h[b] - h[a]);
|
||||
const lakeOutCells = Lakes.setClimateData(h);
|
||||
|
||||
land.forEach(function (i) {
|
||||
cells.fl[i] += prec[cells.g[i]]; // add flux from precipitation
|
||||
cells.fl[i] += (prec[cells.g[i]] * area[i]) / 100; // add flux from precipitation
|
||||
|
||||
// create lake outlet if lake is not in deep depression and flux > evaporation
|
||||
const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
|
||||
const lakes = lakeOutCells[i] ? features.filter((feature) => i === feature.outCell && feature.flux > feature.evaporation) : [];
|
||||
for (const lake of lakes) {
|
||||
const lakeCell = cells.c[i].find(c => h[c] < 20 && cells.f[c] === lake.i);
|
||||
const lakeCell = cells.c[i].find((c) => h[c] < 20 && cells.f[c] === lake.i);
|
||||
cells.fl[lakeCell] += Math.max(lake.flux - lake.evaporation, 0); // not evaporated lake water drains to outlet
|
||||
|
||||
// allow chain lakes to retain identity
|
||||
if (cells.r[lakeCell] !== lake.river) {
|
||||
const sameRiver = cells.c[lakeCell].some(c => cells.r[c] === lake.river);
|
||||
const sameRiver = cells.c[lakeCell].some((c) => cells.r[c] === lake.river);
|
||||
|
||||
if (sameRiver) {
|
||||
cells.r[lakeCell] = lake.river;
|
||||
|
|
@ -78,7 +79,7 @@ window.Rivers = (function () {
|
|||
// downhill cell (make sure it's not in the source lake)
|
||||
let min = null;
|
||||
if (lakeOutCells[i]) {
|
||||
const filtered = cells.c[i].filter(c => !lakes.map(lake => lake.i).includes(cells.f[c]));
|
||||
const filtered = cells.c[i].filter((c) => !lakes.map((lake) => lake.i).includes(cells.f[c]));
|
||||
min = filtered.sort((a, b) => h[a] - h[b])[0];
|
||||
} else if (cells.haven[i]) {
|
||||
min = cells.haven[i];
|
||||
|
|
@ -125,7 +126,7 @@ window.Rivers = (function () {
|
|||
if (h[toCell] < 20) {
|
||||
// pour water to the water body
|
||||
const waterBody = features[cells.f[toCell]];
|
||||
if (waterBody.type === "lake") {
|
||||
if (waterBody.type === 'lake') {
|
||||
if (!waterBody.river || fromFlux > waterBody.enteringFlux) {
|
||||
waterBody.river = river;
|
||||
waterBody.enteringFlux = fromFlux;
|
||||
|
|
@ -168,7 +169,7 @@ window.Rivers = (function () {
|
|||
const widthFactor = !parent || parent === riverId ? 1.2 : 1;
|
||||
const meanderedPoints = addMeandering(riverCells);
|
||||
const discharge = cells.fl[mouth]; // m3 in second
|
||||
const length = rn(getApproximateLength(meanderedPoints), 2);
|
||||
const length = getApproximateLength(meanderedPoints);
|
||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, 0));
|
||||
|
||||
pack.rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells});
|
||||
|
|
@ -180,8 +181,8 @@ window.Rivers = (function () {
|
|||
if (!cells.conf[i]) continue;
|
||||
|
||||
const sortedInflux = cells.c[i]
|
||||
.filter(c => cells.r[c] && h[c] > h[i])
|
||||
.map(c => cells.fl[c])
|
||||
.filter((c) => cells.r[c] && h[c] > h[i])
|
||||
.map((c) => cells.fl[c])
|
||||
.sort((a, b) => b - a);
|
||||
cells.conf[i] = sortedInflux.reduce((acc, flux, index) => (index ? acc + flux : acc), 0);
|
||||
}
|
||||
|
|
@ -193,21 +194,21 @@ window.Rivers = (function () {
|
|||
const {h, c, t} = pack.cells;
|
||||
return Array.from(h).map((h, i) => {
|
||||
if (h < 20 || t[i] < 1) return h;
|
||||
return h + t[i] / 100 + d3.mean(c[i].map(c => t[c])) / 10000;
|
||||
return h + t[i] / 100 + d3.mean(c[i].map((c) => t[c])) / 10000;
|
||||
});
|
||||
};
|
||||
|
||||
// depression filling algorithm (for a correct water flux modeling)
|
||||
const resolveDepressions = function (h) {
|
||||
const {cells, features} = pack;
|
||||
const maxIterations = +document.getElementById("resolveDepressionsStepsOutput").value;
|
||||
const maxIterations = +document.getElementById('resolveDepressionsStepsOutput').value;
|
||||
const checkLakeMaxIteration = maxIterations * 0.85;
|
||||
const elevateLakeMaxIteration = maxIterations * 0.75;
|
||||
|
||||
const height = i => features[cells.f[i]].height || h[i]; // height of lake or specific cell
|
||||
const height = (i) => features[cells.f[i]].height || h[i]; // height of lake or specific cell
|
||||
|
||||
const lakes = features.filter(f => f.type === "lake");
|
||||
const land = cells.i.filter(i => h[i] >= 20 && !cells.b[i]); // exclude near-border cells
|
||||
const lakes = features.filter((f) => f.type === 'lake');
|
||||
const land = cells.i.filter((i) => h[i] >= 20 && !cells.b[i]); // exclude near-border cells
|
||||
land.sort((a, b) => h[a] - h[b]); // lowest cells go first
|
||||
|
||||
const progress = [];
|
||||
|
|
@ -226,12 +227,12 @@ window.Rivers = (function () {
|
|||
if (iteration < checkLakeMaxIteration) {
|
||||
for (const l of lakes) {
|
||||
if (l.closed) continue;
|
||||
const minHeight = d3.min(l.shoreline.map(s => h[s]));
|
||||
const minHeight = d3.min(l.shoreline.map((s) => h[s]));
|
||||
if (minHeight >= 100 || l.height > minHeight) continue;
|
||||
|
||||
if (iteration > elevateLakeMaxIteration) {
|
||||
l.shoreline.forEach(i => (h[i] = cells.h[i]));
|
||||
l.height = d3.min(l.shoreline.map(s => h[s])) - 1;
|
||||
l.shoreline.forEach((i) => (h[i] = cells.h[i]));
|
||||
l.height = d3.min(l.shoreline.map((s) => h[s])) - 1;
|
||||
l.closed = true;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -242,7 +243,7 @@ window.Rivers = (function () {
|
|||
}
|
||||
|
||||
for (const i of land) {
|
||||
const minHeight = d3.min(cells.c[i].map(c => height(c)));
|
||||
const minHeight = d3.min(cells.c[i].map((c) => height(c)));
|
||||
if (minHeight >= 100 || h[i] > minHeight) continue;
|
||||
|
||||
depressions++;
|
||||
|
|
@ -318,15 +319,16 @@ window.Rivers = (function () {
|
|||
};
|
||||
|
||||
const getRiverPoints = (riverCells, riverPoints) => {
|
||||
if (riverPoints) return riverPoints;
|
||||
|
||||
const {p} = pack.cells;
|
||||
return riverCells.map((cell, i) => {
|
||||
if (riverPoints && riverPoints[i]) return riverPoints[i];
|
||||
if (cell === -1) return getBorderPoint(riverCells[i - 1]);
|
||||
return p[cell];
|
||||
});
|
||||
};
|
||||
|
||||
const getBorderPoint = i => {
|
||||
const getBorderPoint = (i) => {
|
||||
const [x, y] = pack.cells.p[i];
|
||||
const min = Math.min(y, graphHeight - y, x, graphWidth - x);
|
||||
if (min === y) return [x, 0];
|
||||
|
|
@ -339,7 +341,7 @@ window.Rivers = (function () {
|
|||
const MAX_FLUX_WIDTH = 2;
|
||||
const LENGTH_FACTOR = 200;
|
||||
const STEP_WIDTH = 1 / LENGTH_FACTOR;
|
||||
const LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / LENGTH_FACTOR);
|
||||
const LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map((n) => n / LENGTH_FACTOR);
|
||||
const MAX_PROGRESSION = last(LENGTH_PROGRESSION);
|
||||
|
||||
const getOffset = (flux, pointNumber, widthFactor = 1, startingWidth = 0) => {
|
||||
|
|
@ -369,7 +371,7 @@ window.Rivers = (function () {
|
|||
|
||||
const right = lineGen(riverPointsRight.reverse());
|
||||
let left = lineGen(riverPointsLeft);
|
||||
left = left.substring(left.indexOf("C"));
|
||||
left = left.substring(left.indexOf('C'));
|
||||
|
||||
return round(right + left, 1);
|
||||
};
|
||||
|
|
@ -405,36 +407,39 @@ window.Rivers = (function () {
|
|||
const getType = function ({i, length, parent}) {
|
||||
if (smallLength === null) {
|
||||
const threshold = Math.ceil(pack.rivers.length * 0.15);
|
||||
smallLength = pack.rivers.map(r => r.length || 0).sort((a, b) => a - b)[threshold];
|
||||
smallLength = pack.rivers.map((r) => r.length || 0).sort((a, b) => a - b)[threshold];
|
||||
}
|
||||
|
||||
const isSmall = length < smallLength;
|
||||
const isFork = each(3)(i) && parent && parent !== i;
|
||||
return rw(riverTypes[isFork ? "fork" : "main"][isSmall ? "small" : "big"]);
|
||||
return rw(riverTypes[isFork ? 'fork' : 'main'][isSmall ? 'small' : 'big']);
|
||||
};
|
||||
|
||||
const getApproximateLength = points => points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0);
|
||||
const getApproximateLength = (points) => {
|
||||
const length = points.reduce((s, v, i, p) => s + (i ? Math.hypot(v[0] - p[i - 1][0], v[1] - p[i - 1][1]) : 0), 0);
|
||||
return rn(length, 2);
|
||||
};
|
||||
|
||||
// Real mouth width examples: Amazon 6000m, Volga 6000m, Dniepr 3000m, Mississippi 1300m, Themes 900m,
|
||||
// Danube 800m, Daugava 600m, Neva 500m, Nile 450m, Don 400m, Wisla 300m, Pripyat 150m, Bug 140m, Muchavets 40m
|
||||
const getWidth = offset => rn((offset / 1.5) ** 1.8, 2); // mouth width in km
|
||||
const getWidth = (offset) => rn((offset / 1.5) ** 1.8, 2); // mouth width in km
|
||||
|
||||
// remove river and all its tributaries
|
||||
const remove = function (id) {
|
||||
const cells = pack.cells;
|
||||
const riversToRemove = pack.rivers.filter(r => r.i === id || r.parent === id || r.basin === id).map(r => r.i);
|
||||
riversToRemove.forEach(r => rivers.select("#river" + r).remove());
|
||||
const riversToRemove = pack.rivers.filter((r) => r.i === id || r.parent === id || r.basin === 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));
|
||||
pack.rivers = pack.rivers.filter((r) => !riversToRemove.includes(r.i));
|
||||
};
|
||||
|
||||
const getBasin = function (r) {
|
||||
const parent = pack.rivers.find(river => river.i === r)?.parent;
|
||||
const parent = pack.rivers.find((river) => river.i === r)?.parent;
|
||||
if (!parent || r === parent) return r;
|
||||
return getBasin(parent);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,37 +1,37 @@
|
|||
window.Routes = (function () {
|
||||
const getRoads = function () {
|
||||
TIME && console.time("generateMainRoads");
|
||||
TIME && console.time('generateMainRoads');
|
||||
const cells = pack.cells;
|
||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
const capitals = burgs.filter(b => b.capital).sort((a, b) => a.population - b.population);
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
const capitals = burgs.filter((b) => b.capital).sort((a, b) => a.population - b.population);
|
||||
|
||||
if (capitals.length < 2) return []; // not enough capitals to build main roads
|
||||
const paths = []; // array to store path segments
|
||||
|
||||
for (const b of capitals) {
|
||||
const connect = capitals.filter(c => c.feature === b.feature && c !== b);
|
||||
const connect = capitals.filter((c) => c.feature === b.feature && c !== b);
|
||||
for (const t of connect) {
|
||||
const [from, exit] = findLandPath(b.cell, t.cell, true);
|
||||
const segments = restorePath(b.cell, exit, "main", from);
|
||||
segments.forEach(s => paths.push(s));
|
||||
const segments = restorePath(b.cell, exit, 'main', from);
|
||||
segments.forEach((s) => paths.push(s));
|
||||
}
|
||||
}
|
||||
|
||||
cells.i.forEach(i => (cells.s[i] += cells.road[i] / 2)); // add roads to suitability score
|
||||
TIME && console.timeEnd("generateMainRoads");
|
||||
cells.i.forEach((i) => (cells.s[i] += cells.road[i] / 2)); // add roads to suitability score
|
||||
TIME && console.timeEnd('generateMainRoads');
|
||||
return paths;
|
||||
};
|
||||
|
||||
const getTrails = function () {
|
||||
TIME && console.time("generateTrails");
|
||||
TIME && console.time('generateTrails');
|
||||
const cells = pack.cells;
|
||||
const burgs = pack.burgs.filter(b => b.i && !b.removed);
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
|
||||
if (burgs.length < 2) return []; // not enough burgs to build trails
|
||||
|
||||
let paths = []; // array to store path segments
|
||||
for (const f of pack.features.filter(f => f.land)) {
|
||||
const isle = burgs.filter(b => b.feature === f.i); // burgs on island
|
||||
for (const f of pack.features.filter((f) => f.land)) {
|
||||
const isle = burgs.filter((b) => b.feature === f.i); // burgs on island
|
||||
if (isle.length < 2) continue;
|
||||
|
||||
isle.forEach(function (b, i) {
|
||||
|
|
@ -43,35 +43,35 @@ window.Routes = (function () {
|
|||
const to = isle[farthest].cell;
|
||||
if (cells.road[to]) return;
|
||||
const [from, exit] = findLandPath(b.cell, to, true);
|
||||
path = restorePath(b.cell, exit, "small", from);
|
||||
path = restorePath(b.cell, exit, 'small', from);
|
||||
} else {
|
||||
// build trail from all other burgs to the closest road on the same island
|
||||
if (cells.road[b.cell]) return;
|
||||
const [from, exit] = findLandPath(b.cell, null, true);
|
||||
if (exit === null) return;
|
||||
path = restorePath(b.cell, exit, "small", from);
|
||||
path = restorePath(b.cell, exit, 'small', from);
|
||||
}
|
||||
if (path) paths = paths.concat(path);
|
||||
});
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("generateTrails");
|
||||
TIME && console.timeEnd('generateTrails');
|
||||
return paths;
|
||||
};
|
||||
|
||||
const getSearoutes = function () {
|
||||
TIME && console.time("generateSearoutes");
|
||||
TIME && console.time('generateSearoutes');
|
||||
const {cells, burgs, features} = pack;
|
||||
const allPorts = burgs.filter(b => b.port > 0 && !b.removed);
|
||||
const allPorts = burgs.filter((b) => b.port > 0 && !b.removed);
|
||||
|
||||
if (!allPorts.length) return [];
|
||||
|
||||
const bodies = new Set(allPorts.map(b => b.port)); // water features with ports
|
||||
const bodies = new Set(allPorts.map((b) => b.port)); // water features with ports
|
||||
let paths = []; // array to store path segments
|
||||
const connected = []; // store cell id of connected burgs
|
||||
|
||||
bodies.forEach(f => {
|
||||
const ports = allPorts.filter(b => b.port === f); // all ports on the same feature
|
||||
bodies.forEach((f) => {
|
||||
const ports = allPorts.filter((b) => b.port === f); // all ports on the same feature
|
||||
if (!ports.length) return;
|
||||
|
||||
if (features[f].border) addOverseaRoute(f, ports[0]);
|
||||
|
|
@ -88,7 +88,7 @@ window.Routes = (function () {
|
|||
const [from, exit, passable] = findOceanPath(target, source, true);
|
||||
if (!passable) continue;
|
||||
|
||||
const path = restorePath(target, exit, "ocean", from);
|
||||
const path = restorePath(target, exit, 'ocean', from);
|
||||
paths = paths.concat(path);
|
||||
|
||||
connected[source] = 1;
|
||||
|
|
@ -99,7 +99,7 @@ window.Routes = (function () {
|
|||
|
||||
function addOverseaRoute(f, port) {
|
||||
const {x, y, cell: source} = port;
|
||||
const dist = p => Math.abs(p[0] - x) + Math.abs(p[1] - y);
|
||||
const dist = (p) => Math.abs(p[0] - x) + Math.abs(p[1] - y);
|
||||
const [x1, y1] = [
|
||||
[0, y],
|
||||
[x, 0],
|
||||
|
|
@ -112,39 +112,39 @@ window.Routes = (function () {
|
|||
const [from, exit, passable] = findOceanPath(target, source, true);
|
||||
|
||||
if (passable) {
|
||||
const path = restorePath(target, exit, "ocean", from);
|
||||
const path = restorePath(target, exit, 'ocean', from);
|
||||
paths = paths.concat(path);
|
||||
last(path).push([x1, y1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("generateSearoutes");
|
||||
TIME && console.timeEnd('generateSearoutes');
|
||||
return paths;
|
||||
};
|
||||
|
||||
const draw = function (main, small, water) {
|
||||
TIME && console.time("drawRoutes");
|
||||
TIME && console.time('drawRoutes');
|
||||
const {cells, burgs} = pack;
|
||||
const {burg, p} = cells;
|
||||
|
||||
const getBurgCoords = b => [burgs[b].x, burgs[b].y];
|
||||
const getPathPoints = cells => cells.map(i => (Array.isArray(i) ? i : burg[i] ? getBurgCoords(burg[i]) : p[i]));
|
||||
const getPath = segment => round(lineGen(getPathPoints(segment)), 1);
|
||||
const getPathsHTML = (paths, type) => paths.map((path, i) => `<path id="${type}${i}" d="${getPath(path)}" />`).join("");
|
||||
const getBurgCoords = (b) => [burgs[b].x, burgs[b].y];
|
||||
const getPathPoints = (cells) => cells.map((i) => (Array.isArray(i) ? i : burg[i] ? getBurgCoords(burg[i]) : p[i]));
|
||||
const getPath = (segment) => round(lineGen(getPathPoints(segment)), 1);
|
||||
const getPathsHTML = (paths, type) => paths.map((path, i) => `<path id="${type}${i}" d="${getPath(path)}" />`).join('');
|
||||
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
roads.html(getPathsHTML(main, "road"));
|
||||
trails.html(getPathsHTML(small, "trail"));
|
||||
roads.html(getPathsHTML(main, 'road'));
|
||||
trails.html(getPathsHTML(small, 'trail'));
|
||||
|
||||
lineGen.curve(d3.curveBundle.beta(1));
|
||||
searoutes.html(getPathsHTML(water, "searoute"));
|
||||
searoutes.html(getPathsHTML(water, 'searoute'));
|
||||
|
||||
TIME && console.timeEnd("drawRoutes");
|
||||
TIME && console.timeEnd('drawRoutes');
|
||||
};
|
||||
|
||||
const regenerate = function () {
|
||||
routes.selectAll("path").remove();
|
||||
routes.selectAll('path').remove();
|
||||
pack.cells.road = new Uint16Array(pack.cells.i.length);
|
||||
pack.cells.crossroad = new Uint16Array(pack.cells.i.length);
|
||||
const main = getRoads();
|
||||
|
|
@ -196,9 +196,9 @@ window.Routes = (function () {
|
|||
let segment = [],
|
||||
current = end,
|
||||
prev = end;
|
||||
const score = type === "main" ? 5 : 1; // to increase road score at cell
|
||||
const score = type === 'main' ? 5 : 1; // to increase road score at cell
|
||||
|
||||
if (type === "ocean" || !cells.road[prev]) segment.push(end);
|
||||
if (type === 'ocean' || !cells.road[prev]) segment.push(end);
|
||||
if (!cells.road[prev]) cells.road[prev] = score;
|
||||
|
||||
for (let i = 0, limit = 1000; i < limit; i++) {
|
||||
|
|
|
|||
740
modules/save.js
740
modules/save.js
|
|
@ -1,503 +1,132 @@
|
|||
// Functions to save and load the map
|
||||
'use strict';
|
||||
|
||||
// download map as SVG
|
||||
async function saveSVG() {
|
||||
TIME && console.time('saveSVG');
|
||||
const url = await getMapURL('svg');
|
||||
const link = document.createElement('a');
|
||||
link.download = getFileName() + '.svg';
|
||||
link.href = url;
|
||||
link.click();
|
||||
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, 'success', 5000);
|
||||
TIME && console.timeEnd('saveSVG');
|
||||
}
|
||||
|
||||
// download map as PNG
|
||||
async function savePNG() {
|
||||
TIME && console.time('savePNG');
|
||||
const url = await getMapURL('png');
|
||||
|
||||
const link = document.createElement('a');
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
link.download = getFileName() + '.png';
|
||||
canvas.toBlob(function (blob) {
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.click();
|
||||
window.setTimeout(function () {
|
||||
canvas.remove();
|
||||
window.URL.revokeObjectURL(link.href);
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, 'success', 5000);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
TIME && console.timeEnd('savePNG');
|
||||
}
|
||||
|
||||
// download map as JPEG
|
||||
async function saveJPEG() {
|
||||
TIME && console.time('saveJPEG');
|
||||
const url = await getMapURL('png');
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = async function () {
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92);
|
||||
const URL = await canvas.toDataURL('image/jpeg', quality);
|
||||
const link = document.createElement('a');
|
||||
link.download = getFileName() + '.jpeg';
|
||||
link.href = URL;
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, 'success', 7000);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
};
|
||||
|
||||
TIME && console.timeEnd('saveJPEG');
|
||||
}
|
||||
|
||||
// download map as png tiles
|
||||
async function saveTiles() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// download schema
|
||||
const urlSchema = await getMapURL('tiles', {debug: true});
|
||||
const zip = new JSZip();
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
|
||||
const imgSchema = new Image();
|
||||
imgSchema.src = urlSchema;
|
||||
imgSchema.onload = function () {
|
||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob((blob) => zip.file(`fmg_tile_schema.png`, blob));
|
||||
};
|
||||
|
||||
// download tiles
|
||||
const url = await getMapURL('tiles');
|
||||
const tilesX = +document.getElementById('tileColsInput').value;
|
||||
const tilesY = +document.getElementById('tileRowsInput').value;
|
||||
const scale = +document.getElementById('tileScaleInput').value;
|
||||
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
const tolesTotal = tilesX * tilesY;
|
||||
|
||||
const width = graphWidth * scale;
|
||||
const height = width * (tileH / tileW);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
let loaded = 0;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
||||
const name = `fmg_tile_${i}.png`;
|
||||
canvas.toBlob((blob) => {
|
||||
zip.file(name, blob);
|
||||
loaded += 1;
|
||||
if (loaded === tolesTotal) return downloadZip();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function downloadZip() {
|
||||
const name = `${getFileName()}.zip`;
|
||||
zip.generateAsync({type: 'blob'}).then((blob) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = name;
|
||||
link.click();
|
||||
link.remove();
|
||||
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// parse map svg to object url
|
||||
async function getMapURL(type, options = {}) {
|
||||
const {debug = false, globe = false, noLabels = false, noWater = false} = options;
|
||||
const cloneEl = document.getElementById('map').cloneNode(true); // clone svg
|
||||
cloneEl.id = 'fantasyMap';
|
||||
document.body.appendChild(cloneEl);
|
||||
const clone = d3.select(cloneEl);
|
||||
if (!debug) 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 (globe) clone.select('#scaleBar').remove();
|
||||
if (noLabels) {
|
||||
clone.select('#labels #states').remove();
|
||||
clone.select('#labels #burgLabels').remove();
|
||||
clone.select('#icons #burgIcons').remove();
|
||||
}
|
||||
if (noWater) {
|
||||
clone.select('#oceanBase').attr('opacity', 0);
|
||||
clone.select('#oceanPattern').attr('opacity', 0);
|
||||
}
|
||||
if (type !== 'png') {
|
||||
// reset transform to show the whole map
|
||||
clone.attr('width', graphWidth).attr('height', graphHeight);
|
||||
clone.select('#viewbox').attr('transform', null);
|
||||
}
|
||||
|
||||
if (type === 'svg') removeUnusedElements(clone);
|
||||
if (customization && type === 'mesh') updateMeshCells(clone);
|
||||
inlineStyle(clone);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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') && emblems.selectAll('use').size()) {
|
||||
cloneEl
|
||||
.getElementById('emblems')
|
||||
?.querySelectorAll('use')
|
||||
.forEach((el) => {
|
||||
const href = el.getAttribute('href') || el.getAttribute('xlink:href');
|
||||
if (!href) return;
|
||||
const emblem = document.getElementById(href.slice(1));
|
||||
if (emblem) cloneDefs.append(emblem.cloneNode(true));
|
||||
});
|
||||
} else {
|
||||
cloneDefs.querySelector('#defs-emblems')?.remove();
|
||||
}
|
||||
|
||||
// add resources TODO
|
||||
|
||||
// replace ocean pattern href to base64
|
||||
if (PRODUCTION && cloneEl.getElementById('oceanicPattern')) {
|
||||
const el = cloneEl.getElementById('oceanicPattern');
|
||||
const url = el.getAttribute('href');
|
||||
await new Promise((resolve) => {
|
||||
getBase64(url, (base64) => {
|
||||
el.setAttribute('href', base64);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add relief icons
|
||||
if (cloneEl.getElementById('terrain')) {
|
||||
const uniqueElements = new Set();
|
||||
const terrainNodes = cloneEl.getElementById('terrain').childNodes;
|
||||
for (let i = 0; i < terrainNodes.length; i++) {
|
||||
const href = terrainNodes[i].getAttribute('href') || terrainNodes[i].getAttribute('xlink:href');
|
||||
uniqueElements.add(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));
|
||||
}
|
||||
|
||||
// add grid pattern
|
||||
if (cloneEl.getElementById('gridOverlay')?.hasChildNodes()) {
|
||||
const type = cloneEl.getElementById('gridOverlay').getAttribute('type');
|
||||
const pattern = svgDefs.getElementById('pattern_' + type);
|
||||
if (pattern) cloneDefs.appendChild(pattern.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',
|
||||
'<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>'
|
||||
);
|
||||
|
||||
// add xlink: for href to support svg1.1
|
||||
if (type === 'svg') {
|
||||
cloneEl.querySelectorAll('[href]').forEach((el) => {
|
||||
const href = el.getAttribute('href');
|
||||
el.removeAttribute('href');
|
||||
el.setAttribute('xlink:href', href);
|
||||
});
|
||||
}
|
||||
|
||||
// load non-standard fonts
|
||||
const usedFonts = getFontsList(clone);
|
||||
const webSafe = ['Georgia', 'Times+New+Roman', 'Comic+Sans+MS', 'Lucida+Sans+Unicode', 'Courier+New', 'Verdana', 'Arial', 'Impact'];
|
||||
const fontsToLoad = usedFonts.filter((font) => !webSafe.includes(font));
|
||||
if (fontsToLoad.length) {
|
||||
const url = 'https://fonts.googleapis.com/css?family=' + fontsToLoad.join('|');
|
||||
const fontStyle = await GFontToDataURI(url);
|
||||
if (fontStyle) clone.select('defs').append('style').text(fontStyle.join('\n'));
|
||||
}
|
||||
|
||||
clone.remove();
|
||||
|
||||
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + 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;
|
||||
}
|
||||
|
||||
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
|
||||
function removeUnusedElements(clone) {
|
||||
if (!terrain.selectAll('use').size()) clone.select('#defs-relief').remove();
|
||||
if (markers.style('display') === 'none') clone.select('#defs-markers').remove();
|
||||
|
||||
for (let empty = 1; empty; ) {
|
||||
empty = 0;
|
||||
clone.selectAll('g').each(function () {
|
||||
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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateMeshCells(clone) {
|
||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter((i) => grid.cells.h[i] >= 20);
|
||||
const scheme = getColorScheme();
|
||||
clone.select('#heights').attr('filter', 'url(#blur1)');
|
||||
clone
|
||||
.select('#heights')
|
||||
.selectAll('polygon')
|
||||
.data(data)
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getGridPolygon(d))
|
||||
.attr('id', (d) => 'cell' + d)
|
||||
.attr('stroke', (d) => getColor(grid.cells.h[d], scheme));
|
||||
}
|
||||
|
||||
// for each g element get inline style
|
||||
function inlineStyle(clone) {
|
||||
const emptyG = clone.append('g').node();
|
||||
const defaultStyles = window.getComputedStyle(emptyG);
|
||||
|
||||
clone.selectAll('g, #ruler *, #scaleBar > text').each(function () {
|
||||
const compStyle = window.getComputedStyle(this);
|
||||
let style = '';
|
||||
|
||||
for (let i = 0; i < compStyle.length; i++) {
|
||||
const key = compStyle[i];
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
// Firefox mask hack
|
||||
if (key === 'mask-image' && value !== defaultStyles.getPropertyValue(key)) {
|
||||
style += "mask-image: url('#land');";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === 'cursor') continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ':' + value + ';';
|
||||
}
|
||||
|
||||
for (const key in compStyle) {
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
if (key === 'cursor') continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ':' + value + ';';
|
||||
}
|
||||
|
||||
if (style != '') this.setAttribute('style', style);
|
||||
});
|
||||
|
||||
emptyG.remove();
|
||||
}
|
||||
// functions to save project as .map file
|
||||
|
||||
// prepare map data for saving
|
||||
function getMapData() {
|
||||
TIME && console.time('createMapDataBlob');
|
||||
TIME && console.time('createMapData');
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const date = new Date();
|
||||
const dateString = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
|
||||
const license = 'File can be loaded in azgaar.github.io/Fantasy-Map-Generator';
|
||||
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join('|');
|
||||
const settings = [
|
||||
distanceUnitInput.value,
|
||||
distanceScaleInput.value,
|
||||
areaUnit.value,
|
||||
heightUnit.value,
|
||||
heightExponentInput.value,
|
||||
temperatureScale.value,
|
||||
barSizeInput.value,
|
||||
barLabel.value,
|
||||
barBackOpacity.value,
|
||||
barBackColor.value,
|
||||
barPosX.value,
|
||||
barPosY.value,
|
||||
populationRate,
|
||||
urbanization,
|
||||
mapSizeOutput.value,
|
||||
latitudeOutput.value,
|
||||
temperatureEquatorOutput.value,
|
||||
temperaturePoleOutput.value,
|
||||
precOutput.value,
|
||||
JSON.stringify(options),
|
||||
mapName.value,
|
||||
+hideLabels.checked,
|
||||
stylePreset.value,
|
||||
+rescaleLabels.checked
|
||||
].join('|');
|
||||
const coords = JSON.stringify(mapCoordinates);
|
||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join('|');
|
||||
const notesData = JSON.stringify(notes);
|
||||
const rulersString = rulers.toString();
|
||||
const date = new Date();
|
||||
const dateString = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
|
||||
const license = 'File can be loaded in azgaar.github.io/Fantasy-Map-Generator';
|
||||
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join('|');
|
||||
const settings = [
|
||||
distanceUnitInput.value,
|
||||
distanceScaleInput.value,
|
||||
areaUnit.value,
|
||||
heightUnit.value,
|
||||
heightExponentInput.value,
|
||||
temperatureScale.value,
|
||||
barSizeInput.value,
|
||||
barLabel.value,
|
||||
barBackOpacity.value,
|
||||
barBackColor.value,
|
||||
barPosX.value,
|
||||
barPosY.value,
|
||||
populationRate,
|
||||
urbanization,
|
||||
mapSizeOutput.value,
|
||||
latitudeOutput.value,
|
||||
temperatureEquatorOutput.value,
|
||||
temperaturePoleOutput.value,
|
||||
precOutput.value,
|
||||
JSON.stringify(options),
|
||||
mapName.value,
|
||||
+hideLabels.checked,
|
||||
stylePreset.value,
|
||||
+rescaleLabels.checked,
|
||||
urbanDensity
|
||||
].join('|');
|
||||
const coords = JSON.stringify(mapCoordinates);
|
||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join('|');
|
||||
const notesData = JSON.stringify(notes);
|
||||
const rulersString = rulers.toString();
|
||||
const fonts = JSON.stringify(getUsedFonts(svg.node()));
|
||||
|
||||
// clone svg
|
||||
const cloneEl = document.getElementById('map').cloneNode(true);
|
||||
// save 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');
|
||||
// reset transform values to default
|
||||
cloneEl.setAttribute('width', graphWidth);
|
||||
cloneEl.setAttribute('height', graphHeight);
|
||||
cloneEl.querySelector('#viewbox').removeAttribute('transform');
|
||||
|
||||
// always remove rulers
|
||||
cloneEl.querySelector('#ruler').innerHTML = '';
|
||||
cloneEl.querySelector('#ruler').innerHTML = ''; // always remove rulers
|
||||
|
||||
const svg_xml = new XMLSerializer().serializeToString(cloneEl);
|
||||
const serializedSVG = 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});
|
||||
const features = JSON.stringify(pack.features);
|
||||
const cultures = JSON.stringify(pack.cultures);
|
||||
const states = JSON.stringify(pack.states);
|
||||
const burgs = JSON.stringify(pack.burgs);
|
||||
const religions = JSON.stringify(pack.religions);
|
||||
const provinces = JSON.stringify(pack.provinces);
|
||||
const rivers = JSON.stringify(pack.rivers);
|
||||
const resources = JSON.stringify(pack.resources);
|
||||
const {spacing, cellsX, cellsY, boundary, points, features} = grid;
|
||||
const gridGeneral = JSON.stringify({spacing, cellsX, cellsY, boundary, points, features});
|
||||
const packFeatures = JSON.stringify(pack.features);
|
||||
const cultures = JSON.stringify(pack.cultures);
|
||||
const states = JSON.stringify(pack.states);
|
||||
const burgs = JSON.stringify(pack.burgs);
|
||||
const religions = JSON.stringify(pack.religions);
|
||||
const provinces = JSON.stringify(pack.provinces);
|
||||
const rivers = JSON.stringify(pack.rivers);
|
||||
const markers = JSON.stringify(pack.markers);
|
||||
|
||||
// store name array only if it is not the same as default
|
||||
const defaultNB = Names.getNameBases();
|
||||
const namesData = nameBases
|
||||
.map((b, i) => {
|
||||
const names = defaultNB[i] && defaultNB[i].b === b.b ? '' : b.b;
|
||||
return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`;
|
||||
})
|
||||
.join('/');
|
||||
// store name array only if not the same as default
|
||||
const defaultNB = Names.getNameBases();
|
||||
const namesData = nameBases
|
||||
.map((b, i) => {
|
||||
const names = defaultNB[i] && defaultNB[i].b === b.b ? '' : b.b;
|
||||
return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`;
|
||||
})
|
||||
.join('/');
|
||||
|
||||
// round population to save space
|
||||
const pop = Array.from(pack.cells.pop).map((p) => rn(p, 4));
|
||||
// round population to save space
|
||||
const pop = Array.from(pack.cells.pop).map((p) => rn(p, 4));
|
||||
|
||||
// data format as below
|
||||
const data = [
|
||||
params,
|
||||
settings,
|
||||
coords,
|
||||
biomes,
|
||||
notesData,
|
||||
svg_xml,
|
||||
gridGeneral,
|
||||
grid.cells.h,
|
||||
grid.cells.prec,
|
||||
grid.cells.f,
|
||||
grid.cells.t,
|
||||
grid.cells.temp,
|
||||
features,
|
||||
cultures,
|
||||
states,
|
||||
burgs,
|
||||
pack.cells.biome,
|
||||
pack.cells.burg,
|
||||
pack.cells.conf,
|
||||
pack.cells.culture,
|
||||
pack.cells.fl,
|
||||
pop,
|
||||
pack.cells.r,
|
||||
pack.cells.road,
|
||||
pack.cells.s,
|
||||
pack.cells.state,
|
||||
pack.cells.religion,
|
||||
pack.cells.province,
|
||||
pack.cells.crossroad,
|
||||
religions,
|
||||
provinces,
|
||||
namesData,
|
||||
rivers,
|
||||
rulersString,
|
||||
pack.cells.resource,
|
||||
resources
|
||||
].join('\r\n');
|
||||
const blob = new Blob([data], {type: 'text/plain'});
|
||||
|
||||
TIME && console.timeEnd('createMapDataBlob');
|
||||
resolve(blob);
|
||||
});
|
||||
// data format as below
|
||||
const mapData = [
|
||||
params,
|
||||
settings,
|
||||
coords,
|
||||
biomes,
|
||||
notesData,
|
||||
serializedSVG,
|
||||
gridGeneral,
|
||||
grid.cells.h,
|
||||
grid.cells.prec,
|
||||
grid.cells.f,
|
||||
grid.cells.t,
|
||||
grid.cells.temp,
|
||||
packFeatures,
|
||||
cultures,
|
||||
states,
|
||||
burgs,
|
||||
pack.cells.biome,
|
||||
pack.cells.burg,
|
||||
pack.cells.conf,
|
||||
pack.cells.culture,
|
||||
pack.cells.fl,
|
||||
pop,
|
||||
pack.cells.r,
|
||||
pack.cells.road,
|
||||
pack.cells.s,
|
||||
pack.cells.state,
|
||||
pack.cells.religion,
|
||||
pack.cells.province,
|
||||
pack.cells.crossroad,
|
||||
religions,
|
||||
provinces,
|
||||
namesData,
|
||||
rivers,
|
||||
rulersString,
|
||||
fonts,
|
||||
markers
|
||||
].join('\r\n');
|
||||
TIME && console.timeEnd('createMapData');
|
||||
return mapData;
|
||||
}
|
||||
|
||||
// Download .map file
|
||||
async function saveMap() {
|
||||
function dowloadMap() {
|
||||
if (customization) return tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
closeDialogs('#alert');
|
||||
|
||||
const blob = await getMapData();
|
||||
const mapData = getMapData();
|
||||
const blob = new Blob([mapData], {type: 'text/plain'});
|
||||
const URL = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.download = getFileName() + '.map';
|
||||
|
|
@ -507,127 +136,25 @@ async function saveMap() {
|
|||
window.URL.revokeObjectURL(URL);
|
||||
}
|
||||
|
||||
function saveGeoJSON_Cells() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
const cells = pack.cells;
|
||||
const getPopulation = (i) => {
|
||||
const [r, u] = getCellPopulation(i);
|
||||
return rn(r + u);
|
||||
};
|
||||
const getHeight = (i) => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]]));
|
||||
|
||||
cells.i.forEach((i) => {
|
||||
const coordinates = getCellCoordinates(cells.v[i]);
|
||||
const height = getHeight(i);
|
||||
const biome = cells.biome[i];
|
||||
const type = pack.features[cells.f[i]].type;
|
||||
const population = getPopulation(i);
|
||||
const state = cells.state[i];
|
||||
const province = cells.province[i];
|
||||
const culture = cells.culture[i];
|
||||
const religion = cells.religion[i];
|
||||
const neighbors = cells.c[i];
|
||||
|
||||
const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors};
|
||||
const feature = {type: 'Feature', geometry: {type: 'Polygon', coordinates}, properties};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Cells') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function saveGeoJSON_Routes() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
|
||||
routes.selectAll('g > path').each(function () {
|
||||
const coordinates = getRoutePoints(this);
|
||||
const id = this.id;
|
||||
const type = this.parentElement.id;
|
||||
|
||||
const feature = {type: 'Feature', geometry: {type: 'LineString', coordinates}, properties: {id, type}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Routes') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function saveGeoJSON_Rivers() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
|
||||
rivers.selectAll('path').each(function () {
|
||||
const coordinates = getRiverPoints(this);
|
||||
const id = this.id;
|
||||
const width = +this.dataset.increment;
|
||||
const increment = +this.dataset.increment;
|
||||
const river = pack.rivers.find((r) => r.i === +id.slice(5));
|
||||
const name = river ? river.name : '';
|
||||
const type = river ? river.type : '';
|
||||
const i = river ? river.i : '';
|
||||
const basin = river ? river.basin : '';
|
||||
|
||||
const feature = {type: 'Feature', geometry: {type: 'LineString', coordinates}, properties: {id, i, basin, name, type, width, increment}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Rivers') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function saveGeoJSON_Markers() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
|
||||
markers.selectAll('use').each(function () {
|
||||
const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y);
|
||||
const id = this.id;
|
||||
const type = this.dataset.id.substring(1);
|
||||
const icon = document.getElementById(type).textContent;
|
||||
const note = notes.length ? notes.find((note) => note.id === this.id) : null;
|
||||
const name = note ? note.name : '';
|
||||
const legend = note ? note.legend : '';
|
||||
|
||||
const feature = {type: 'Feature', geometry: {type: 'Point', coordinates}, properties: {id, type, icon, name, legend}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Markers') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function getCellCoordinates(vertices) {
|
||||
const p = pack.vertices.p;
|
||||
const coordinates = vertices.map((n) => getQGIScoordinates(p[n][0], p[n][1]));
|
||||
return [coordinates.concat([coordinates[0]])];
|
||||
}
|
||||
|
||||
function getRoutePoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength();
|
||||
const increment = l / Math.ceil(l / 2);
|
||||
for (let i = 0; i <= l; i += increment) {
|
||||
const p = node.getPointAtLength(i);
|
||||
points.push(getQGIScoordinates(p.x, p.y));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
function getRiverPoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength() / 2; // half-length
|
||||
const increment = 0.25; // defines density of points
|
||||
for (let i = l, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p1 = node.getPointAtLength(i);
|
||||
const p2 = node.getPointAtLength(c);
|
||||
const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
||||
points.push([x, y]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
async function quickSave() {
|
||||
async function saveToDropbox() {
|
||||
if (customization) return tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
const blob = await getMapData();
|
||||
closeDialogs('#alert');
|
||||
const mapData = getMapData();
|
||||
const filename = getFileName() + '.map';
|
||||
try {
|
||||
await Cloud.providers.dropbox.save(filename, mapData);
|
||||
tip('Map is saved to your Dropbox', true, 'success', 8000);
|
||||
} catch (msg) {
|
||||
console.error(msg);
|
||||
tip('Cannot save .map to your Dropbox', true, 'error', 8000);
|
||||
}
|
||||
}
|
||||
|
||||
function quickSave() {
|
||||
if (customization) return tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
|
||||
const mapData = getMapData();
|
||||
const blob = new Blob([mapData], {type: 'text/plain'});
|
||||
if (blob) ldb.set('lastMap', blob); // auto-save map
|
||||
tip('Map is saved to browser memory. Please also save as .map file to secure progress', true, 'success', 2000);
|
||||
}
|
||||
|
|
@ -642,19 +169,16 @@ const saveReminder = function () {
|
|||
"Don't forget to save your map on a regular basis!",
|
||||
'Just a gentle reminder for you to save the map',
|
||||
"Please don't forget to save your progress (saving as .map is the best option)",
|
||||
"Don't want to be reminded about need to save? Press CTRL+Q",
|
||||
"You'd better to save your progress as .map file",
|
||||
"Don't want to lose the worldbuiding progress? Save your map right now",
|
||||
'There is no way to restore the map other than .map file. Please save it regularly'
|
||||
"Don't want to be reminded about need to save? Press CTRL+Q"
|
||||
];
|
||||
const interval = 15 * 60 * 1000; // remind every 15 minutes
|
||||
|
||||
saveReminder.reminder = setInterval(() => {
|
||||
if (customization) return;
|
||||
tip(ra(message), true, 'warn', 10000);
|
||||
}, 1e6);
|
||||
tip(ra(message), true, 'warn', 2500);
|
||||
}, interval);
|
||||
saveReminder.status = 1;
|
||||
};
|
||||
|
||||
saveReminder();
|
||||
|
||||
function toggleSaveReminder() {
|
||||
|
|
|
|||
843
modules/save.js.orig
Normal file
843
modules/save.js.orig
Normal file
|
|
@ -0,0 +1,843 @@
|
|||
<<<<<<< HEAD
|
||||
// Functions to save and load the map
|
||||
'use strict';
|
||||
|
||||
// download map as SVG
|
||||
async function saveSVG() {
|
||||
TIME && console.time('saveSVG');
|
||||
const url = await getMapURL('svg');
|
||||
const link = document.createElement('a');
|
||||
link.download = getFileName() + '.svg';
|
||||
link.href = url;
|
||||
link.click();
|
||||
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, 'success', 5000);
|
||||
TIME && console.timeEnd('saveSVG');
|
||||
}
|
||||
|
||||
// download map as PNG
|
||||
async function savePNG() {
|
||||
TIME && console.time('savePNG');
|
||||
const url = await getMapURL('png');
|
||||
|
||||
const link = document.createElement('a');
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = function () {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
link.download = getFileName() + '.png';
|
||||
canvas.toBlob(function (blob) {
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.click();
|
||||
window.setTimeout(function () {
|
||||
canvas.remove();
|
||||
window.URL.revokeObjectURL(link.href);
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, 'success', 5000);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
TIME && console.timeEnd('savePNG');
|
||||
}
|
||||
|
||||
// download map as JPEG
|
||||
async function saveJPEG() {
|
||||
TIME && console.time('saveJPEG');
|
||||
const url = await getMapURL('png');
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = svgWidth * pngResolutionInput.value;
|
||||
canvas.height = svgHeight * pngResolutionInput.value;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
img.onload = async function () {
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92);
|
||||
const URL = await canvas.toDataURL('image/jpeg', quality);
|
||||
const link = document.createElement('a');
|
||||
link.download = getFileName() + '.jpeg';
|
||||
link.href = URL;
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, 'success', 7000);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
};
|
||||
|
||||
TIME && console.timeEnd('saveJPEG');
|
||||
}
|
||||
|
||||
// download map as png tiles
|
||||
async function saveTiles() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// download schema
|
||||
const urlSchema = await getMapURL('tiles', {debug: true});
|
||||
const zip = new JSZip();
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
|
||||
const imgSchema = new Image();
|
||||
imgSchema.src = urlSchema;
|
||||
imgSchema.onload = function () {
|
||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob((blob) => zip.file(`fmg_tile_schema.png`, blob));
|
||||
};
|
||||
|
||||
// download tiles
|
||||
const url = await getMapURL('tiles');
|
||||
const tilesX = +document.getElementById('tileColsInput').value;
|
||||
const tilesY = +document.getElementById('tileRowsInput').value;
|
||||
const scale = +document.getElementById('tileScaleInput').value;
|
||||
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
const tolesTotal = tilesX * tilesY;
|
||||
|
||||
const width = graphWidth * scale;
|
||||
const height = width * (tileH / tileW);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
let loaded = 0;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
||||
const name = `fmg_tile_${i}.png`;
|
||||
canvas.toBlob((blob) => {
|
||||
zip.file(name, blob);
|
||||
loaded += 1;
|
||||
if (loaded === tolesTotal) return downloadZip();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function downloadZip() {
|
||||
const name = `${getFileName()}.zip`;
|
||||
zip.generateAsync({type: 'blob'}).then((blob) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = name;
|
||||
link.click();
|
||||
link.remove();
|
||||
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// parse map svg to object url
|
||||
async function getMapURL(type, options = {}) {
|
||||
const {debug = false, globe = false, noLabels = false, noWater = false} = options;
|
||||
const cloneEl = document.getElementById('map').cloneNode(true); // clone svg
|
||||
cloneEl.id = 'fantasyMap';
|
||||
document.body.appendChild(cloneEl);
|
||||
const clone = d3.select(cloneEl);
|
||||
if (!debug) 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 (globe) clone.select('#scaleBar').remove();
|
||||
if (noLabels) {
|
||||
clone.select('#labels #states').remove();
|
||||
clone.select('#labels #burgLabels').remove();
|
||||
clone.select('#icons #burgIcons').remove();
|
||||
}
|
||||
if (noWater) {
|
||||
clone.select('#oceanBase').attr('opacity', 0);
|
||||
clone.select('#oceanPattern').attr('opacity', 0);
|
||||
}
|
||||
if (type !== 'png') {
|
||||
// reset transform to show the whole map
|
||||
clone.attr('width', graphWidth).attr('height', graphHeight);
|
||||
clone.select('#viewbox').attr('transform', null);
|
||||
}
|
||||
|
||||
if (type === 'svg') removeUnusedElements(clone);
|
||||
if (customization && type === 'mesh') updateMeshCells(clone);
|
||||
inlineStyle(clone);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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') && emblems.selectAll('use').size()) {
|
||||
cloneEl
|
||||
.getElementById('emblems')
|
||||
?.querySelectorAll('use')
|
||||
.forEach((el) => {
|
||||
const href = el.getAttribute('href') || el.getAttribute('xlink:href');
|
||||
if (!href) return;
|
||||
const emblem = document.getElementById(href.slice(1));
|
||||
if (emblem) cloneDefs.append(emblem.cloneNode(true));
|
||||
});
|
||||
} else {
|
||||
cloneDefs.querySelector('#defs-emblems')?.remove();
|
||||
}
|
||||
|
||||
// add resources TODO
|
||||
|
||||
// replace ocean pattern href to base64
|
||||
if (PRODUCTION && cloneEl.getElementById('oceanicPattern')) {
|
||||
const el = cloneEl.getElementById('oceanicPattern');
|
||||
const url = el.getAttribute('href');
|
||||
await new Promise((resolve) => {
|
||||
getBase64(url, (base64) => {
|
||||
el.setAttribute('href', base64);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add relief icons
|
||||
if (cloneEl.getElementById('terrain')) {
|
||||
const uniqueElements = new Set();
|
||||
const terrainNodes = cloneEl.getElementById('terrain').childNodes;
|
||||
for (let i = 0; i < terrainNodes.length; i++) {
|
||||
const href = terrainNodes[i].getAttribute('href') || terrainNodes[i].getAttribute('xlink:href');
|
||||
uniqueElements.add(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));
|
||||
}
|
||||
|
||||
// add grid pattern
|
||||
if (cloneEl.getElementById('gridOverlay')?.hasChildNodes()) {
|
||||
const type = cloneEl.getElementById('gridOverlay').getAttribute('type');
|
||||
const pattern = svgDefs.getElementById('pattern_' + type);
|
||||
if (pattern) cloneDefs.appendChild(pattern.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',
|
||||
'<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>'
|
||||
);
|
||||
|
||||
// add xlink: for href to support svg1.1
|
||||
if (type === 'svg') {
|
||||
cloneEl.querySelectorAll('[href]').forEach((el) => {
|
||||
const href = el.getAttribute('href');
|
||||
el.removeAttribute('href');
|
||||
el.setAttribute('xlink:href', href);
|
||||
});
|
||||
}
|
||||
|
||||
// load non-standard fonts
|
||||
const usedFonts = getFontsList(clone);
|
||||
const webSafe = ['Georgia', 'Times+New+Roman', 'Comic+Sans+MS', 'Lucida+Sans+Unicode', 'Courier+New', 'Verdana', 'Arial', 'Impact'];
|
||||
const fontsToLoad = usedFonts.filter((font) => !webSafe.includes(font));
|
||||
if (fontsToLoad.length) {
|
||||
const url = 'https://fonts.googleapis.com/css?family=' + fontsToLoad.join('|');
|
||||
const fontStyle = await GFontToDataURI(url);
|
||||
if (fontStyle) clone.select('defs').append('style').text(fontStyle.join('\n'));
|
||||
}
|
||||
|
||||
clone.remove();
|
||||
|
||||
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + 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;
|
||||
}
|
||||
|
||||
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
|
||||
function removeUnusedElements(clone) {
|
||||
if (!terrain.selectAll('use').size()) clone.select('#defs-relief').remove();
|
||||
if (markers.style('display') === 'none') clone.select('#defs-markers').remove();
|
||||
|
||||
for (let empty = 1; empty; ) {
|
||||
empty = 0;
|
||||
clone.selectAll('g').each(function () {
|
||||
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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateMeshCells(clone) {
|
||||
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter((i) => grid.cells.h[i] >= 20);
|
||||
const scheme = getColorScheme();
|
||||
clone.select('#heights').attr('filter', 'url(#blur1)');
|
||||
clone
|
||||
.select('#heights')
|
||||
.selectAll('polygon')
|
||||
.data(data)
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getGridPolygon(d))
|
||||
.attr('id', (d) => 'cell' + d)
|
||||
.attr('stroke', (d) => getColor(grid.cells.h[d], scheme));
|
||||
}
|
||||
|
||||
// for each g element get inline style
|
||||
function inlineStyle(clone) {
|
||||
const emptyG = clone.append('g').node();
|
||||
const defaultStyles = window.getComputedStyle(emptyG);
|
||||
|
||||
clone.selectAll('g, #ruler *, #scaleBar > text').each(function () {
|
||||
const compStyle = window.getComputedStyle(this);
|
||||
let style = '';
|
||||
|
||||
for (let i = 0; i < compStyle.length; i++) {
|
||||
const key = compStyle[i];
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
// Firefox mask hack
|
||||
if (key === 'mask-image' && value !== defaultStyles.getPropertyValue(key)) {
|
||||
style += "mask-image: url('#land');";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === 'cursor') continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ':' + value + ';';
|
||||
}
|
||||
|
||||
for (const key in compStyle) {
|
||||
const value = compStyle.getPropertyValue(key);
|
||||
|
||||
if (key === 'cursor') continue; // cursor should be default
|
||||
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
|
||||
if (value === defaultStyles.getPropertyValue(key)) continue;
|
||||
style += key + ':' + value + ';';
|
||||
}
|
||||
|
||||
if (style != '') this.setAttribute('style', style);
|
||||
});
|
||||
|
||||
emptyG.remove();
|
||||
}
|
||||
|
||||
// prepare map data for saving
|
||||
function getMapData() {
|
||||
TIME && console.time('createMapDataBlob');
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const date = new Date();
|
||||
const dateString = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
|
||||
const license = 'File can be loaded in azgaar.github.io/Fantasy-Map-Generator';
|
||||
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join('|');
|
||||
const settings = [
|
||||
distanceUnitInput.value,
|
||||
distanceScaleInput.value,
|
||||
areaUnit.value,
|
||||
heightUnit.value,
|
||||
heightExponentInput.value,
|
||||
temperatureScale.value,
|
||||
barSizeInput.value,
|
||||
barLabel.value,
|
||||
barBackOpacity.value,
|
||||
barBackColor.value,
|
||||
barPosX.value,
|
||||
barPosY.value,
|
||||
populationRate,
|
||||
urbanization,
|
||||
mapSizeOutput.value,
|
||||
latitudeOutput.value,
|
||||
temperatureEquatorOutput.value,
|
||||
temperaturePoleOutput.value,
|
||||
precOutput.value,
|
||||
JSON.stringify(options),
|
||||
mapName.value,
|
||||
+hideLabels.checked,
|
||||
stylePreset.value,
|
||||
+rescaleLabels.checked
|
||||
].join('|');
|
||||
const coords = JSON.stringify(mapCoordinates);
|
||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join('|');
|
||||
const notesData = JSON.stringify(notes);
|
||||
const rulersString = rulers.toString();
|
||||
|
||||
// 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');
|
||||
|
||||
// always remove rulers
|
||||
cloneEl.querySelector('#ruler').innerHTML = '';
|
||||
|
||||
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});
|
||||
const features = JSON.stringify(pack.features);
|
||||
const cultures = JSON.stringify(pack.cultures);
|
||||
const states = JSON.stringify(pack.states);
|
||||
const burgs = JSON.stringify(pack.burgs);
|
||||
const religions = JSON.stringify(pack.religions);
|
||||
const provinces = JSON.stringify(pack.provinces);
|
||||
const rivers = JSON.stringify(pack.rivers);
|
||||
const resources = JSON.stringify(pack.resources);
|
||||
|
||||
// store name array only if it is not the same as default
|
||||
const defaultNB = Names.getNameBases();
|
||||
const namesData = nameBases
|
||||
.map((b, i) => {
|
||||
const names = defaultNB[i] && defaultNB[i].b === b.b ? '' : b.b;
|
||||
return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`;
|
||||
})
|
||||
.join('/');
|
||||
|
||||
// round population to save space
|
||||
const pop = Array.from(pack.cells.pop).map((p) => rn(p, 4));
|
||||
|
||||
// data format as below
|
||||
const data = [
|
||||
params,
|
||||
settings,
|
||||
coords,
|
||||
biomes,
|
||||
notesData,
|
||||
svg_xml,
|
||||
gridGeneral,
|
||||
grid.cells.h,
|
||||
grid.cells.prec,
|
||||
grid.cells.f,
|
||||
grid.cells.t,
|
||||
grid.cells.temp,
|
||||
features,
|
||||
cultures,
|
||||
states,
|
||||
burgs,
|
||||
pack.cells.biome,
|
||||
pack.cells.burg,
|
||||
pack.cells.conf,
|
||||
pack.cells.culture,
|
||||
pack.cells.fl,
|
||||
pop,
|
||||
pack.cells.r,
|
||||
pack.cells.road,
|
||||
pack.cells.s,
|
||||
pack.cells.state,
|
||||
pack.cells.religion,
|
||||
pack.cells.province,
|
||||
pack.cells.crossroad,
|
||||
religions,
|
||||
provinces,
|
||||
namesData,
|
||||
rivers,
|
||||
rulersString,
|
||||
pack.cells.resource,
|
||||
resources
|
||||
].join('\r\n');
|
||||
const blob = new Blob([data], {type: 'text/plain'});
|
||||
|
||||
TIME && console.timeEnd('createMapDataBlob');
|
||||
resolve(blob);
|
||||
});
|
||||
}
|
||||
|
||||
// Download .map file
|
||||
async function saveMap() {
|
||||
if (customization) return tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
closeDialogs('#alert');
|
||||
=======
|
||||
"use strict";
|
||||
// functions to save project as .map file
|
||||
|
||||
// prepare map data for saving
|
||||
function getMapData() {
|
||||
TIME && console.time("createMapData");
|
||||
|
||||
const date = new Date();
|
||||
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
||||
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
|
||||
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|");
|
||||
const settings = [
|
||||
distanceUnitInput.value,
|
||||
distanceScaleInput.value,
|
||||
areaUnit.value,
|
||||
heightUnit.value,
|
||||
heightExponentInput.value,
|
||||
temperatureScale.value,
|
||||
barSizeInput.value,
|
||||
barLabel.value,
|
||||
barBackOpacity.value,
|
||||
barBackColor.value,
|
||||
barPosX.value,
|
||||
barPosY.value,
|
||||
populationRate,
|
||||
urbanization,
|
||||
mapSizeOutput.value,
|
||||
latitudeOutput.value,
|
||||
temperatureEquatorOutput.value,
|
||||
temperaturePoleOutput.value,
|
||||
precOutput.value,
|
||||
JSON.stringify(options),
|
||||
mapName.value,
|
||||
+hideLabels.checked,
|
||||
stylePreset.value,
|
||||
+rescaleLabels.checked,
|
||||
urbanDensity
|
||||
].join("|");
|
||||
const coords = JSON.stringify(mapCoordinates);
|
||||
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
|
||||
const notesData = JSON.stringify(notes);
|
||||
const rulersString = rulers.toString();
|
||||
const fonts = JSON.stringify(getUsedFonts(svg.node()));
|
||||
|
||||
// save svg
|
||||
const cloneEl = document.getElementById("map").cloneNode(true);
|
||||
|
||||
// reset transform values to default
|
||||
cloneEl.setAttribute("width", graphWidth);
|
||||
cloneEl.setAttribute("height", graphHeight);
|
||||
cloneEl.querySelector("#viewbox").removeAttribute("transform");
|
||||
|
||||
cloneEl.querySelector("#ruler").innerHTML = ""; // always remove rulers
|
||||
|
||||
const serializedSVG = new XMLSerializer().serializeToString(cloneEl);
|
||||
|
||||
const {spacing, cellsX, cellsY, boundary, points, features} = grid;
|
||||
const gridGeneral = JSON.stringify({spacing, cellsX, cellsY, boundary, points, features});
|
||||
const packFeatures = JSON.stringify(pack.features);
|
||||
const cultures = JSON.stringify(pack.cultures);
|
||||
const states = JSON.stringify(pack.states);
|
||||
const burgs = JSON.stringify(pack.burgs);
|
||||
const religions = JSON.stringify(pack.religions);
|
||||
const provinces = JSON.stringify(pack.provinces);
|
||||
const rivers = JSON.stringify(pack.rivers);
|
||||
const markers = JSON.stringify(pack.markers);
|
||||
|
||||
// store name array only if not the same as default
|
||||
const defaultNB = Names.getNameBases();
|
||||
const namesData = nameBases
|
||||
.map((b, i) => {
|
||||
const names = defaultNB[i] && defaultNB[i].b === b.b ? "" : b.b;
|
||||
return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`;
|
||||
})
|
||||
.join("/");
|
||||
|
||||
// round population to save space
|
||||
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4));
|
||||
|
||||
// data format as below
|
||||
const mapData = [
|
||||
params,
|
||||
settings,
|
||||
coords,
|
||||
biomes,
|
||||
notesData,
|
||||
serializedSVG,
|
||||
gridGeneral,
|
||||
grid.cells.h,
|
||||
grid.cells.prec,
|
||||
grid.cells.f,
|
||||
grid.cells.t,
|
||||
grid.cells.temp,
|
||||
packFeatures,
|
||||
cultures,
|
||||
states,
|
||||
burgs,
|
||||
pack.cells.biome,
|
||||
pack.cells.burg,
|
||||
pack.cells.conf,
|
||||
pack.cells.culture,
|
||||
pack.cells.fl,
|
||||
pop,
|
||||
pack.cells.r,
|
||||
pack.cells.road,
|
||||
pack.cells.s,
|
||||
pack.cells.state,
|
||||
pack.cells.religion,
|
||||
pack.cells.province,
|
||||
pack.cells.crossroad,
|
||||
religions,
|
||||
provinces,
|
||||
namesData,
|
||||
rivers,
|
||||
rulersString,
|
||||
fonts,
|
||||
markers
|
||||
].join("\r\n");
|
||||
TIME && console.timeEnd("createMapData");
|
||||
return mapData;
|
||||
}
|
||||
|
||||
// Download .map file
|
||||
function dowloadMap() {
|
||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
closeDialogs("#alert");
|
||||
>>>>>>> master
|
||||
|
||||
const mapData = getMapData();
|
||||
const blob = new Blob([mapData], {type: "text/plain"});
|
||||
const URL = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.download = getFileName() + '.map';
|
||||
link.href = URL;
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, 'success', 7000);
|
||||
window.URL.revokeObjectURL(URL);
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
function saveGeoJSON_Cells() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
const cells = pack.cells;
|
||||
const getPopulation = (i) => {
|
||||
const [r, u] = getCellPopulation(i);
|
||||
return rn(r + u);
|
||||
};
|
||||
const getHeight = (i) => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]]));
|
||||
|
||||
cells.i.forEach((i) => {
|
||||
const coordinates = getCellCoordinates(cells.v[i]);
|
||||
const height = getHeight(i);
|
||||
const biome = cells.biome[i];
|
||||
const type = pack.features[cells.f[i]].type;
|
||||
const population = getPopulation(i);
|
||||
const state = cells.state[i];
|
||||
const province = cells.province[i];
|
||||
const culture = cells.culture[i];
|
||||
const religion = cells.religion[i];
|
||||
const neighbors = cells.c[i];
|
||||
|
||||
const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors};
|
||||
const feature = {type: 'Feature', geometry: {type: 'Polygon', coordinates}, properties};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Cells') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function saveGeoJSON_Routes() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
|
||||
routes.selectAll('g > path').each(function () {
|
||||
const coordinates = getRoutePoints(this);
|
||||
const id = this.id;
|
||||
const type = this.parentElement.id;
|
||||
|
||||
const feature = {type: 'Feature', geometry: {type: 'LineString', coordinates}, properties: {id, type}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Routes') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function saveGeoJSON_Rivers() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
|
||||
rivers.selectAll('path').each(function () {
|
||||
const coordinates = getRiverPoints(this);
|
||||
const id = this.id;
|
||||
const width = +this.dataset.increment;
|
||||
const increment = +this.dataset.increment;
|
||||
const river = pack.rivers.find((r) => r.i === +id.slice(5));
|
||||
const name = river ? river.name : '';
|
||||
const type = river ? river.type : '';
|
||||
const i = river ? river.i : '';
|
||||
const basin = river ? river.basin : '';
|
||||
|
||||
const feature = {type: 'Feature', geometry: {type: 'LineString', coordinates}, properties: {id, i, basin, name, type, width, increment}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Rivers') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function saveGeoJSON_Markers() {
|
||||
const json = {type: 'FeatureCollection', features: []};
|
||||
|
||||
markers.selectAll('use').each(function () {
|
||||
const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y);
|
||||
const id = this.id;
|
||||
const type = this.dataset.id.substring(1);
|
||||
const icon = document.getElementById(type).textContent;
|
||||
const note = notes.length ? notes.find((note) => note.id === this.id) : null;
|
||||
const name = note ? note.name : '';
|
||||
const legend = note ? note.legend : '';
|
||||
|
||||
const feature = {type: 'Feature', geometry: {type: 'Point', coordinates}, properties: {id, type, icon, name, legend}};
|
||||
json.features.push(feature);
|
||||
});
|
||||
|
||||
const name = getFileName('Markers') + '.geojson';
|
||||
downloadFile(JSON.stringify(json), name, 'application/json');
|
||||
}
|
||||
|
||||
function getCellCoordinates(vertices) {
|
||||
const p = pack.vertices.p;
|
||||
const coordinates = vertices.map((n) => getQGIScoordinates(p[n][0], p[n][1]));
|
||||
return [coordinates.concat([coordinates[0]])];
|
||||
}
|
||||
|
||||
function getRoutePoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength();
|
||||
const increment = l / Math.ceil(l / 2);
|
||||
for (let i = 0; i <= l; i += increment) {
|
||||
const p = node.getPointAtLength(i);
|
||||
points.push(getQGIScoordinates(p.x, p.y));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
function getRiverPoints(node) {
|
||||
let points = [];
|
||||
const l = node.getTotalLength() / 2; // half-length
|
||||
const increment = 0.25; // defines density of points
|
||||
for (let i = l, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p1 = node.getPointAtLength(i);
|
||||
const p2 = node.getPointAtLength(c);
|
||||
const [x, y] = getQGIScoordinates((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
||||
points.push([x, y]);
|
||||
=======
|
||||
async function saveToDropbox() {
|
||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
closeDialogs("#alert");
|
||||
const mapData = getMapData();
|
||||
const filename = getFileName() + ".map";
|
||||
try {
|
||||
await Cloud.providers.dropbox.save(filename, mapData);
|
||||
tip("Map is saved to your Dropbox", true, "success", 8000);
|
||||
} catch (msg) {
|
||||
console.error(msg);
|
||||
tip("Cannot save .map to your Dropbox", true, "error", 8000);
|
||||
>>>>>>> master
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
async function quickSave() {
|
||||
if (customization) return tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
const blob = await getMapData();
|
||||
if (blob) ldb.set('lastMap', blob); // auto-save map
|
||||
tip('Map is saved to browser memory. Please also save as .map file to secure progress', true, 'success', 2000);
|
||||
}
|
||||
|
||||
const saveReminder = function () {
|
||||
if (localStorage.getItem('noReminder')) return;
|
||||
const message = [
|
||||
"Please don't forget to save your work as a .map file",
|
||||
'Please remember to save work as a .map file',
|
||||
"Saving in .map format will ensure your data won't be lost in case of issues",
|
||||
'Safety is number one priority. Please save the map',
|
||||
"Don't forget to save your map on a regular basis!",
|
||||
'Just a gentle reminder for you to save the map',
|
||||
"Please don't forget to save your progress (saving as .map is the best option)",
|
||||
"Don't want to be reminded about need to save? Press CTRL+Q",
|
||||
"You'd better to save your progress as .map file",
|
||||
"Don't want to lose the worldbuiding progress? Save your map right now",
|
||||
'There is no way to restore the map other than .map file. Please save it regularly'
|
||||
];
|
||||
|
||||
saveReminder.reminder = setInterval(() => {
|
||||
if (customization) return;
|
||||
tip(ra(message), true, 'warn', 10000);
|
||||
}, 1e6);
|
||||
=======
|
||||
function quickSave() {
|
||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
|
||||
const mapData = getMapData();
|
||||
const blob = new Blob([mapData], {type: "text/plain"});
|
||||
if (blob) ldb.set("lastMap", blob); // auto-save map
|
||||
tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000);
|
||||
}
|
||||
|
||||
const saveReminder = function () {
|
||||
if (localStorage.getItem("noReminder")) return;
|
||||
const message = [
|
||||
"Please don't forget to save your work as a .map file",
|
||||
"Please remember to save work as a .map file",
|
||||
"Saving in .map format will ensure your data won't be lost in case of issues",
|
||||
"Safety is number one priority. Please save the map",
|
||||
"Don't forget to save your map on a regular basis!",
|
||||
"Just a gentle reminder for you to save the map",
|
||||
"Please don't forget to save your progress (saving as .map is the best option)",
|
||||
"Don't want to be reminded about need to save? Press CTRL+Q"
|
||||
];
|
||||
const interval = 15 * 60 * 1000; // remind every 15 minutes
|
||||
|
||||
saveReminder.reminder = setInterval(() => {
|
||||
if (customization) return;
|
||||
tip(ra(message), true, "warn", 2500);
|
||||
}, interval);
|
||||
>>>>>>> master
|
||||
saveReminder.status = 1;
|
||||
};
|
||||
saveReminder();
|
||||
|
||||
function toggleSaveReminder() {
|
||||
if (saveReminder.status) {
|
||||
tip('Save reminder is turned off. Press CTRL+Q again to re-initiate', true, 'warn', 2000);
|
||||
clearInterval(saveReminder.reminder);
|
||||
localStorage.setItem('noReminder', true);
|
||||
saveReminder.status = 0;
|
||||
} else {
|
||||
tip('Save reminder is turned on. Press CTRL+Q to turn off', true, 'warn', 2000);
|
||||
localStorage.removeItem('noReminder');
|
||||
saveReminder();
|
||||
}
|
||||
}
|
||||
159
modules/ui/3d.js
159
modules/ui/3d.js
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,8 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
class Battle {
|
||||
constructor(attacker, defender) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
closeDialogs('.stable');
|
||||
customization = 13; // enter customization to avoid unwanted dialog closing
|
||||
|
||||
Battle.prototype.context = this; // store context
|
||||
|
|
@ -14,21 +14,21 @@ class Battle {
|
|||
this.defenders = {regiments: [], distances: [], morale: 100, casualties: 0, power: 0};
|
||||
|
||||
this.addHeaders();
|
||||
this.addRegiment("attackers", attacker);
|
||||
this.addRegiment("defenders", defender);
|
||||
this.addRegiment('attackers', attacker);
|
||||
this.addRegiment('defenders', defender);
|
||||
this.place = this.definePlace();
|
||||
this.defineType();
|
||||
this.name = this.defineName();
|
||||
this.randomize();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.calculateStrength('attackers');
|
||||
this.calculateStrength('defenders');
|
||||
this.getInitialMorale();
|
||||
|
||||
$("#battleScreen").dialog({
|
||||
$('#battleScreen').dialog({
|
||||
title: this.name,
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: "center", at: "center", of: "#map"},
|
||||
position: {my: 'center', at: 'center', of: '#map'},
|
||||
close: () => Battle.prototype.context.cancelResults()
|
||||
});
|
||||
|
||||
|
|
@ -36,42 +36,42 @@ class Battle {
|
|||
modules.Battle = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("battleType").addEventListener("click", ev => this.toggleChange(ev));
|
||||
document.getElementById("battleType").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changeType(ev));
|
||||
document.getElementById("battleNameShow").addEventListener("click", () => Battle.prototype.context.showNameSection());
|
||||
document.getElementById("battleNamePlace").addEventListener("change", ev => (Battle.prototype.context.place = ev.target.value));
|
||||
document.getElementById("battleNameFull").addEventListener("change", ev => Battle.prototype.context.changeName(ev));
|
||||
document.getElementById("battleNameCulture").addEventListener("click", () => Battle.prototype.context.generateName("culture"));
|
||||
document.getElementById("battleNameRandom").addEventListener("click", () => Battle.prototype.context.generateName("random"));
|
||||
document.getElementById("battleNameHide").addEventListener("click", this.hideNameSection);
|
||||
document.getElementById("battleAddRegiment").addEventListener("click", this.addSide);
|
||||
document.getElementById("battleRoll").addEventListener("click", () => Battle.prototype.context.randomize());
|
||||
document.getElementById("battleRun").addEventListener("click", () => Battle.prototype.context.run());
|
||||
document.getElementById("battleApply").addEventListener("click", () => Battle.prototype.context.applyResults());
|
||||
document.getElementById("battleCancel").addEventListener("click", () => Battle.prototype.context.cancelResults());
|
||||
document.getElementById("battleWiki").addEventListener("click", () => wiki("Battle-Simulator"));
|
||||
document.getElementById('battleType').addEventListener('click', (ev) => this.toggleChange(ev));
|
||||
document.getElementById('battleType').nextElementSibling.addEventListener('click', (ev) => Battle.prototype.context.changeType(ev));
|
||||
document.getElementById('battleNameShow').addEventListener('click', () => Battle.prototype.context.showNameSection());
|
||||
document.getElementById('battleNamePlace').addEventListener('change', (ev) => (Battle.prototype.context.place = ev.target.value));
|
||||
document.getElementById('battleNameFull').addEventListener('change', (ev) => Battle.prototype.context.changeName(ev));
|
||||
document.getElementById('battleNameCulture').addEventListener('click', () => Battle.prototype.context.generateName('culture'));
|
||||
document.getElementById('battleNameRandom').addEventListener('click', () => Battle.prototype.context.generateName('random'));
|
||||
document.getElementById('battleNameHide').addEventListener('click', this.hideNameSection);
|
||||
document.getElementById('battleAddRegiment').addEventListener('click', this.addSide);
|
||||
document.getElementById('battleRoll').addEventListener('click', () => Battle.prototype.context.randomize());
|
||||
document.getElementById('battleRun').addEventListener('click', () => Battle.prototype.context.run());
|
||||
document.getElementById('battleApply').addEventListener('click', () => Battle.prototype.context.applyResults());
|
||||
document.getElementById('battleCancel').addEventListener('click', () => Battle.prototype.context.cancelResults());
|
||||
document.getElementById('battleWiki').addEventListener('click', () => wiki('Battle-Simulator'));
|
||||
|
||||
document.getElementById("battlePhase_attackers").addEventListener("click", ev => this.toggleChange(ev));
|
||||
document.getElementById("battlePhase_attackers").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "attackers"));
|
||||
document.getElementById("battlePhase_defenders").addEventListener("click", ev => this.toggleChange(ev));
|
||||
document.getElementById("battlePhase_defenders").nextElementSibling.addEventListener("click", ev => Battle.prototype.context.changePhase(ev, "defenders"));
|
||||
document.getElementById("battleDie_attackers").addEventListener("click", () => Battle.prototype.context.rollDie("attackers"));
|
||||
document.getElementById("battleDie_defenders").addEventListener("click", () => Battle.prototype.context.rollDie("defenders"));
|
||||
document.getElementById('battlePhase_attackers').addEventListener('click', (ev) => this.toggleChange(ev));
|
||||
document.getElementById('battlePhase_attackers').nextElementSibling.addEventListener('click', (ev) => Battle.prototype.context.changePhase(ev, 'attackers'));
|
||||
document.getElementById('battlePhase_defenders').addEventListener('click', (ev) => this.toggleChange(ev));
|
||||
document.getElementById('battlePhase_defenders').nextElementSibling.addEventListener('click', (ev) => Battle.prototype.context.changePhase(ev, 'defenders'));
|
||||
document.getElementById('battleDie_attackers').addEventListener('click', () => Battle.prototype.context.rollDie('attackers'));
|
||||
document.getElementById('battleDie_defenders').addEventListener('click', () => Battle.prototype.context.rollDie('defenders'));
|
||||
}
|
||||
|
||||
defineType() {
|
||||
const attacker = this.attackers.regiments[0];
|
||||
const defender = this.defenders.regiments[0];
|
||||
const getType = () => {
|
||||
const typesA = Object.keys(attacker.u).map(name => options.military.find(u => u.name === name).type);
|
||||
const typesD = Object.keys(defender.u).map(name => options.military.find(u => u.name === name).type);
|
||||
const typesA = Object.keys(attacker.u).map((name) => options.military.find((u) => u.name === name).type);
|
||||
const typesD = Object.keys(defender.u).map((name) => options.military.find((u) => u.name === name).type);
|
||||
|
||||
if (attacker.n && defender.n) return "naval"; // attacker and defender are navals
|
||||
if (typesA.every(t => t === "aviation") && typesD.every(t => t === "aviation")) return "air"; // if attackers and defender have only aviation units
|
||||
if (attacker.n && !defender.n && typesA.some(t => t !== "naval")) return "landing"; // if attacked is naval with non-naval units and defender is not naval
|
||||
if (!defender.n && pack.burgs[pack.cells.burg[this.cell]].walls) return "siege"; // defender is in walled town
|
||||
if (P(0.1) && [5, 6, 7, 8, 9, 12].includes(pack.cells.biome[this.cell])) return "ambush"; // 20% if defenders are in forest or marshes
|
||||
return "field";
|
||||
if (attacker.n && defender.n) return 'naval'; // attacker and defender are navals
|
||||
if (typesA.every((t) => t === 'aviation') && typesD.every((t) => t === 'aviation')) return 'air'; // if attackers and defender have only aviation units
|
||||
if (attacker.n && !defender.n && typesA.some((t) => t !== 'naval')) return 'landing'; // if attacked is naval with non-naval units and defender is not naval
|
||||
if (!defender.n && pack.burgs[pack.cells.burg[this.cell]].walls) return 'siege'; // defender is in walled town
|
||||
if (P(0.1) && [5, 6, 7, 8, 9, 12].includes(pack.cells.biome[this.cell])) return 'ambush'; // 20% if defenders are in forest or marshes
|
||||
return 'field';
|
||||
};
|
||||
|
||||
this.type = getType();
|
||||
|
|
@ -79,25 +79,25 @@ class Battle {
|
|||
}
|
||||
|
||||
setType() {
|
||||
document.getElementById("battleType").className = "icon-button-" + this.type;
|
||||
document.getElementById('battleType').className = 'icon-button-' + this.type;
|
||||
|
||||
const sideSpecific = document.getElementById("battlePhases_" + this.type + "_attackers");
|
||||
const attackers = sideSpecific ? sideSpecific.content : document.getElementById("battlePhases_" + this.type).content;
|
||||
const defenders = sideSpecific ? document.getElementById("battlePhases_" + this.type + "_defenders").content : attackers;
|
||||
const sideSpecific = document.getElementById('battlePhases_' + this.type + '_attackers');
|
||||
const attackers = sideSpecific ? sideSpecific.content : document.getElementById('battlePhases_' + this.type).content;
|
||||
const defenders = sideSpecific ? document.getElementById('battlePhases_' + this.type + '_defenders').content : attackers;
|
||||
|
||||
document.getElementById("battlePhase_attackers").nextElementSibling.innerHTML = "";
|
||||
document.getElementById("battlePhase_defenders").nextElementSibling.innerHTML = "";
|
||||
document.getElementById("battlePhase_attackers").nextElementSibling.append(attackers.cloneNode(true));
|
||||
document.getElementById("battlePhase_defenders").nextElementSibling.append(defenders.cloneNode(true));
|
||||
document.getElementById('battlePhase_attackers').nextElementSibling.innerHTML = '';
|
||||
document.getElementById('battlePhase_defenders').nextElementSibling.innerHTML = '';
|
||||
document.getElementById('battlePhase_attackers').nextElementSibling.append(attackers.cloneNode(true));
|
||||
document.getElementById('battlePhase_defenders').nextElementSibling.append(defenders.cloneNode(true));
|
||||
}
|
||||
|
||||
definePlace() {
|
||||
const cells = pack.cells,
|
||||
i = this.cell;
|
||||
const burg = cells.burg[i] ? pack.burgs[cells.burg[i]].name : null;
|
||||
const getRiver = i => {
|
||||
const river = pack.rivers.find(r => r.i === i);
|
||||
return river.name + " " + river.type;
|
||||
const getRiver = (i) => {
|
||||
const river = pack.rivers.find((r) => r.i === i);
|
||||
return river.name + ' ' + river.type;
|
||||
};
|
||||
const river = !burg && cells.r[i] ? getRiver(cells.r[i]) : null;
|
||||
const proper = burg || river ? null : Names.getCulture(cells.culture[this.cell]);
|
||||
|
|
@ -105,28 +105,28 @@ class Battle {
|
|||
}
|
||||
|
||||
defineName() {
|
||||
if (this.type === "field") return "Battle of " + this.place;
|
||||
if (this.type === "naval") return "Naval Battle of " + this.place;
|
||||
if (this.type === "siege") return "Siege of " + this.place;
|
||||
if (this.type === "ambush") return this.place + " Ambush";
|
||||
if (this.type === "landing") return this.place + " Landing";
|
||||
if (this.type === "air") return `${this.place} ${P(0.8) ? "Air Battle" : "Dogfight"}`;
|
||||
if (this.type === 'field') return 'Battle of ' + this.place;
|
||||
if (this.type === 'naval') return 'Naval Battle of ' + this.place;
|
||||
if (this.type === 'siege') return 'Siege of ' + this.place;
|
||||
if (this.type === 'ambush') return this.place + ' Ambush';
|
||||
if (this.type === 'landing') return this.place + ' Landing';
|
||||
if (this.type === 'air') return `${this.place} ${P(0.8) ? 'Air Battle' : 'Dogfight'}`;
|
||||
}
|
||||
|
||||
getTypeName() {
|
||||
if (this.type === "field") return "field battle";
|
||||
if (this.type === "naval") return "naval battle";
|
||||
if (this.type === "siege") return "siege";
|
||||
if (this.type === "ambush") return "ambush";
|
||||
if (this.type === "landing") return "landing";
|
||||
if (this.type === "air") return "battle";
|
||||
if (this.type === 'field') return 'field battle';
|
||||
if (this.type === 'naval') return 'naval battle';
|
||||
if (this.type === 'siege') return 'siege';
|
||||
if (this.type === 'ambush') return 'ambush';
|
||||
if (this.type === 'landing') return 'landing';
|
||||
if (this.type === 'air') return 'battle';
|
||||
}
|
||||
|
||||
addHeaders() {
|
||||
let headers = "<thead><tr><th></th><th></th>";
|
||||
let headers = '<thead><tr><th></th><th></th>';
|
||||
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, " "));
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
headers += `<th data-tip="${label}">${u.icon}</th>`;
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ class Battle {
|
|||
|
||||
const state = pack.states[regiment.state];
|
||||
const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScaleInput.value) | 0; // distance between regiment and its base
|
||||
const color = state.color[0] === "#" ? state.color : "#999";
|
||||
const color = state.color[0] === '#' ? state.color : '#999';
|
||||
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect"></rect>
|
||||
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
|
||||
|
|
@ -160,28 +160,28 @@ class Battle {
|
|||
casualties += `<td data-tip="Casualties" style="width: 2.5em; text-align: center; color: red">0</td></tr>`;
|
||||
survivors += `<td data-tip="Survivors" style="width: 2.5em; text-align: center; color: green">${regiment.a || 0}</td></tr>`;
|
||||
|
||||
const div = side === "attackers" ? battleAttackers : battleDefenders;
|
||||
div.innerHTML += body + initial + casualties + survivors + "</tbody>";
|
||||
const div = side === 'attackers' ? battleAttackers : battleDefenders;
|
||||
div.innerHTML += body + initial + casualties + survivors + '</tbody>';
|
||||
this[side].regiments.push(regiment);
|
||||
this[side].distances.push(distance);
|
||||
}
|
||||
|
||||
addSide() {
|
||||
const body = document.getElementById("regimentSelectorBody");
|
||||
const body = document.getElementById('regimentSelectorBody');
|
||||
const context = Battle.prototype.context;
|
||||
const regiments = pack.states
|
||||
.filter(s => s.military && !s.removed)
|
||||
.map(s => s.military)
|
||||
.filter((s) => s.military && !s.removed)
|
||||
.map((s) => s.military)
|
||||
.flat();
|
||||
const distance = reg => rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const isAdded = reg => context.defenders.regiments.some(r => r === reg) || context.attackers.regiments.some(r => r === reg);
|
||||
const distance = (reg) => rn(Math.hypot(context.y - reg.y, context.x - reg.x) * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
const isAdded = (reg) => context.defenders.regiments.some((r) => r === reg) || context.attackers.regiments.some((r) => r === reg);
|
||||
|
||||
body.innerHTML = regiments
|
||||
.map(r => {
|
||||
.map((r) => {
|
||||
const s = pack.states[r.state],
|
||||
added = isAdded(r),
|
||||
dist = added ? "0 " + distanceUnitInput.value : distance(r);
|
||||
return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name}
|
||||
dist = added ? '0 ' + distanceUnitInput.value : distance(r);
|
||||
return `<div ${added ? "class='inactive'" : ''} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name}
|
||||
data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment">
|
||||
<svg width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
<div style="width:6em">${s.name.slice(0, 11)}</div>
|
||||
|
|
@ -191,43 +191,43 @@ class Battle {
|
|||
<div style="width:4em">${dist}</div>
|
||||
</div>`;
|
||||
})
|
||||
.join("");
|
||||
.join('');
|
||||
|
||||
$("#regimentSelectorScreen").dialog({
|
||||
$('#regimentSelectorScreen').dialog({
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
title: "Add regiment to the battle",
|
||||
position: {my: "left center", at: "right+10 center", of: "#battleScreen"},
|
||||
title: 'Add regiment to the battle',
|
||||
position: {my: 'left center', at: 'right+10 center', of: '#battleScreen'},
|
||||
close: addSideClosed,
|
||||
buttons: {
|
||||
"Add to attackers": () => addSideClicked("attackers"),
|
||||
"Add to defenders": () => addSideClicked("defenders"),
|
||||
Cancel: () => $("#regimentSelectorScreen").dialog("close")
|
||||
'Add to attackers': () => addSideClicked('attackers'),
|
||||
'Add to defenders': () => addSideClicked('defenders'),
|
||||
Cancel: () => $('#regimentSelectorScreen').dialog('close')
|
||||
}
|
||||
});
|
||||
|
||||
applySorting(regimentSelectorHeader);
|
||||
body.addEventListener("click", selectLine);
|
||||
body.addEventListener('click', selectLine);
|
||||
|
||||
function selectLine(ev) {
|
||||
if (ev.target.className === "inactive") {
|
||||
tip("Regiment is already in the battle", false, "error");
|
||||
if (ev.target.className === 'inactive') {
|
||||
tip('Regiment is already in the battle', false, 'error');
|
||||
return;
|
||||
}
|
||||
ev.target.classList.toggle("selected");
|
||||
ev.target.classList.toggle('selected');
|
||||
}
|
||||
|
||||
function addSideClicked(side) {
|
||||
const selected = body.querySelectorAll(".selected");
|
||||
const selected = body.querySelectorAll('.selected');
|
||||
if (!selected.length) {
|
||||
tip("Please select a regiment first", false, "error");
|
||||
tip('Please select a regiment first', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$("#regimentSelectorScreen").dialog("close");
|
||||
selected.forEach(line => {
|
||||
$('#regimentSelectorScreen').dialog('close');
|
||||
selected.forEach((line) => {
|
||||
const state = pack.states[line.dataset.s];
|
||||
const regiment = state.military.find(r => r.i == +line.dataset.i);
|
||||
const regiment = state.military.find((r) => r.i == +line.dataset.i);
|
||||
Battle.prototype.addRegiment.call(context, side, regiment);
|
||||
Battle.prototype.calculateStrength.call(context, side);
|
||||
Battle.prototype.getInitialMorale.call(context);
|
||||
|
|
@ -235,7 +235,7 @@ class Battle {
|
|||
// move regiment
|
||||
const defenders = context.defenders.regiments,
|
||||
attackers = context.attackers.regiments;
|
||||
const shift = side === "attackers" ? attackers.length * -8 : (defenders.length - 1) * 8;
|
||||
const shift = side === 'attackers' ? attackers.length * -8 : (defenders.length - 1) * 8;
|
||||
regiment.px = regiment.x;
|
||||
regiment.py = regiment.y;
|
||||
Military.moveRegiment(regiment, defenders[0].x, defenders[0].y + shift);
|
||||
|
|
@ -243,34 +243,34 @@ class Battle {
|
|||
}
|
||||
|
||||
function addSideClosed() {
|
||||
body.innerHTML = "";
|
||||
body.removeEventListener("click", selectLine);
|
||||
body.innerHTML = '';
|
||||
body.removeEventListener('click', selectLine);
|
||||
}
|
||||
}
|
||||
|
||||
showNameSection() {
|
||||
document.querySelectorAll("#battleBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("battleNameSection").style.display = "inline-block";
|
||||
document.querySelectorAll('#battleBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('battleNameSection').style.display = 'inline-block';
|
||||
|
||||
document.getElementById("battleNamePlace").value = this.place;
|
||||
document.getElementById("battleNameFull").value = this.name;
|
||||
document.getElementById('battleNamePlace').value = this.place;
|
||||
document.getElementById('battleNameFull').value = this.name;
|
||||
}
|
||||
|
||||
hideNameSection() {
|
||||
document.querySelectorAll("#battleBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("battleNameSection").style.display = "none";
|
||||
document.querySelectorAll('#battleBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('battleNameSection').style.display = 'none';
|
||||
}
|
||||
|
||||
changeName(ev) {
|
||||
this.name = ev.target.value;
|
||||
$("#battleScreen").dialog({title: this.name});
|
||||
$('#battleScreen').dialog({title: this.name});
|
||||
}
|
||||
|
||||
generateName(type) {
|
||||
const place = type === "culture" ? Names.getCulture(pack.cells.culture[this.cell], null, null, "") : Names.getBase(rand(nameBases.length - 1));
|
||||
document.getElementById("battleNamePlace").value = this.place = place;
|
||||
document.getElementById("battleNameFull").value = this.name = this.defineName();
|
||||
$("#battleScreen").dialog({title: this.name});
|
||||
const place = type === 'culture' ? Names.getCulture(pack.cells.culture[this.cell], null, null, '') : Names.getBase(rand(nameBases.length - 1));
|
||||
document.getElementById('battleNamePlace').value = this.place = place;
|
||||
document.getElementById('battleNameFull').value = this.name = this.defineName();
|
||||
$('#battleScreen').dialog({title: this.name});
|
||||
}
|
||||
|
||||
getJoinedForces(regiments) {
|
||||
|
|
@ -324,38 +324,38 @@ class Battle {
|
|||
const forces = this.getJoinedForces(this[side].regiments);
|
||||
const phase = this[side].phase;
|
||||
const adjuster = Math.max(populationRate / 10, 10); // population adjuster, by default 100
|
||||
this[side].power = d3.sum(options.military.map(u => (forces[u.name] || 0) * u.power * scheme[phase][u.type])) / adjuster;
|
||||
this[side].power = d3.sum(options.military.map((u) => (forces[u.name] || 0) * u.power * scheme[phase][u.type])) / adjuster;
|
||||
const UIvalue = this[side].power ? Math.max(this[side].power | 0, 1) : 0;
|
||||
document.getElementById("battlePower_" + side).innerHTML = UIvalue;
|
||||
document.getElementById('battlePower_' + side).innerHTML = UIvalue;
|
||||
}
|
||||
|
||||
getInitialMorale() {
|
||||
const powerFee = diff => Math.min(Math.max(100 - diff ** 1.5 * 10 + 10, 50), 100);
|
||||
const distanceFee = dist => Math.min(d3.mean(dist) / 50, 15);
|
||||
const powerFee = (diff) => minmax(100 - diff ** 1.5 * 10 + 10, 50, 100);
|
||||
const distanceFee = (dist) => Math.min(d3.mean(dist) / 50, 15);
|
||||
const powerDiff = this.defenders.power / this.attackers.power;
|
||||
this.attackers.morale = powerFee(powerDiff) - distanceFee(this.attackers.distances);
|
||||
this.defenders.morale = powerFee(1 / powerDiff) - distanceFee(this.defenders.distances);
|
||||
this.updateMorale("attackers");
|
||||
this.updateMorale("defenders");
|
||||
this.updateMorale('attackers');
|
||||
this.updateMorale('defenders');
|
||||
}
|
||||
|
||||
updateMorale(side) {
|
||||
const morale = document.getElementById("battleMorale_" + side);
|
||||
morale.dataset.tip = morale.dataset.tip.replace(morale.value, "");
|
||||
const morale = document.getElementById('battleMorale_' + side);
|
||||
morale.dataset.tip = morale.dataset.tip.replace(morale.value, '');
|
||||
morale.value = this[side].morale | 0;
|
||||
morale.dataset.tip += morale.value;
|
||||
}
|
||||
|
||||
randomize() {
|
||||
this.rollDie("attackers");
|
||||
this.rollDie("defenders");
|
||||
this.rollDie('attackers');
|
||||
this.rollDie('defenders');
|
||||
this.selectPhase();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.calculateStrength('attackers');
|
||||
this.calculateStrength('defenders');
|
||||
}
|
||||
|
||||
rollDie(side) {
|
||||
const el = document.getElementById("battleDie_" + side);
|
||||
const el = document.getElementById('battleDie_' + side);
|
||||
const prev = +el.innerHTML;
|
||||
do {
|
||||
el.innerHTML = rand(1, 6);
|
||||
|
|
@ -369,131 +369,131 @@ class Battle {
|
|||
const powerRatio = this.attackers.power / this.defenders.power;
|
||||
|
||||
const getFieldBattlePhase = () => {
|
||||
const prev = [this.attackers.phase || "skirmish", this.defenders.phase || "skirmish"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'skirmish', this.defenders.phase || 'skirmish']; // previous phase
|
||||
|
||||
// chance if moral < 25
|
||||
if (P(1 - morale[0] / 25)) return ["retreat", "pursue"];
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"];
|
||||
if (P(1 - morale[0] / 25)) return ['retreat', 'pursue'];
|
||||
if (P(1 - morale[1] / 25)) return ['pursue', 'retreat'];
|
||||
|
||||
// skirmish phase continuation depends on ranged forces number
|
||||
if (prev[0] === "skirmish" && prev[1] === "skirmish") {
|
||||
if (prev[0] === 'skirmish' && prev[1] === 'skirmish') {
|
||||
const forces = this.getJoinedForces(this.attackers.regiments.concat(this.defenders.regiments));
|
||||
const total = d3.sum(Object.values(forces)); // total forces
|
||||
const ranged =
|
||||
d3.sum(
|
||||
options.military
|
||||
.filter(u => u.type === "ranged")
|
||||
.map(u => u.name)
|
||||
.map(u => forces[u])
|
||||
.filter((u) => u.type === 'ranged')
|
||||
.map((u) => u.name)
|
||||
.map((u) => forces[u])
|
||||
) / total; // ranged units
|
||||
if (P(ranged) || P(0.8 - i / 10)) return ["skirmish", "skirmish"];
|
||||
if (P(ranged) || P(0.8 - i / 10)) return ['skirmish', 'skirmish'];
|
||||
}
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
return ['melee', 'melee']; // default option
|
||||
};
|
||||
|
||||
const getNavalBattlePhase = () => {
|
||||
const prev = [this.attackers.phase || "shelling", this.defenders.phase || "shelling"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'shelling', this.defenders.phase || 'shelling']; // previous phase
|
||||
|
||||
if (prev[0] === "withdrawal") return ["withdrawal", "chase"];
|
||||
if (prev[0] === "chase") return ["chase", "withdrawal"];
|
||||
if (prev[0] === 'withdrawal') return ['withdrawal', 'chase'];
|
||||
if (prev[0] === 'chase') return ['chase', 'withdrawal'];
|
||||
|
||||
// withdrawal phase when power imbalanced
|
||||
if (!prev[0] === "boarding") {
|
||||
if (powerRatio < 0.5 || (P(this.attackers.casualties) && powerRatio < 1)) return ["withdrawal", "chase"];
|
||||
if (powerRatio > 2 || (P(this.defenders.casualties) && powerRatio > 1)) return ["chase", "withdrawal"];
|
||||
if (!prev[0] === 'boarding') {
|
||||
if (powerRatio < 0.5 || (P(this.attackers.casualties) && powerRatio < 1)) return ['withdrawal', 'chase'];
|
||||
if (powerRatio > 2 || (P(this.defenders.casualties) && powerRatio > 1)) return ['chase', 'withdrawal'];
|
||||
}
|
||||
|
||||
// boarding phase can start from 2nd iteration
|
||||
if (prev[0] === "boarding" || P(i / 10 - 0.1)) return ["boarding", "boarding"];
|
||||
if (prev[0] === 'boarding' || P(i / 10 - 0.1)) return ['boarding', 'boarding'];
|
||||
|
||||
return ["shelling", "shelling"]; // default option
|
||||
return ['shelling', 'shelling']; // default option
|
||||
};
|
||||
|
||||
const getSiegePhase = () => {
|
||||
const prev = [this.attackers.phase || "blockade", this.defenders.phase || "sheltering"]; // previous phase
|
||||
let phase = ["blockade", "sheltering"]; // default phase
|
||||
const prev = [this.attackers.phase || 'blockade', this.defenders.phase || 'sheltering']; // previous phase
|
||||
let phase = ['blockade', 'sheltering']; // default phase
|
||||
|
||||
if (prev[0] === "retreat" || prev[0] === "looting") return prev;
|
||||
if (prev[0] === 'retreat' || prev[0] === 'looting') return prev;
|
||||
|
||||
if (P(1 - morale[0] / 30) && powerRatio < 1) return ["retreat", "pursue"]; // attackers retreat chance if moral < 30
|
||||
if (P(1 - morale[1] / 15)) return ["looting", "surrendering"]; // defenders surrendering chance if moral < 15
|
||||
if (P(1 - morale[0] / 30) && powerRatio < 1) return ['retreat', 'pursue']; // attackers retreat chance if moral < 30
|
||||
if (P(1 - morale[1] / 15)) return ['looting', 'surrendering']; // defenders surrendering chance if moral < 15
|
||||
|
||||
if (P((powerRatio - 1) / 2)) return ["storming", "defense"]; // start storm
|
||||
if (P((powerRatio - 1) / 2)) return ['storming', 'defense']; // start storm
|
||||
|
||||
if (prev[0] !== "storming") {
|
||||
const machinery = options.military.filter(u => u.type === "machinery").map(u => u.name); // machinery units
|
||||
if (prev[0] !== 'storming') {
|
||||
const machinery = options.military.filter((u) => u.type === 'machinery').map((u) => u.name); // machinery units
|
||||
|
||||
const attackers = this.getJoinedForces(this.attackers.regiments);
|
||||
const machineryA = d3.sum(machinery.map(u => attackers[u]));
|
||||
if (i && machineryA && P(0.9)) phase[0] = "bombardment";
|
||||
const machineryA = d3.sum(machinery.map((u) => attackers[u]));
|
||||
if (i && machineryA && P(0.9)) phase[0] = 'bombardment';
|
||||
|
||||
const defenders = this.getJoinedForces(this.defenders.regiments);
|
||||
const machineryD = d3.sum(machinery.map(u => defenders[u]));
|
||||
if (machineryD && P(0.9)) phase[1] = "bombardment";
|
||||
const machineryD = d3.sum(machinery.map((u) => defenders[u]));
|
||||
if (machineryD && P(0.9)) phase[1] = 'bombardment';
|
||||
|
||||
if (i && prev[1] !== "sortie" && machineryD < machineryA && P(0.25) && P(morale[1] / 70)) phase[1] = "sortie"; // defenders sortie
|
||||
if (i && prev[1] !== 'sortie' && machineryD < machineryA && P(0.25) && P(morale[1] / 70)) phase[1] = 'sortie'; // defenders sortie
|
||||
}
|
||||
|
||||
return phase;
|
||||
};
|
||||
|
||||
const getAmbushPhase = () => {
|
||||
const prev = [this.attackers.phase || "shock", this.defenders.phase || "surprise"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'shock', this.defenders.phase || 'surprise']; // previous phase
|
||||
|
||||
if (prev[1] === "surprise" && P(1 - (powerRatio * i) / 5)) return ["shock", "surprise"];
|
||||
if (prev[1] === 'surprise' && P(1 - (powerRatio * i) / 5)) return ['shock', 'surprise'];
|
||||
|
||||
// chance if moral < 25
|
||||
if (P(1 - morale[0] / 25)) return ["retreat", "pursue"];
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"];
|
||||
if (P(1 - morale[0] / 25)) return ['retreat', 'pursue'];
|
||||
if (P(1 - morale[1] / 25)) return ['pursue', 'retreat'];
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
return ['melee', 'melee']; // default option
|
||||
};
|
||||
|
||||
const getLandingPhase = () => {
|
||||
const prev = [this.attackers.phase || "landing", this.defenders.phase || "defense"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'landing', this.defenders.phase || 'defense']; // previous phase
|
||||
|
||||
if (prev[1] === "waiting") return ["flee", "waiting"];
|
||||
if (prev[1] === "pursue") return ["flee", P(0.3) ? "pursue" : "waiting"];
|
||||
if (prev[1] === "retreat") return ["pursue", "retreat"];
|
||||
if (prev[1] === 'waiting') return ['flee', 'waiting'];
|
||||
if (prev[1] === 'pursue') return ['flee', P(0.3) ? 'pursue' : 'waiting'];
|
||||
if (prev[1] === 'retreat') return ['pursue', 'retreat'];
|
||||
|
||||
if (prev[0] === "landing") {
|
||||
const attackers = P(i / 2) ? "melee" : "landing";
|
||||
const defenders = i ? prev[1] : P(0.5) ? "defense" : "shock";
|
||||
if (prev[0] === 'landing') {
|
||||
const attackers = P(i / 2) ? 'melee' : 'landing';
|
||||
const defenders = i ? prev[1] : P(0.5) ? 'defense' : 'shock';
|
||||
return [attackers, defenders];
|
||||
}
|
||||
|
||||
if (P(1 - morale[0] / 40)) return ["flee", "pursue"]; // chance if moral < 40
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"]; // chance if moral < 25
|
||||
if (P(1 - morale[0] / 40)) return ['flee', 'pursue']; // chance if moral < 40
|
||||
if (P(1 - morale[1] / 25)) return ['pursue', 'retreat']; // chance if moral < 25
|
||||
|
||||
return ["melee", "melee"]; // default option
|
||||
return ['melee', 'melee']; // default option
|
||||
};
|
||||
|
||||
const getAirBattlePhase = () => {
|
||||
const prev = [this.attackers.phase || "maneuvering", this.defenders.phase || "maneuvering"]; // previous phase
|
||||
const prev = [this.attackers.phase || 'maneuvering', this.defenders.phase || 'maneuvering']; // previous phase
|
||||
|
||||
// chance if moral < 25
|
||||
if (P(1 - morale[0] / 25)) return ["retreat", "pursue"];
|
||||
if (P(1 - morale[1] / 25)) return ["pursue", "retreat"];
|
||||
if (P(1 - morale[0] / 25)) return ['retreat', 'pursue'];
|
||||
if (P(1 - morale[1] / 25)) return ['pursue', 'retreat'];
|
||||
|
||||
if (prev[0] === "maneuvering" && P(1 - i / 10)) return ["maneuvering", "maneuvering"];
|
||||
if (prev[0] === 'maneuvering' && P(1 - i / 10)) return ['maneuvering', 'maneuvering'];
|
||||
|
||||
return ["dogfight", "dogfight"]; // default option
|
||||
return ['dogfight', 'dogfight']; // default option
|
||||
};
|
||||
|
||||
const phase = (function (type) {
|
||||
switch (type) {
|
||||
case "field":
|
||||
case 'field':
|
||||
return getFieldBattlePhase();
|
||||
case "naval":
|
||||
case 'naval':
|
||||
return getNavalBattlePhase();
|
||||
case "siege":
|
||||
case 'siege':
|
||||
return getSiegePhase();
|
||||
case "ambush":
|
||||
case 'ambush':
|
||||
return getAmbushPhase();
|
||||
case "landing":
|
||||
case 'landing':
|
||||
return getLandingPhase();
|
||||
case "air":
|
||||
case 'air':
|
||||
return getAirBattlePhase();
|
||||
default:
|
||||
getFieldBattlePhase();
|
||||
|
|
@ -503,23 +503,23 @@ class Battle {
|
|||
this.attackers.phase = phase[0];
|
||||
this.defenders.phase = phase[1];
|
||||
|
||||
const buttonA = document.getElementById("battlePhase_attackers");
|
||||
buttonA.className = "icon-button-" + this.attackers.phase;
|
||||
const buttonA = document.getElementById('battlePhase_attackers');
|
||||
buttonA.className = 'icon-button-' + this.attackers.phase;
|
||||
buttonA.dataset.tip = buttonA.nextElementSibling.querySelector("[data-phase='" + phase[0] + "']").dataset.tip;
|
||||
|
||||
const buttonD = document.getElementById("battlePhase_defenders");
|
||||
buttonD.className = "icon-button-" + this.defenders.phase;
|
||||
const buttonD = document.getElementById('battlePhase_defenders');
|
||||
buttonD.className = 'icon-button-' + this.defenders.phase;
|
||||
buttonD.dataset.tip = buttonD.nextElementSibling.querySelector("[data-phase='" + phase[1] + "']").dataset.tip;
|
||||
}
|
||||
|
||||
run() {
|
||||
// validations
|
||||
if (!this.attackers.power) {
|
||||
tip("Attackers army destroyed", false, "warn");
|
||||
tip('Attackers army destroyed', false, 'warn');
|
||||
return;
|
||||
}
|
||||
if (!this.defenders.power) {
|
||||
tip("Defenders army destroyed", false, "warn");
|
||||
tip('Defenders army destroyed', false, 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -558,8 +558,8 @@ class Battle {
|
|||
const casualtiesA = (casualties * defense) / (attack + defense); // attackers casualties, ~5% per iteration
|
||||
const casualtiesD = (casualties * attack) / (attack + defense); // defenders casualties, ~5% per iteration
|
||||
|
||||
this.calculateCasualties("attackers", casualtiesA);
|
||||
this.calculateCasualties("defenders", casualtiesD);
|
||||
this.calculateCasualties('attackers', casualtiesA);
|
||||
this.calculateCasualties('defenders', casualtiesD);
|
||||
this.attackers.casualties += casualtiesA;
|
||||
this.defenders.casualties += casualtiesD;
|
||||
|
||||
|
|
@ -568,14 +568,14 @@ class Battle {
|
|||
this.defenders.morale = Math.max(this.defenders.morale - casualtiesD * 100 - 1, 0);
|
||||
|
||||
// update table values
|
||||
this.updateTable("attackers");
|
||||
this.updateTable("defenders");
|
||||
this.updateTable('attackers');
|
||||
this.updateTable('defenders');
|
||||
|
||||
// prepare for next iteration
|
||||
this.iteration += 1;
|
||||
this.selectPhase();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.calculateStrength('attackers');
|
||||
this.calculateStrength('defenders');
|
||||
}
|
||||
|
||||
calculateCasualties(side, casualties) {
|
||||
|
|
@ -591,9 +591,9 @@ class Battle {
|
|||
|
||||
updateTable(side) {
|
||||
for (const r of this[side].regiments) {
|
||||
const tbody = document.getElementById("battle" + r.state + "-" + r.i);
|
||||
const battleCasualties = tbody.querySelector(".battleCasualties");
|
||||
const battleSurvivors = tbody.querySelector(".battleSurvivors");
|
||||
const tbody = document.getElementById('battle' + r.state + '-' + r.i);
|
||||
const battleCasualties = tbody.querySelector('.battleCasualties');
|
||||
const battleSurvivors = tbody.querySelector('.battleSurvivors');
|
||||
|
||||
let index = 3; // index to find table element easily
|
||||
for (const u of options.military) {
|
||||
|
|
@ -615,35 +615,35 @@ class Battle {
|
|||
|
||||
const hideSection = function () {
|
||||
button.style.opacity = 1;
|
||||
div.style.display = "none";
|
||||
div.style.display = 'none';
|
||||
};
|
||||
if (div.style.display === "block") {
|
||||
if (div.style.display === 'block') {
|
||||
hideSection();
|
||||
return;
|
||||
}
|
||||
|
||||
button.style.opacity = 0.5;
|
||||
div.style.display = "block";
|
||||
div.style.display = 'block';
|
||||
|
||||
document.getElementsByTagName("body")[0].addEventListener("click", hideSection, {once: true});
|
||||
document.getElementsByTagName('body')[0].addEventListener('click', hideSection, {once: true});
|
||||
}
|
||||
|
||||
changeType(ev) {
|
||||
if (ev.target.tagName !== "BUTTON") return;
|
||||
if (ev.target.tagName !== 'BUTTON') return;
|
||||
this.type = ev.target.dataset.type;
|
||||
this.setType();
|
||||
this.selectPhase();
|
||||
this.calculateStrength("attackers");
|
||||
this.calculateStrength("defenders");
|
||||
this.calculateStrength('attackers');
|
||||
this.calculateStrength('defenders');
|
||||
this.name = this.defineName();
|
||||
$("#battleScreen").dialog({title: this.name});
|
||||
$('#battleScreen').dialog({title: this.name});
|
||||
}
|
||||
|
||||
changePhase(ev, side) {
|
||||
if (ev.target.tagName !== "BUTTON") return;
|
||||
if (ev.target.tagName !== 'BUTTON') return;
|
||||
const phase = (this[side].phase = ev.target.dataset.phase);
|
||||
const button = document.getElementById("battlePhase_" + side);
|
||||
button.className = "icon-button-" + phase;
|
||||
const button = document.getElementById('battlePhase_' + side);
|
||||
button.className = 'icon-button-' + phase;
|
||||
button.dataset.tip = ev.target.dataset.tip;
|
||||
this.calculateStrength(side);
|
||||
}
|
||||
|
|
@ -654,34 +654,49 @@ class Battle {
|
|||
const relativeCasualties = this.defenders.casualties / (this.attackers.casualties + this.attackers.casualties);
|
||||
const battleStatus = getBattleStatus(relativeCasualties, maxCasualties);
|
||||
function getBattleStatus(relative, max) {
|
||||
if (isNaN(relative)) return ["standoff", "standoff"]; // if no casualties at all
|
||||
if (max < 0.05) return ["minor skirmishes", "minor skirmishes"];
|
||||
if (relative > 95) return ["attackers flawless victory", "disorderly retreat of defenders"];
|
||||
if (relative > 0.7) return ["attackers decisive victory", "defenders disastrous defeat"];
|
||||
if (relative > 0.6) return ["attackers victory", "defenders defeat"];
|
||||
if (relative > 0.4) return ["stalemate", "stalemate"];
|
||||
if (relative > 0.3) return ["attackers defeat", "defenders victory"];
|
||||
if (relative > 0.5) return ["attackers disastrous defeat", "decisive victory of defenders"];
|
||||
if (relative >= 0) return ["attackers disorderly retreat", "flawless victory of defenders"];
|
||||
return ["stalemate", "stalemate"]; // exception
|
||||
if (isNaN(relative)) return ['standoff', 'standoff']; // if no casualties at all
|
||||
if (max < 0.05) return ['minor skirmishes', 'minor skirmishes'];
|
||||
if (relative > 95) return ['attackers flawless victory', 'disorderly retreat of defenders'];
|
||||
if (relative > 0.7) return ['attackers decisive victory', 'defenders disastrous defeat'];
|
||||
if (relative > 0.6) return ['attackers victory', 'defenders defeat'];
|
||||
if (relative > 0.4) return ['stalemate', 'stalemate'];
|
||||
if (relative > 0.3) return ['attackers defeat', 'defenders victory'];
|
||||
if (relative > 0.5) return ['attackers disastrous defeat', 'decisive victory of defenders'];
|
||||
if (relative >= 0) return ['attackers disorderly retreat', 'flawless victory of defenders'];
|
||||
return ['stalemate', 'stalemate']; // exception
|
||||
}
|
||||
|
||||
this.attackers.regiments.forEach(r => applyResultForSide(r, "attackers"));
|
||||
this.defenders.regiments.forEach(r => applyResultForSide(r, "defenders"));
|
||||
this.attackers.regiments.forEach((r) => applyResultForSide(r, 'attackers'));
|
||||
this.defenders.regiments.forEach((r) => applyResultForSide(r, 'defenders'));
|
||||
|
||||
function applyResultForSide(r, side) {
|
||||
const id = "regiment" + r.state + "-" + r.i;
|
||||
const id = 'regiment' + r.state + '-' + r.i;
|
||||
|
||||
// add result to regiment note
|
||||
const note = notes.find(n => n.id === id);
|
||||
const note = notes.find((n) => n.id === id);
|
||||
if (note) {
|
||||
const status = side === "attackers" ? battleStatus[0] : battleStatus[1];
|
||||
const status = side === 'attackers' ? battleStatus[0] : battleStatus[1];
|
||||
const losses = r.a ? Math.abs(d3.sum(Object.values(r.casualties))) / r.a : 1;
|
||||
const regStatus = losses === 1 ? "is destroyed" : losses > 0.8 ? "is almost completely destroyed" : losses > 0.5 ? "suffered terrible losses" : losses > 0.3 ? "suffered severe losses" : losses > 0.2 ? "suffered heavy losses" : losses > 0.05 ? "suffered significant losses" : losses > 0 ? "suffered unsignificant losses" : "left the battle without loss";
|
||||
const regStatus =
|
||||
losses === 1
|
||||
? 'is destroyed'
|
||||
: losses > 0.8
|
||||
? 'is almost completely destroyed'
|
||||
: losses > 0.5
|
||||
? 'suffered terrible losses'
|
||||
: losses > 0.3
|
||||
? 'suffered severe losses'
|
||||
: losses > 0.2
|
||||
? 'suffered heavy losses'
|
||||
: losses > 0.05
|
||||
? 'suffered significant losses'
|
||||
: losses > 0
|
||||
? 'suffered unsignificant losses'
|
||||
: 'left the battle without loss';
|
||||
const casualties = Object.keys(r.casualties)
|
||||
.map(t => (r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null))
|
||||
.filter(c => c);
|
||||
const casualtiesText = casualties.length ? " Casualties: " + list(casualties) + "." : "";
|
||||
.map((t) => (r.casualties[t] ? `${Math.abs(r.casualties[t])} ${t}` : null))
|
||||
.filter((c) => c);
|
||||
const casualtiesText = casualties.length ? ' Casualties: ' + list(casualties) + '.' : '';
|
||||
const legend = `\r\n\r\n${battleName} (${options.year} ${options.eraShort}): ${status}. The regiment ${regStatus}.${casualtiesText}`;
|
||||
note.legend += legend;
|
||||
}
|
||||
|
|
@ -691,57 +706,47 @@ class Battle {
|
|||
armies.select(`g#${id} > text`).text(Military.getTotal(r)); // update reg box
|
||||
}
|
||||
|
||||
// append battlefield marker
|
||||
void (function addMarkerSymbol() {
|
||||
if (svg.select("#defs-markers").select("#marker_battlefield").size()) return;
|
||||
const symbol = svg.select("#defs-markers").append("symbol").attr("id", "marker_battlefield").attr("viewBox", "0 0 30 30");
|
||||
symbol.append("path").attr("d", "M6,19 l9,10 L24,19").attr("fill", "#000000").attr("stroke", "none");
|
||||
symbol.append("circle").attr("cx", 15).attr("cy", 15).attr("r", 10).attr("fill", "#ffffff").attr("stroke", "#000000").attr("stroke-width", 1);
|
||||
symbol.append("text").attr("x", "50%").attr("y", "52%").attr("fill", "#000000").attr("stroke", "#3200ff").attr("stroke-width", 0).attr("font-size", "12px").attr("dominant-baseline", "central").text("⚔️");
|
||||
})();
|
||||
const i = last(pack.markers)?.i + 1 || 0;
|
||||
{
|
||||
// append battlefield marker
|
||||
const marker = {i, x: this.x, y: this.y, cell: this.cell, icon: '⚔️', type: 'battlefields', dy: 52};
|
||||
pack.markers.push(marker);
|
||||
const markerHTML = drawMarker(marker);
|
||||
document.getElementById('markers').insertAdjacentHTML('beforeend', markerHTML);
|
||||
}
|
||||
|
||||
const getSide = (regs, n) => (regs.length > 1 ? `${n ? "regiments" : "forces"} of ${list([...new Set(regs.map(r => pack.states[r.state].name))])}` : getAdjective(pack.states[regs[0].state].name) + " " + regs[0].name);
|
||||
const getLosses = casualties => Math.min(rn(casualties * 100), 100);
|
||||
const getSide = (regs, n) =>
|
||||
regs.length > 1 ? `${n ? 'regiments' : 'forces'} of ${list([...new Set(regs.map((r) => pack.states[r.state].name))])}` : getAdjective(pack.states[regs[0].state].name) + ' ' + regs[0].name;
|
||||
const getLosses = (casualties) => Math.min(rn(casualties * 100), 100);
|
||||
|
||||
const status = battleStatus[+P(0.7)];
|
||||
const result = `The ${this.getTypeName(this.type)} ended in ${status}`;
|
||||
const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(this.defenders.regiments, 0)}. ${result}.
|
||||
const legend = `${this.name} took place in ${options.year} ${options.eraShort}. It was fought between ${getSide(this.attackers.regiments, 1)} and ${getSide(
|
||||
this.defenders.regiments,
|
||||
0
|
||||
)}. ${result}.
|
||||
\r\nAttackers losses: ${getLosses(this.attackers.casualties)}%, defenders losses: ${getLosses(this.defenders.casualties)}%`;
|
||||
const id = getNextId("markerElement");
|
||||
notes.push({id, name: this.name, legend});
|
||||
notes.push({id: `marker${i}`, name: this.name, legend});
|
||||
|
||||
tip(`${this.name} is over. ${result}`, true, "success", 4000);
|
||||
tip(`${this.name} is over. ${result}`, true, 'success', 4000);
|
||||
|
||||
markers
|
||||
.append("use")
|
||||
.attr("id", id)
|
||||
.attr("xlink:href", "#marker_battlefield")
|
||||
.attr("data-id", "#marker_battlefield")
|
||||
.attr("data-x", this.x)
|
||||
.attr("data-y", this.y)
|
||||
.attr("x", this.x - 15)
|
||||
.attr("y", this.y - 30)
|
||||
.attr("data-size", 1)
|
||||
.attr("width", 30)
|
||||
.attr("height", 30);
|
||||
|
||||
$("#battleScreen").dialog("destroy");
|
||||
$('#battleScreen').dialog('destroy');
|
||||
this.cleanData();
|
||||
}
|
||||
|
||||
cancelResults() {
|
||||
// move regiments back to initial positions
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach(r => Military.moveRegiment(r, r.px, r.py));
|
||||
$("#battleScreen").dialog("close");
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach((r) => Military.moveRegiment(r, r.px, r.py));
|
||||
$('#battleScreen').dialog('close');
|
||||
this.cleanData();
|
||||
}
|
||||
|
||||
cleanData() {
|
||||
battleAttackers.innerHTML = battleDefenders.innerHTML = ""; // clean DOM
|
||||
battleAttackers.innerHTML = battleDefenders.innerHTML = ''; // clean DOM
|
||||
customization = 0; // exit edit mode
|
||||
|
||||
// clean temp data
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach(r => {
|
||||
this.attackers.regiments.concat(this.defenders.regiments).forEach((r) => {
|
||||
delete r.px;
|
||||
delete r.py;
|
||||
delete r.casualties;
|
||||
|
|
|
|||
|
|
@ -1,55 +1,55 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
function editBiomes() {
|
||||
if (customization) return;
|
||||
closeDialogs("#biomesEditor, .stable");
|
||||
if (!layerIsOn("toggleBiomes")) toggleBiomes();
|
||||
if (layerIsOn("toggleStates")) toggleStates();
|
||||
if (layerIsOn("toggleCultures")) toggleCultures();
|
||||
if (layerIsOn("toggleReligions")) toggleReligions();
|
||||
if (layerIsOn("toggleProvinces")) toggleProvinces();
|
||||
closeDialogs('#biomesEditor, .stable');
|
||||
if (!layerIsOn('toggleBiomes')) toggleBiomes();
|
||||
if (layerIsOn('toggleStates')) toggleStates();
|
||||
if (layerIsOn('toggleCultures')) toggleCultures();
|
||||
if (layerIsOn('toggleReligions')) toggleReligions();
|
||||
if (layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
|
||||
const body = document.getElementById("biomesBody");
|
||||
const body = document.getElementById('biomesBody');
|
||||
const animate = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||
refreshBiomesEditor();
|
||||
|
||||
if (modules.editBiomes) return;
|
||||
modules.editBiomes = true;
|
||||
|
||||
$("#biomesEditor").dialog({
|
||||
title: "Biomes Editor",
|
||||
$('#biomesEditor').dialog({
|
||||
title: 'Biomes Editor',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: closeBiomesEditor,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg"}
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("biomesEditorRefresh").addEventListener("click", refreshBiomesEditor);
|
||||
document.getElementById("biomesEditStyle").addEventListener("click", () => editStyle("biomes"));
|
||||
document.getElementById("biomesLegend").addEventListener("click", toggleLegend);
|
||||
document.getElementById("biomesPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("biomesManually").addEventListener("click", enterBiomesCustomizationMode);
|
||||
document.getElementById("biomesManuallyApply").addEventListener("click", applyBiomesChange);
|
||||
document.getElementById("biomesManuallyCancel").addEventListener("click", () => exitBiomesCustomizationMode());
|
||||
document.getElementById("biomesRestore").addEventListener("click", restoreInitialBiomes);
|
||||
document.getElementById("biomesAdd").addEventListener("click", addCustomBiome);
|
||||
document.getElementById("biomesRegenerateReliefIcons").addEventListener("click", regenerateIcons);
|
||||
document.getElementById("biomesExport").addEventListener("click", downloadBiomesData);
|
||||
document.getElementById('biomesEditorRefresh').addEventListener('click', refreshBiomesEditor);
|
||||
document.getElementById('biomesEditStyle').addEventListener('click', () => editStyle('biomes'));
|
||||
document.getElementById('biomesLegend').addEventListener('click', toggleLegend);
|
||||
document.getElementById('biomesPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('biomesManually').addEventListener('click', enterBiomesCustomizationMode);
|
||||
document.getElementById('biomesManuallyApply').addEventListener('click', applyBiomesChange);
|
||||
document.getElementById('biomesManuallyCancel').addEventListener('click', () => exitBiomesCustomizationMode());
|
||||
document.getElementById('biomesRestore').addEventListener('click', restoreInitialBiomes);
|
||||
document.getElementById('biomesAdd').addEventListener('click', addCustomBiome);
|
||||
document.getElementById('biomesRegenerateReliefIcons').addEventListener('click', regenerateIcons);
|
||||
document.getElementById('biomesExport').addEventListener('click', downloadBiomesData);
|
||||
|
||||
body.addEventListener("click", function (ev) {
|
||||
body.addEventListener('click', function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList;
|
||||
if (cl.contains("fillRect")) biomeChangeColor(el);
|
||||
else if (cl.contains("icon-info-circled")) openWiki(el);
|
||||
else if (cl.contains("icon-trash-empty")) removeCustomBiome(el);
|
||||
if (cl.contains('fillRect')) biomeChangeColor(el);
|
||||
else if (cl.contains('icon-info-circled')) openWiki(el);
|
||||
else if (cl.contains('icon-trash-empty')) removeCustomBiome(el);
|
||||
if (customization === 6) selectBiomeOnLineClick(el);
|
||||
});
|
||||
|
||||
body.addEventListener("change", function (ev) {
|
||||
body.addEventListener('change', function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList;
|
||||
if (cl.contains("biomeName")) biomeChangeName(el);
|
||||
else if (cl.contains("biomeHabitability")) biomeChangeHabitability(el);
|
||||
if (cl.contains('biomeName')) biomeChangeName(el);
|
||||
else if (cl.contains('biomeHabitability')) biomeChangeHabitability(el);
|
||||
});
|
||||
|
||||
function refreshBiomesEditor() {
|
||||
|
|
@ -76,14 +76,14 @@ function editBiomes() {
|
|||
}
|
||||
|
||||
function biomesEditorAddLines() {
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
const b = biomesData;
|
||||
let lines = "",
|
||||
let lines = '',
|
||||
totalArea = 0,
|
||||
totalPopulation = 0;
|
||||
|
||||
for (const i of b.i) {
|
||||
if (!i || biomesData.name[i] === "removed") continue; // ignore water and removed biomes
|
||||
if (!i || biomesData.name[i] === 'removed') continue; // ignore water and removed biomes
|
||||
const area = b.area[i] * distanceScaleInput.value ** 2;
|
||||
const rural = b.rural[i] * populationRate;
|
||||
const urban = b.urban[i] * populationRate * urbanization;
|
||||
|
|
@ -94,7 +94,9 @@ function editBiomes() {
|
|||
|
||||
lines += `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability="${b.habitability[i]}"
|
||||
data-cells=${b.cells[i]} data-area=${area} data-population=${population} data-color=${b.color[i]}>
|
||||
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${b.color[i]}" class="fillRect pointer"></svg>
|
||||
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
b.color[i]
|
||||
}" class="fillRect pointer"></svg>
|
||||
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
|
||||
<span data-tip="Biome habitability percent" class="hide">%</span>
|
||||
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 class="biomeHabitability hide" value=${b.habitability[i]}>
|
||||
|
|
@ -105,40 +107,40 @@ function editBiomes() {
|
|||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="biomePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Open Wikipedia article about the biome" class="icon-info-circled pointer hide"></span>
|
||||
${i > 12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ""}
|
||||
${i > 12 && !b.cells[i] ? '<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>' : ''}
|
||||
</div>`;
|
||||
}
|
||||
body.innerHTML = lines;
|
||||
|
||||
// update footer
|
||||
biomesFooterBiomes.innerHTML = body.querySelectorAll(":scope > div").length;
|
||||
biomesFooterCells.innerHTML = pack.cells.h.filter(h => h >= 20).length;
|
||||
biomesFooterBiomes.innerHTML = body.querySelectorAll(':scope > div').length;
|
||||
biomesFooterCells.innerHTML = pack.cells.h.filter((h) => h >= 20).length;
|
||||
biomesFooterArea.innerHTML = si(totalArea) + unit;
|
||||
biomesFooterPopulation.innerHTML = si(totalPopulation);
|
||||
biomesFooterArea.dataset.area = totalArea;
|
||||
biomesFooterPopulation.dataset.population = totalPopulation;
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.biomes").forEach(el => el.addEventListener("mouseenter", ev => biomeHighlightOn(ev)));
|
||||
body.querySelectorAll("div.biomes").forEach(el => el.addEventListener("mouseleave", ev => biomeHighlightOff(ev)));
|
||||
body.querySelectorAll('div.biomes').forEach((el) => el.addEventListener('mouseenter', (ev) => biomeHighlightOn(ev)));
|
||||
body.querySelectorAll('div.biomes').forEach((el) => el.addEventListener('mouseleave', (ev) => biomeHighlightOff(ev)));
|
||||
|
||||
if (body.dataset.type === "percentage") {
|
||||
body.dataset.type = "absolute";
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(biomesHeader);
|
||||
$("#biomesEditor").dialog({width: fitContent()});
|
||||
$('#biomesEditor').dialog({width: fitContent()});
|
||||
}
|
||||
|
||||
function biomeHighlightOn(event) {
|
||||
if (customization === 6) return;
|
||||
const biome = +event.target.dataset.id;
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.select('#biome' + biome)
|
||||
.raise()
|
||||
.transition(animate)
|
||||
.attr("stroke-width", 2)
|
||||
.attr("stroke", "#cd4c11");
|
||||
.attr('stroke-width', 2)
|
||||
.attr('stroke', '#cd4c11');
|
||||
}
|
||||
|
||||
function biomeHighlightOff(event) {
|
||||
|
|
@ -146,23 +148,23 @@ function editBiomes() {
|
|||
const biome = +event.target.dataset.id;
|
||||
const color = biomesData.color[biome];
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.select('#biome' + biome)
|
||||
.transition()
|
||||
.attr("stroke-width", 0.7)
|
||||
.attr("stroke", color);
|
||||
.attr('stroke-width', 0.7)
|
||||
.attr('stroke', color);
|
||||
}
|
||||
|
||||
function biomeChangeColor(el) {
|
||||
const currentFill = el.getAttribute("fill");
|
||||
const currentFill = el.getAttribute('fill');
|
||||
const biome = +el.parentNode.parentNode.dataset.id;
|
||||
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
el.setAttribute('fill', fill);
|
||||
biomesData.color[biome] = fill;
|
||||
biomes
|
||||
.select("#biome" + biome)
|
||||
.attr("fill", fill)
|
||||
.attr("stroke", fill);
|
||||
.select('#biome' + biome)
|
||||
.attr('fill', fill)
|
||||
.attr('stroke', fill);
|
||||
};
|
||||
|
||||
openPicker(currentFill, callback);
|
||||
|
|
@ -179,7 +181,7 @@ function editBiomes() {
|
|||
const failed = isNaN(+el.value) || +el.value < 0 || +el.value > 9999;
|
||||
if (failed) {
|
||||
el.value = biomesData.habitability[biome];
|
||||
tip("Please provide a valid number in range 0-9999", false, "error");
|
||||
tip('Please provide a valid number in range 0-9999', false, 'error');
|
||||
return;
|
||||
}
|
||||
biomesData.habitability[biome] = +el.value;
|
||||
|
|
@ -190,69 +192,69 @@ function editBiomes() {
|
|||
|
||||
function openWiki(el) {
|
||||
const name = el.parentNode.dataset.name;
|
||||
if (name === "Custom" || !name) {
|
||||
tip("Please provide a biome name", false, "error");
|
||||
if (name === 'Custom' || !name) {
|
||||
tip('Please provide a biome name', false, 'error');
|
||||
return;
|
||||
}
|
||||
const wiki = "https://en.wikipedia.org/wiki/";
|
||||
const wiki = 'https://en.wikipedia.org/wiki/';
|
||||
|
||||
switch (name) {
|
||||
case "Hot desert":
|
||||
openURL(wiki + "Desert_climate#Hot_desert_climates");
|
||||
case "Cold desert":
|
||||
openURL(wiki + "Desert_climate#Cold_desert_climates");
|
||||
case "Savanna":
|
||||
openURL(wiki + "Tropical_and_subtropical_grasslands,_savannas,_and_shrublands");
|
||||
case "Grassland":
|
||||
openURL(wiki + "Temperate_grasslands,_savannas,_and_shrublands");
|
||||
case "Tropical seasonal forest":
|
||||
openURL(wiki + "Seasonal_tropical_forest");
|
||||
case "Temperate deciduous forest":
|
||||
openURL(wiki + "Temperate_deciduous_forest");
|
||||
case "Tropical rainforest":
|
||||
openURL(wiki + "Tropical_rainforest");
|
||||
case "Temperate rainforest":
|
||||
openURL(wiki + "Temperate_rainforest");
|
||||
case "Taiga":
|
||||
openURL(wiki + "Taiga");
|
||||
case "Tundra":
|
||||
openURL(wiki + "Tundra");
|
||||
case "Glacier":
|
||||
openURL(wiki + "Glacier");
|
||||
case "Wetland":
|
||||
openURL(wiki + "Wetland");
|
||||
case 'Hot desert':
|
||||
openURL(wiki + 'Desert_climate#Hot_desert_climates');
|
||||
case 'Cold desert':
|
||||
openURL(wiki + 'Desert_climate#Cold_desert_climates');
|
||||
case 'Savanna':
|
||||
openURL(wiki + 'Tropical_and_subtropical_grasslands,_savannas,_and_shrublands');
|
||||
case 'Grassland':
|
||||
openURL(wiki + 'Temperate_grasslands,_savannas,_and_shrublands');
|
||||
case 'Tropical seasonal forest':
|
||||
openURL(wiki + 'Seasonal_tropical_forest');
|
||||
case 'Temperate deciduous forest':
|
||||
openURL(wiki + 'Temperate_deciduous_forest');
|
||||
case 'Tropical rainforest':
|
||||
openURL(wiki + 'Tropical_rainforest');
|
||||
case 'Temperate rainforest':
|
||||
openURL(wiki + 'Temperate_rainforest');
|
||||
case 'Taiga':
|
||||
openURL(wiki + 'Taiga');
|
||||
case 'Tundra':
|
||||
openURL(wiki + 'Tundra');
|
||||
case 'Glacier':
|
||||
openURL(wiki + 'Glacier');
|
||||
case 'Wetland':
|
||||
openURL(wiki + 'Wetland');
|
||||
default:
|
||||
openURL(`https://en.wikipedia.org/w/index.php?search=${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
if (legend.selectAll("*").size()) {
|
||||
if (legend.selectAll('*').size()) {
|
||||
clearLegend();
|
||||
return;
|
||||
} // hide legend
|
||||
const d = biomesData;
|
||||
const data = Array.from(d.i)
|
||||
.filter(i => d.cells[i])
|
||||
.filter((i) => d.cells[i])
|
||||
.sort((a, b) => d.area[b] - d.area[a])
|
||||
.map(i => [i, d.color[i], d.name[i]]);
|
||||
drawLegend("Biomes", data);
|
||||
.map((i) => [i, d.color[i], d.name[i]]);
|
||||
drawLegend('Biomes', data);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const totalCells = +biomesFooterCells.innerHTML;
|
||||
const totalArea = +biomesFooterArea.dataset.area;
|
||||
const totalPopulation = +biomesFooterPopulation.dataset.population;
|
||||
|
||||
body.querySelectorAll(":scope> div").forEach(function (el) {
|
||||
el.querySelector(".biomeCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100) + "%";
|
||||
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100) + "%";
|
||||
el.querySelector(".biomePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + "%";
|
||||
body.querySelectorAll(':scope> div').forEach(function (el) {
|
||||
el.querySelector('.biomeCells').innerHTML = rn((+el.dataset.cells / totalCells) * 100) + '%';
|
||||
el.querySelector('.biomeArea').innerHTML = rn((+el.dataset.area / totalArea) * 100) + '%';
|
||||
el.querySelector('.biomePopulation').innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + '%';
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
body.dataset.type = 'absolute';
|
||||
biomesEditorAddLines();
|
||||
}
|
||||
}
|
||||
|
|
@ -261,14 +263,14 @@ function editBiomes() {
|
|||
const b = biomesData,
|
||||
i = biomesData.i.length;
|
||||
if (i > 254) {
|
||||
tip("Maximum number of biomes reached (255), data cleansing is required", false, "error");
|
||||
tip('Maximum number of biomes reached (255), data cleansing is required', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
b.i.push(i);
|
||||
b.color.push(getRandomColor());
|
||||
b.habitability.push(50);
|
||||
b.name.push("Custom");
|
||||
b.name.push('Custom');
|
||||
b.iconsDensity.push(0);
|
||||
b.icons.push([]);
|
||||
b.cost.push(50);
|
||||
|
|
@ -278,7 +280,7 @@ function editBiomes() {
|
|||
b.cells.push(0);
|
||||
b.area.push(0);
|
||||
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
const line = `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability=${b.habitability[i]} data-cells=0 data-area=0 data-population=0 data-color=${b.color[i]}>
|
||||
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${b.color[i]}" class="fillRect pointer"></svg>
|
||||
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
|
||||
|
|
@ -293,84 +295,84 @@ function editBiomes() {
|
|||
<span data-tip="Remove the custom biome" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
|
||||
body.insertAdjacentHTML("beforeend", line);
|
||||
biomesFooterBiomes.innerHTML = body.querySelectorAll(":scope > div").length;
|
||||
$("#biomesEditor").dialog({width: fitContent()});
|
||||
body.insertAdjacentHTML('beforeend', line);
|
||||
biomesFooterBiomes.innerHTML = body.querySelectorAll(':scope > div').length;
|
||||
$('#biomesEditor').dialog({width: fitContent()});
|
||||
}
|
||||
|
||||
function removeCustomBiome(el) {
|
||||
const biome = +el.parentNode.dataset.id;
|
||||
el.parentNode.remove();
|
||||
biomesData.name[biome] = "removed";
|
||||
biomesData.name[biome] = 'removed';
|
||||
biomesFooterBiomes.innerHTML = +biomesFooterBiomes.innerHTML - 1;
|
||||
}
|
||||
|
||||
function regenerateIcons() {
|
||||
ReliefIcons();
|
||||
if (!layerIsOn("toggleRelief")) toggleRelief();
|
||||
if (!layerIsOn('toggleRelief')) toggleRelief();
|
||||
}
|
||||
|
||||
function downloadBiomesData() {
|
||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||
let data = "Id,Biome,Color,Habitability,Cells,Area " + unit + ",Population\n"; // headers
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
let data = 'Id,Biome,Color,Habitability,Cells,Area ' + unit + ',Population\n'; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += el.dataset.color + ",";
|
||||
data += el.dataset.habitability + "%,";
|
||||
data += el.dataset.cells + ",";
|
||||
data += el.dataset.area + ",";
|
||||
data += el.dataset.population + "\n";
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.name + ',';
|
||||
data += el.dataset.color + ',';
|
||||
data += el.dataset.habitability + '%,';
|
||||
data += el.dataset.cells + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName("Biomes") + ".csv";
|
||||
const name = getFileName('Biomes') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function enterBiomesCustomizationMode() {
|
||||
if (!layerIsOn("toggleBiomes")) toggleBiomes();
|
||||
if (!layerIsOn('toggleBiomes')) toggleBiomes();
|
||||
customization = 6;
|
||||
biomes.append("g").attr("id", "temp");
|
||||
biomes.append('g').attr('id', 'temp');
|
||||
|
||||
document.querySelectorAll("#biomesBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.querySelectorAll("#biomesBottom > div").forEach(el => (el.style.display = "block"));
|
||||
body.querySelector("div.biomes").classList.add("selected");
|
||||
document.querySelectorAll('#biomesBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.querySelectorAll('#biomesBottom > div').forEach((el) => (el.style.display = 'block'));
|
||||
body.querySelector('div.biomes').classList.add('selected');
|
||||
|
||||
biomesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
|
||||
body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "none"));
|
||||
biomesFooter.style.display = "none";
|
||||
$("#biomesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
|
||||
biomesEditor.querySelectorAll('.hide').forEach((el) => el.classList.add('hidden'));
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'none'));
|
||||
biomesFooter.style.display = 'none';
|
||||
$('#biomesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}});
|
||||
|
||||
tip("Click on biome to select, drag the circle to change biome", true);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectBiomeOnMapClick).call(d3.drag().on("start", dragBiomeBrush)).on("touchmove mousemove", moveBiomeBrush);
|
||||
tip('Click on biome to select, drag the circle to change biome', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectBiomeOnMapClick).call(d3.drag().on('start', dragBiomeBrush)).on('touchmove mousemove', moveBiomeBrush);
|
||||
}
|
||||
|
||||
function selectBiomeOnLineClick(line) {
|
||||
const selected = body.querySelector("div.selected");
|
||||
if (selected) selected.classList.remove("selected");
|
||||
line.classList.add("selected");
|
||||
const selected = body.querySelector('div.selected');
|
||||
if (selected) selected.classList.remove('selected');
|
||||
line.classList.add('selected');
|
||||
}
|
||||
|
||||
function selectBiomeOnMapClick() {
|
||||
const point = d3.mouse(this);
|
||||
const i = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[i] < 20) {
|
||||
tip("You cannot reassign water via biomes. Please edit the Heightmap to change water", false, "error");
|
||||
tip('You cannot reassign water via biomes. Please edit the Heightmap to change water', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const assigned = biomes.select("#temp").select("polygon[data-cell='" + i + "']");
|
||||
const biome = assigned.size() ? +assigned.attr("data-biome") : pack.cells.biome[i];
|
||||
const assigned = biomes.select('#temp').select("polygon[data-cell='" + i + "']");
|
||||
const biome = assigned.size() ? +assigned.attr('data-biome') : pack.cells.biome[i];
|
||||
|
||||
body.querySelector("div.selected").classList.remove("selected");
|
||||
body.querySelector("div[data-id='" + biome + "']").classList.add("selected");
|
||||
body.querySelector('div.selected').classList.remove('selected');
|
||||
body.querySelector("div[data-id='" + biome + "']").classList.add('selected');
|
||||
}
|
||||
|
||||
function dragBiomeBrush() {
|
||||
const r = +biomesManuallyBrush.value;
|
||||
|
||||
d3.event.on("drag", () => {
|
||||
d3.event.on('drag', () => {
|
||||
if (!d3.event.dx && !d3.event.dy) return;
|
||||
const p = d3.mouse(this);
|
||||
moveCircle(p[0], p[1], r);
|
||||
|
|
@ -383,20 +385,20 @@ function editBiomes() {
|
|||
|
||||
// change region within selection
|
||||
function changeBiomeForSelection(selection) {
|
||||
const temp = biomes.select("#temp");
|
||||
const selected = body.querySelector("div.selected");
|
||||
const temp = biomes.select('#temp');
|
||||
const selected = body.querySelector('div.selected');
|
||||
|
||||
const biomeNew = selected.dataset.id;
|
||||
const color = biomesData.color[biomeNew];
|
||||
|
||||
selection.forEach(function (i) {
|
||||
const exists = temp.select("polygon[data-cell='" + i + "']");
|
||||
const biomeOld = exists.size() ? +exists.attr("data-biome") : pack.cells.biome[i];
|
||||
const biomeOld = exists.size() ? +exists.attr('data-biome') : pack.cells.biome[i];
|
||||
if (biomeNew === biomeOld) return;
|
||||
|
||||
// change of append new element
|
||||
if (exists.size()) exists.attr("data-biome", biomeNew).attr("fill", color).attr("stroke", color);
|
||||
else temp.append("polygon").attr("data-cell", i).attr("data-biome", biomeNew).attr("points", getPackPolygon(i)).attr("fill", color).attr("stroke", color);
|
||||
if (exists.size()) exists.attr('data-biome', biomeNew).attr('fill', color).attr('stroke', color);
|
||||
else temp.append('polygon').attr('data-cell', i).attr('data-biome', biomeNew).attr('points', getPackPolygon(i)).attr('fill', color).attr('stroke', color);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -408,7 +410,7 @@ function editBiomes() {
|
|||
}
|
||||
|
||||
function applyBiomesChange() {
|
||||
const changed = biomes.select("#temp").selectAll("polygon");
|
||||
const changed = biomes.select('#temp').selectAll('polygon');
|
||||
changed.each(function () {
|
||||
const i = +this.dataset.cell;
|
||||
const b = +this.dataset.biome;
|
||||
|
|
@ -424,21 +426,21 @@ function editBiomes() {
|
|||
|
||||
function exitBiomesCustomizationMode(close) {
|
||||
customization = 0;
|
||||
biomes.select("#temp").remove();
|
||||
biomes.select('#temp').remove();
|
||||
removeCircle();
|
||||
|
||||
document.querySelectorAll("#biomesBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.querySelectorAll("#biomesBottom > div").forEach(el => (el.style.display = "none"));
|
||||
document.querySelectorAll('#biomesBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.querySelectorAll('#biomesBottom > div').forEach((el) => (el.style.display = 'none'));
|
||||
|
||||
body.querySelectorAll("div > input, select, span, svg").forEach(e => (e.style.pointerEvents = "all"));
|
||||
biomesEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden"));
|
||||
biomesFooter.style.display = "block";
|
||||
if (!close) $("#biomesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'all'));
|
||||
biomesEditor.querySelectorAll('.hide').forEach((el) => el.classList.remove('hidden'));
|
||||
biomesFooter.style.display = 'block';
|
||||
if (!close) $('#biomesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}});
|
||||
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
const selected = document.querySelector("#biomesBody > div.selected");
|
||||
if (selected) selected.classList.remove("selected");
|
||||
const selected = document.querySelector('#biomesBody > div.selected');
|
||||
if (selected) selected.classList.remove('selected');
|
||||
}
|
||||
|
||||
function restoreInitialBiomes() {
|
||||
|
|
@ -450,6 +452,6 @@ function editBiomes() {
|
|||
}
|
||||
|
||||
function closeBiomesEditor() {
|
||||
exitBiomesCustomizationMode("close");
|
||||
exitBiomesCustomizationMode('close');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,14 @@ function editBurg(id) {
|
|||
burgLabels.selectAll('text').call(d3.drag().on('start', dragBurgLabel)).classed('draggable', true);
|
||||
updateBurgValues();
|
||||
|
||||
const my = id || d3.event.target.tagName === 'text' ? 'center bottom-40' : 'center top+40';
|
||||
const at = id ? 'center' : d3.event.target.tagName === 'text' ? 'top' : 'bottom';
|
||||
const of = id ? 'svg' : d3.event.target;
|
||||
|
||||
const my = id || d3.event.target.tagName === "text" ? "center bottom-20" : "center top+20";
|
||||
const at = id ? "center" : d3.event.target.tagName === "text" ? "top" : "bottom";
|
||||
const of = id ? "svg" : d3.event.target;
|
||||
$('#burgEditor').dialog({
|
||||
title: 'Edit Burg',
|
||||
resizable: false,
|
||||
close: closeBurgEditor,
|
||||
position: {my, at, of, collision: 'fit'}
|
||||
position: {my, at, of, collision: "fit"}
|
||||
});
|
||||
|
||||
if (modules.editBurg) return;
|
||||
|
|
@ -39,6 +38,8 @@ function editBurg(id) {
|
|||
document.getElementById('burgNameReCulture').addEventListener('click', generateNameCulture);
|
||||
document.getElementById('burgPopulation').addEventListener('change', changePopulation);
|
||||
burgBody.querySelectorAll('.burgFeature').forEach((el) => el.addEventListener('click', toggleFeature));
|
||||
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed);
|
||||
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed);
|
||||
|
||||
document.getElementById('burgStyleShow').addEventListener('click', showStyleSection);
|
||||
document.getElementById('burgStyleHide').addEventListener('click', hideStyleSection);
|
||||
|
|
@ -48,6 +49,8 @@ function editBurg(id) {
|
|||
|
||||
document.getElementById('burgSeeInMFCG').addEventListener('click', openInMFCG);
|
||||
document.getElementById('burgEditEmblem').addEventListener('click', openEmblemEdit);
|
||||
document.getElementById("burgEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgToggleMFCGMap").addEventListener("click", toggleMFCGMap);
|
||||
document.getElementById('burgRelocate').addEventListener('click', toggleRelocateBurg);
|
||||
document.getElementById('burglLegend').addEventListener('click', editBurgLegend);
|
||||
document.getElementById('burgLock').addEventListener('click', toggleBurgLockButton);
|
||||
|
|
@ -68,7 +71,14 @@ function editBurg(id) {
|
|||
document.getElementById('burgState').innerHTML = stateName;
|
||||
document.getElementById('burgProvince').innerHTML = provinceName;
|
||||
|
||||
document.getElementById('burgEditAnchorStyle').style.display = +b.port ? 'inline-block' : 'none';
|
||||
document.getElementById("burgName").value = b.name;
|
||||
document.getElementById("burgType").value = b.type || "Generic";
|
||||
document.getElementById("burgPopulation").value = rn(b.population * populationRate * urbanization);
|
||||
document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none";
|
||||
document.getElementById("burgName").value = b.name;
|
||||
document.getElementById("burgType").value = b.type || "Generic";
|
||||
document.getElementById("burgPopulation").value = rn(b.population * populationRate * urbanization);
|
||||
document.getElementById("burgEditAnchorStyle").style.display = +b.port ? "inline-block" : "none";
|
||||
|
||||
// update list and select culture
|
||||
const cultureSelect = document.getElementById('burgCulture');
|
||||
|
|
@ -119,6 +129,14 @@ function editBurg(id) {
|
|||
const coaID = 'burgCOA' + id;
|
||||
COArenderer.trigger(coaID, b.coa);
|
||||
document.getElementById('burgEmblem').setAttribute('href', '#' + coaID);
|
||||
|
||||
if (options.showMFCGMap) {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "block";
|
||||
updateMFCGFrame(b);
|
||||
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
|
||||
} else {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function getProduction(pool) {
|
||||
|
|
@ -411,11 +429,7 @@ function editBurg(id) {
|
|||
}
|
||||
}
|
||||
|
||||
function showBurgELockTip() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
showBurgLockTip(id);
|
||||
}
|
||||
|
||||
function showStyleSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('burgStyleSection').style.display = 'inline-block';
|
||||
|
|
@ -443,57 +457,62 @@ function editBurg(id) {
|
|||
|
||||
function openInMFCG(event) {
|
||||
const id = elSelected.attr('data-id');
|
||||
const burg = pack.burgs[id];
|
||||
const defSeed = +(seed + id.padStart(4, 0));
|
||||
if (isCtrlClick(event)) {
|
||||
prompt(
|
||||
`Please provide a Medieval Fantasy City Generator seed.
|
||||
Seed should be a number. Default seed is FMG map seed + burg id padded to 4 chars with zeros (${defSeed}).
|
||||
Please note that if seed is custom, "Overworld" button from MFCG will open a different map`,
|
||||
{default: burg.MFCG || defSeed, step: 1, min: 1, max: 1e13 - 1},
|
||||
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL);
|
||||
document.getElementById("mfcgLink").setAttribute("href", mfcgURL);
|
||||
(v) => {
|
||||
burg.MFCG = v;
|
||||
openMFCG(v);
|
||||
}
|
||||
);
|
||||
} else openMFCG();
|
||||
}
|
||||
function getBurgSeed(burg) {
|
||||
return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`);
|
||||
}
|
||||
|
||||
function openMFCG(seed) {
|
||||
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), 100), 6); // to be removed once change on MFDC is done
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
function getMFCGlink(burg) {
|
||||
const {cells} = pack;
|
||||
const {name, population, cell} = burg;
|
||||
const burgSeed = getBurgSeed(burg);
|
||||
const sizeRaw = 2.13 * Math.pow((population * populationRate) / urbanDensity, 0.385);
|
||||
const size = minmax(Math.ceil(sizeRaw), 6, 100);
|
||||
const people = rn(population * populationRate * urbanization);
|
||||
|
||||
const s = burg.MFCG || defSeed;
|
||||
const cell = burg.cell;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : '';
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return '&sea=' + norm;
|
||||
}
|
||||
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return "&sea=" + norm;
|
||||
}
|
||||
|
||||
const site = 'http://fantasycities.watabou.ru/?random=0&continuous=0';
|
||||
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);
|
||||
}
|
||||
const url = `${baseURL}&name=${name}&population=${people}&size=${size}&seed=${burgSeed}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
function changeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = +this.value;
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
}
|
||||
|
||||
function randomizeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = rand(1e9 - 1);
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
document.getElementById("mfcgBurgSeed").value = burgSeed;
|
||||
}
|
||||
|
||||
function openEmblemEdit() {
|
||||
|
|
@ -502,6 +521,12 @@ function editBurg(id) {
|
|||
editEmblem('burg', 'burgCOA' + id, burg);
|
||||
}
|
||||
|
||||
function toggleMFCGMap() {
|
||||
options.showMFCGMap = !options.showMFCGMap;
|
||||
document.getElementById("mfcgPreviewSection").style.display = options.showMFCGMap ? "block" : "none";
|
||||
document.getElementById("burgToggleMFCGMap").className = options.showMFCGMap ? "icon-map" : "icon-map-o";
|
||||
}
|
||||
|
||||
function toggleRelocateBurg() {
|
||||
const toggler = document.getElementById('toggleCells');
|
||||
document.getElementById('burgRelocate').classList.toggle('pressed');
|
||||
|
|
|
|||
739
modules/ui/burg-editor.js.orig
Normal file
739
modules/ui/burg-editor.js.orig
Normal file
|
|
@ -0,0 +1,739 @@
|
|||
'use strict';
|
||||
function editBurg(id) {
|
||||
if (customization) return;
|
||||
closeDialogs('.stable');
|
||||
if (!layerIsOn('toggleIcons')) toggleIcons();
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
|
||||
const burg = id || d3.event.target.dataset.id;
|
||||
elSelected = burgLabels.select("[data-id='" + burg + "']");
|
||||
burgLabels.selectAll('text').call(d3.drag().on('start', dragBurgLabel)).classed('draggable', true);
|
||||
updateBurgValues();
|
||||
|
||||
<<<<<<< HEAD
|
||||
const my = id || d3.event.target.tagName === 'text' ? 'center bottom-40' : 'center top+40';
|
||||
const at = id ? 'center' : d3.event.target.tagName === 'text' ? 'top' : 'bottom';
|
||||
const of = id ? 'svg' : d3.event.target;
|
||||
|
||||
$('#burgEditor').dialog({
|
||||
title: 'Edit Burg',
|
||||
resizable: false,
|
||||
close: closeBurgEditor,
|
||||
position: {my, at, of, collision: 'fit'}
|
||||
=======
|
||||
$("#burgEditor").dialog({
|
||||
title: "Edit Burg",
|
||||
resizable: false,
|
||||
close: closeBurgEditor,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"}
|
||||
>>>>>>> master
|
||||
});
|
||||
|
||||
if (modules.editBurg) return;
|
||||
modules.editBurg = true;
|
||||
|
||||
// add listeners
|
||||
<<<<<<< HEAD
|
||||
document.getElementById('burgGroupShow').addEventListener('click', showGroupSection);
|
||||
document.getElementById('burgGroupHide').addEventListener('click', hideGroupSection);
|
||||
document.getElementById('burgSelectGroup').addEventListener('change', changeGroup);
|
||||
document.getElementById('burgInputGroup').addEventListener('change', createNewGroup);
|
||||
document.getElementById('burgAddGroup').addEventListener('click', toggleNewGroupInput);
|
||||
document.getElementById('burgRemoveGroup').addEventListener('click', removeBurgsGroup);
|
||||
|
||||
document.getElementById('burgName').addEventListener('input', changeName);
|
||||
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));
|
||||
|
||||
document.getElementById('burgStyleShow').addEventListener('click', showStyleSection);
|
||||
document.getElementById('burgStyleHide').addEventListener('click', hideStyleSection);
|
||||
document.getElementById('burgEditLabelStyle').addEventListener('click', editGroupLabelStyle);
|
||||
document.getElementById('burgEditIconStyle').addEventListener('click', editGroupIconStyle);
|
||||
document.getElementById('burgEditAnchorStyle').addEventListener('click', editGroupAnchorStyle);
|
||||
|
||||
document.getElementById('burgSeeInMFCG').addEventListener('click', openInMFCG);
|
||||
document.getElementById('burgEditEmblem').addEventListener('click', openEmblemEdit);
|
||||
document.getElementById('burgRelocate').addEventListener('click', toggleRelocateBurg);
|
||||
document.getElementById('burglLegend').addEventListener('click', editBurgLegend);
|
||||
document.getElementById('burgLock').addEventListener('click', toggleBurgLockButton);
|
||||
document.getElementById('burgLock').addEventListener('mouseover', showBurgELockTip);
|
||||
document.getElementById('burgRemove').addEventListener('click', removeSelectedBurg);
|
||||
=======
|
||||
document.getElementById("burgGroupShow").addEventListener("click", showGroupSection);
|
||||
document.getElementById("burgGroupHide").addEventListener("click", hideGroupSection);
|
||||
document.getElementById("burgSelectGroup").addEventListener("change", changeGroup);
|
||||
document.getElementById("burgInputGroup").addEventListener("change", createNewGroup);
|
||||
document.getElementById("burgAddGroup").addEventListener("click", toggleNewGroupInput);
|
||||
document.getElementById("burgRemoveGroup").addEventListener("click", removeBurgsGroup);
|
||||
|
||||
document.getElementById("burgName").addEventListener("input", changeName);
|
||||
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));
|
||||
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed);
|
||||
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed);
|
||||
|
||||
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection);
|
||||
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection);
|
||||
document.getElementById("burgEditLabelStyle").addEventListener("click", editGroupLabelStyle);
|
||||
document.getElementById("burgEditIconStyle").addEventListener("click", editGroupIconStyle);
|
||||
document.getElementById("burgEditAnchorStyle").addEventListener("click", editGroupAnchorStyle);
|
||||
|
||||
document.getElementById("burgEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgToggleMFCGMap").addEventListener("click", toggleMFCGMap);
|
||||
document.getElementById("burgEditEmblem").addEventListener("click", openEmblemEdit);
|
||||
document.getElementById("burgRelocate").addEventListener("click", toggleRelocateBurg);
|
||||
document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
|
||||
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton);
|
||||
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
|
||||
>>>>>>> master
|
||||
|
||||
function updateBurgValues() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
const b = pack.burgs[id];
|
||||
|
||||
document.getElementById('burgName').value = b.name;
|
||||
document.getElementById('burgType').value = b.type || 'Generic';
|
||||
document.getElementById('burgPopulation').value = rn(b.population * populationRate * urbanization);
|
||||
|
||||
const stateName = pack.states[b.state].fullName || pack.states[b.state].name;
|
||||
const province = pack.cells.province[b.cell];
|
||||
const provinceName = province ? pack.provinces[province].fullName : '';
|
||||
document.getElementById('burgState').innerHTML = stateName;
|
||||
document.getElementById('burgProvince').innerHTML = provinceName;
|
||||
|
||||
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('burgTemperatureLike').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');
|
||||
if (b.port) document.getElementById('burgPort').classList.remove('inactive');
|
||||
else document.getElementById('burgPort').classList.add('inactive');
|
||||
if (b.citadel) document.getElementById('burgCitadel').classList.remove('inactive');
|
||||
else document.getElementById('burgCitadel').classList.add('inactive');
|
||||
if (b.walls) document.getElementById('burgWalls').classList.remove('inactive');
|
||||
else document.getElementById('burgWalls').classList.add('inactive');
|
||||
if (b.plaza) document.getElementById('burgPlaza').classList.remove('inactive');
|
||||
else document.getElementById('burgPlaza').classList.add('inactive');
|
||||
if (b.temple) document.getElementById('burgTemple').classList.remove('inactive');
|
||||
else document.getElementById('burgTemple').classList.add('inactive');
|
||||
if (b.shanty) document.getElementById('burgShanty').classList.remove('inactive');
|
||||
else document.getElementById('burgShanty').classList.add('inactive');
|
||||
|
||||
// economics block
|
||||
document.getElementById('burgProduction').innerHTML = getProduction(b.produced);
|
||||
const deals = pack.trade.deals;
|
||||
document.getElementById('burgExport').innerHTML = getExport(deals.filter((deal) => deal.exporter === b.i));
|
||||
document.getElementById('burgImport').innerHTML = '';
|
||||
|
||||
//toggle lock
|
||||
updateBurgLockIcon();
|
||||
|
||||
// select group
|
||||
const group = elSelected.node().parentNode.id;
|
||||
const select = document.getElementById('burgSelectGroup');
|
||||
select.options.length = 0; // remove all options
|
||||
|
||||
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);
|
||||
<<<<<<< HEAD
|
||||
document.getElementById('burgEmblem').setAttribute('href', '#' + coaID);
|
||||
=======
|
||||
document.getElementById("burgEmblem").setAttribute("href", "#" + coaID);
|
||||
|
||||
if (options.showMFCGMap) {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "block";
|
||||
updateMFCGFrame(b);
|
||||
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
|
||||
} else {
|
||||
document.getElementById("mfcgPreviewSection").style.display = "none";
|
||||
}
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function getProduction(pool) {
|
||||
let html = '';
|
||||
|
||||
for (const resourceId in pool) {
|
||||
const {name, unit, icon} = Resources.get(+resourceId);
|
||||
const production = pool[resourceId];
|
||||
const unitName = production > 1 ? unit + 's' : unit;
|
||||
|
||||
html += `<span data-tip="${name}: ${production} ${unitName}">
|
||||
<svg class="resIcon"><use href="#${icon}"></svg>
|
||||
<span style="margin: 0 0.2em 0 -0.2em">${production}</span>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function getExport(dealsArray) {
|
||||
if (!dealsArray.length) return 'no';
|
||||
|
||||
const totalIncome = rn(d3.sum(dealsArray.map((deal) => deal.burgIncome)));
|
||||
const exported = dealsArray.map((deal) => {
|
||||
const {resourceId, quantity, burgIncome} = deal;
|
||||
const {name, unit, icon} = Resources.get(resourceId);
|
||||
const unitName = quantity > 1 ? unit + 's' : unit;
|
||||
|
||||
return `<span data-tip="${name}: ${quantity} ${unitName}. Income: ${rn(burgIncome)}">
|
||||
<svg class="resIcon"><use href="#${icon}"></svg>
|
||||
<span style="margin: 0 0.2em 0 -0.2em">${quantity}</span>
|
||||
</span>`;
|
||||
});
|
||||
|
||||
return `${totalIncome}: ${exported.join('')}`;
|
||||
}
|
||||
|
||||
// [-1; 31] °C, source: https://en.wikipedia.org/wiki/List_of_cities_by_average_temperature
|
||||
function getTemperatureLikeness(temperature) {
|
||||
if (temperature < -15) return 'nowhere in the real-world';
|
||||
if (temperature < -5) return 'in Yakutsk';
|
||||
if (temperature > 31) return 'nowhere in the real-world';
|
||||
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() {
|
||||
const tr = parseTransform(this.getAttribute('transform'));
|
||||
const dx = +tr[0] - d3.event.x,
|
||||
dy = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const x = d3.event.x,
|
||||
y = d3.event.y;
|
||||
this.setAttribute('transform', `translate(${dx + x},${dy + y})`);
|
||||
tip('Use dragging for fine-tuning only, to actually move burg use "Relocate" button', false, 'warning');
|
||||
});
|
||||
}
|
||||
|
||||
function showGroupSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('burgGroupSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideGroupSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('burgGroupSection').style.display = 'none';
|
||||
document.getElementById('burgInputGroup').style.display = 'none';
|
||||
document.getElementById('burgInputGroup').value = '';
|
||||
document.getElementById('burgSelectGroup').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
moveBurgToGroup(id, this.value);
|
||||
}
|
||||
|
||||
function toggleNewGroupInput() {
|
||||
if (burgInputGroup.style.display === 'none') {
|
||||
burgInputGroup.style.display = 'inline-block';
|
||||
burgInputGroup.focus();
|
||||
burgSelectGroup.style.display = 'none';
|
||||
} else {
|
||||
burgInputGroup.style.display = 'none';
|
||||
burgSelectGroup.style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
|
||||
function createNewGroup() {
|
||||
if (!this.value) {
|
||||
tip('Please provide a valid group name', false, 'error');
|
||||
return;
|
||||
}
|
||||
const group = this.value
|
||||
.toLowerCase()
|
||||
.replace(/ /g, '_')
|
||||
.replace(/[^\w\s]/gi, '');
|
||||
|
||||
if (document.getElementById(group)) {
|
||||
tip('Element with this id already exists. Please provide a unique name', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number.isFinite(+group.charAt(0))) {
|
||||
tip('Group name should start with a letter', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const id = +elSelected.attr('data-id');
|
||||
const oldGroup = elSelected.node().parentNode.id;
|
||||
|
||||
const label = document.querySelector("#burgLabels [data-id='" + id + "']");
|
||||
const icon = document.querySelector("#burgIcons [data-id='" + id + "']");
|
||||
const anchor = document.querySelector("#anchors [data-id='" + id + "']");
|
||||
if (!label || !icon) {
|
||||
ERROR && console.error('Cannot find label or icon elements');
|
||||
return;
|
||||
}
|
||||
|
||||
const labelG = document.querySelector('#burgLabels > #' + oldGroup);
|
||||
const iconG = document.querySelector('#burgIcons > #' + oldGroup);
|
||||
const anchorG = document.querySelector('#anchors > #' + oldGroup);
|
||||
|
||||
// just rename if only 1 element left
|
||||
const count = elSelected.node().parentNode.childElementCount;
|
||||
if (oldGroup !== 'cities' && oldGroup !== 'towns' && count === 1) {
|
||||
document.getElementById('burgSelectGroup').selectedOptions[0].remove();
|
||||
document.getElementById('burgSelectGroup').options.add(new Option(group, group, false, true));
|
||||
toggleNewGroupInput();
|
||||
document.getElementById('burgInputGroup').value = '';
|
||||
labelG.id = group;
|
||||
iconG.id = group;
|
||||
if (anchor) anchorG.id = group;
|
||||
return;
|
||||
}
|
||||
|
||||
// create new groups
|
||||
document.getElementById('burgSelectGroup').options.add(new Option(group, group, false, true));
|
||||
toggleNewGroupInput();
|
||||
document.getElementById('burgInputGroup').value = '';
|
||||
|
||||
const newLabelG = document.querySelector('#burgLabels').appendChild(labelG.cloneNode(false));
|
||||
newLabelG.id = group;
|
||||
const newIconG = document.querySelector('#burgIcons').appendChild(iconG.cloneNode(false));
|
||||
newIconG.id = group;
|
||||
if (anchor) {
|
||||
const newAnchorG = document.querySelector('#anchors').appendChild(anchorG.cloneNode(false));
|
||||
newAnchorG.id = group;
|
||||
}
|
||||
moveBurgToGroup(id, group);
|
||||
}
|
||||
|
||||
function removeBurgsGroup() {
|
||||
const group = elSelected.node().parentNode;
|
||||
const basic = group.id === 'cities' || group.id === 'towns';
|
||||
|
||||
const burgsInGroup = [];
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
burgsInGroup.push(+group.children[i].dataset.id);
|
||||
}
|
||||
const burgsToRemove = burgsInGroup.filter((b) => !(pack.burgs[b].capital || pack.burgs[b].lock));
|
||||
const capital = burgsToRemove.length < burgsInGroup.length;
|
||||
|
||||
const message = `Are you sure you want to remove
|
||||
${basic || capital ? 'all unlocked elements in the group' : 'the entire burg group'}?
|
||||
<br>Please note that capital or locked burgs will not be deleted.
|
||||
<br><br>Burgs to be removed: ${burgsToRemove.length}`;
|
||||
confirmationDialog({title: 'Remove burg group', message, confirm: 'Remove', onConfirm: removeGroup});
|
||||
|
||||
function removeGroup() {
|
||||
$(this).dialog('close');
|
||||
$('#burgEditor').dialog('close');
|
||||
hideGroupSection();
|
||||
burgsToRemove.forEach((b) => removeBurg(b));
|
||||
|
||||
if (!basic && !capital) {
|
||||
// entirely remove group
|
||||
const labelG = document.querySelector('#burgLabels > #' + group.id);
|
||||
const iconG = document.querySelector('#burgIcons > #' + group.id);
|
||||
const anchorG = document.querySelector('#anchors > #' + group.id);
|
||||
if (labelG) labelG.remove();
|
||||
if (iconG) iconG.remove();
|
||||
if (anchorG) anchorG.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
pack.burgs[id].name = burgName.value;
|
||||
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;
|
||||
burgName.value = Names.getCulture(culture);
|
||||
changeName();
|
||||
}
|
||||
|
||||
function changePopulation() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
pack.burgs[id].population = rn(burgPopulation.value / populationRate / urbanization, 4);
|
||||
}
|
||||
|
||||
function toggleFeature() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
const b = pack.burgs[id];
|
||||
const feature = this.dataset.feature;
|
||||
const turnOn = this.classList.contains('inactive');
|
||||
if (feature === 'port') togglePort(id);
|
||||
else if (feature === 'capital') toggleCapital(id);
|
||||
else b[feature] = +turnOn;
|
||||
if (b[feature]) this.classList.remove('inactive');
|
||||
else if (!b[feature]) this.classList.add('inactive');
|
||||
|
||||
if (b.port) document.getElementById('burgEditAnchorStyle').style.display = 'inline-block';
|
||||
else document.getElementById('burgEditAnchorStyle').style.display = 'none';
|
||||
}
|
||||
|
||||
function toggleBurgLockButton() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
toggleBurgLock(id);
|
||||
updateBurgLockIcon();
|
||||
}
|
||||
|
||||
function updateBurgLockIcon() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
const b = pack.burgs[id];
|
||||
if (b.lock) {
|
||||
document.getElementById('burgLock').classList.remove('icon-lock-open');
|
||||
document.getElementById('burgLock').classList.add('icon-lock');
|
||||
} else {
|
||||
document.getElementById('burgLock').classList.remove('icon-lock');
|
||||
document.getElementById('burgLock').classList.add('icon-lock-open');
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
function showBurgELockTip() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
showBurgLockTip(id);
|
||||
}
|
||||
|
||||
=======
|
||||
>>>>>>> master
|
||||
function showStyleSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('burgStyleSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideStyleSection() {
|
||||
document.querySelectorAll('#burgBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('burgStyleSection').style.display = 'none';
|
||||
}
|
||||
|
||||
function editGroupLabelStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle('labels', g);
|
||||
}
|
||||
|
||||
function editGroupIconStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle('burgIcons', g);
|
||||
}
|
||||
|
||||
function editGroupAnchorStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle('anchors', g);
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
function openInMFCG(event) {
|
||||
const id = elSelected.attr('data-id');
|
||||
const burg = pack.burgs[id];
|
||||
const defSeed = +(seed + id.padStart(4, 0));
|
||||
if (isCtrlClick(event)) {
|
||||
prompt(
|
||||
`Please provide a Medieval Fantasy City Generator seed.
|
||||
Seed should be a number. Default seed is FMG map seed + burg id padded to 4 chars with zeros (${defSeed}).
|
||||
Please note that if seed is custom, "Overworld" button from MFCG will open a different map`,
|
||||
{default: burg.MFCG || defSeed, step: 1, min: 1, max: 1e13 - 1},
|
||||
(v) => {
|
||||
burg.MFCG = v;
|
||||
openMFCG(v);
|
||||
}
|
||||
);
|
||||
} else openMFCG();
|
||||
|
||||
function openMFCG(seed) {
|
||||
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), 100), 6); // to be removed once change on MFDC is done
|
||||
const population = rn(burg.population * populationRate * urbanization);
|
||||
|
||||
const s = burg.MFCG || defSeed;
|
||||
const cell = burg.cell;
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[burg.cell] ? getSeaDirections(burg.cell) : '';
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return '&sea=' + norm;
|
||||
}
|
||||
|
||||
const site = 'http://fantasycities.watabou.ru/?random=0&continuous=0';
|
||||
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 updateMFCGFrame(burg) {
|
||||
const mfcgURL = getMFCGlink(burg);
|
||||
document.getElementById("mfcgPreview").setAttribute("src", mfcgURL);
|
||||
document.getElementById("mfcgLink").setAttribute("href", mfcgURL);
|
||||
}
|
||||
function getBurgSeed(burg) {
|
||||
return burg.MFCG || Number(`${seed}${String(burg.i).padStart(4, 0)}`);
|
||||
}
|
||||
|
||||
function getMFCGlink(burg) {
|
||||
const {cells} = pack;
|
||||
const {name, population, cell} = burg;
|
||||
const burgSeed = getBurgSeed(burg);
|
||||
const sizeRaw = 2.13 * Math.pow((population * populationRate) / urbanDensity, 0.385);
|
||||
const size = minmax(Math.ceil(sizeRaw), 6, 100);
|
||||
const people = rn(population * populationRate * urbanization);
|
||||
|
||||
const hub = +cells.road[cell] > 50;
|
||||
const river = cells.r[cell] ? 1 : 0;
|
||||
|
||||
const coast = +burg.port;
|
||||
const citadel = +burg.citadel;
|
||||
const walls = +burg.walls;
|
||||
const plaza = +burg.plaza;
|
||||
const temple = +burg.temple;
|
||||
const shanty = +burg.shanty;
|
||||
|
||||
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
|
||||
function getSeaDirections(i) {
|
||||
const p1 = cells.p[i];
|
||||
const p2 = cells.p[cells.haven[i]];
|
||||
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
|
||||
if (deg < 0) deg += 360;
|
||||
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
|
||||
return "&sea=" + norm;
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
const baseURL = "https://watabou.github.io/city-generator/?random=0&continuous=0";
|
||||
const url = `${baseURL}&name=${name}&population=${people}&size=${size}&seed=${burgSeed}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
function changeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = +this.value;
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
}
|
||||
|
||||
function randomizeSeed() {
|
||||
const id = +elSelected.attr("data-id");
|
||||
const burg = pack.burgs[id];
|
||||
const burgSeed = rand(1e9 - 1);
|
||||
burg.MFCG = burgSeed;
|
||||
updateMFCGFrame(burg);
|
||||
document.getElementById("mfcgBurgSeed").value = burgSeed;
|
||||
}
|
||||
|
||||
function openEmblemEdit() {
|
||||
const id = +elSelected.attr('data-id'),
|
||||
burg = pack.burgs[id];
|
||||
editEmblem('burg', 'burgCOA' + id, burg);
|
||||
}
|
||||
|
||||
function toggleMFCGMap() {
|
||||
options.showMFCGMap = !options.showMFCGMap;
|
||||
document.getElementById("mfcgPreviewSection").style.display = options.showMFCGMap ? "block" : "none";
|
||||
document.getElementById("burgToggleMFCGMap").className = options.showMFCGMap ? "icon-map" : "icon-map-o";
|
||||
}
|
||||
|
||||
function toggleRelocateBurg() {
|
||||
const toggler = document.getElementById('toggleCells');
|
||||
document.getElementById('burgRelocate').classList.toggle('pressed');
|
||||
if (document.getElementById('burgRelocate').classList.contains('pressed')) {
|
||||
viewbox.style('cursor', 'crosshair').on('click', relocateBurgOnClick);
|
||||
tip('Click on map to relocate burg. Hold Shift for continuous move', true);
|
||||
if (!layerIsOn('toggleCells')) {
|
||||
toggleCells();
|
||||
toggler.dataset.forced = true;
|
||||
}
|
||||
} else {
|
||||
clearMainTip();
|
||||
viewbox.on('click', clicked).style('cursor', 'default');
|
||||
if (layerIsOn('toggleCells') && toggler.dataset.forced) {
|
||||
toggleCells();
|
||||
toggler.dataset.forced = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function relocateBurgOnClick() {
|
||||
const cells = pack.cells;
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
const id = +elSelected.attr('data-id');
|
||||
const burg = pack.burgs[id];
|
||||
|
||||
if (cells.h[cell] < 20) {
|
||||
tip('Cannot place burg into the water! Select a land cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (cells.burg[cell] && cells.burg[cell] !== id) {
|
||||
tip('There is already a burg in this cell. Please select a free cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const newState = cells.state[cell];
|
||||
const oldState = burg.state;
|
||||
|
||||
if (newState !== oldState && burg.capital) {
|
||||
tip('Capital cannot be relocated into another state!', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// change UI
|
||||
const x = rn(point[0], 2),
|
||||
y = rn(point[1], 2);
|
||||
burgIcons
|
||||
.select("[data-id='" + id + "']")
|
||||
.attr('transform', null)
|
||||
.attr('cx', x)
|
||||
.attr('cy', y);
|
||||
burgLabels
|
||||
.select("text[data-id='" + id + "']")
|
||||
.attr('transform', null)
|
||||
.attr('x', x)
|
||||
.attr('y', y);
|
||||
const anchor = anchors.select("use[data-id='" + id + "']");
|
||||
if (anchor.size()) {
|
||||
const size = anchor.attr('width');
|
||||
const xa = rn(x - size * 0.47, 2);
|
||||
const ya = rn(y - size * 0.47, 2);
|
||||
anchor.attr('transform', null).attr('x', xa).attr('y', ya);
|
||||
}
|
||||
|
||||
// change data
|
||||
cells.burg[burg.cell] = 0;
|
||||
cells.burg[cell] = id;
|
||||
burg.cell = cell;
|
||||
burg.state = newState;
|
||||
burg.x = x;
|
||||
burg.y = y;
|
||||
if (burg.capital) pack.states[newState].center = burg.cell;
|
||||
|
||||
if (d3.event.shiftKey === false) toggleRelocateBurg();
|
||||
}
|
||||
|
||||
function editBurgLegend() {
|
||||
const id = elSelected.attr('data-id');
|
||||
const name = elSelected.text();
|
||||
editNotes('burg' + id, name);
|
||||
}
|
||||
|
||||
function removeSelectedBurg() {
|
||||
const id = +elSelected.attr('data-id');
|
||||
if (pack.burgs[id].capital) {
|
||||
alertMessage.innerHTML = `You cannot remove the burg as it is a state capital.<br><br>
|
||||
You can change the capital using Burgs Editor (shift + T)`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove burg',
|
||||
buttons: {
|
||||
Ok: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const message = 'Are you sure you want to remove the burg? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
removeBurg(id);
|
||||
$('#burgEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove burg', message, confirm: 'Remove', onConfirm});
|
||||
}
|
||||
}
|
||||
|
||||
function closeBurgEditor() {
|
||||
document.getElementById('burgRelocate').classList.remove('pressed');
|
||||
burgLabels.selectAll('text').call(d3.drag().on('drag', null)).classed('draggable', false);
|
||||
unselect();
|
||||
}
|
||||
}
|
||||
|
|
@ -79,17 +79,20 @@ function overviewBurgs() {
|
|||
const province = prov ? pack.provinces[prov].name : '';
|
||||
const culture = pack.cultures[b.culture].name;
|
||||
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${b.name}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false">
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${
|
||||
b.name
|
||||
}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
<input data-tip="Burg province" class="burgState" value="${province}" disabled>
|
||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(b.culture)}</select>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(
|
||||
b.culture
|
||||
)}</select>
|
||||
<span data-tip="Burg population" class="icon-male"></span>
|
||||
<input data-tip="Burg population. Type to change" class="burgPopulation" value=${si(population)}>
|
||||
<div class="burgType">
|
||||
<span data-tip="${b.capital ? ' This burg is a state capital' : 'Click to assign a capital status'}" class="icon-star-empty${b.capital ? '' : ' inactive pointer'}"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? '' : ' inactive'}" style="font-size:.9em"></span>
|
||||
}"></span>
|
||||
</div>
|
||||
<span data-tip="Zoom to burg" class="icon-dot-circled pointer"></span>
|
||||
<span class="locks pointer ${b.lock ? 'icon-lock' : 'icon-lock-open inactive'}"></span>
|
||||
|
|
@ -202,11 +205,6 @@ function overviewBurgs() {
|
|||
}
|
||||
}
|
||||
|
||||
function showBurgOLockTip() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
showBurgLockTip(burg);
|
||||
}
|
||||
|
||||
function openBurgEditor() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
editBurg(burg);
|
||||
|
|
@ -281,6 +279,7 @@ function overviewBurgs() {
|
|||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, state: s.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const burgs = pack.burgs
|
||||
.filter((b) => b.i && !b.removed)
|
||||
.map((b) => {
|
||||
|
|
@ -292,6 +291,7 @@ function overviewBurgs() {
|
|||
return {id, i: b.i, state: b.state, culture: b.culture, province, parent, name: b.name, population, capital, x: b.x, y: b.y};
|
||||
});
|
||||
const data = states.concat(burgs);
|
||||
if (data.length < 2) return tip("No burgs to show", false, "error");
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
|
|
@ -401,6 +401,12 @@ function overviewBurgs() {
|
|||
|
||||
const base = this.value === 'states' ? getStatesData() : this.value === 'cultures' ? getCulturesData() : this.value === 'parent' ? getParentData() : getProvincesData();
|
||||
burgs.forEach((b) => (b.id = b.i + base.length - 1));
|
||||
? getStatesData()
|
||||
: this.value === "cultures"
|
||||
? getCulturesData()
|
||||
: this.value === "parent"
|
||||
? getParentData()
|
||||
: getProvincesData();
|
||||
|
||||
const data = base.concat(burgs);
|
||||
|
||||
|
|
@ -435,6 +441,8 @@ function overviewBurgs() {
|
|||
function downloadBurgsData() {
|
||||
let data = 'Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation (' + heightUnit.value + '),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n'; // headers
|
||||
const valid = pack.burgs.filter((b) => b.i && !b.removed); // all valid burgs
|
||||
heightUnit.value +
|
||||
"),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers
|
||||
|
||||
valid.forEach((b) => {
|
||||
data += b.i + ',';
|
||||
|
|
|
|||
603
modules/ui/burgs-overview.js.orig
Normal file
603
modules/ui/burgs-overview.js.orig
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
'use strict';
|
||||
function overviewBurgs() {
|
||||
if (customization) return;
|
||||
closeDialogs('#burgsOverview, .stable');
|
||||
if (!layerIsOn('toggleIcons')) toggleIcons();
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
|
||||
const body = document.getElementById('burgsBody');
|
||||
updateFilter();
|
||||
burgsOverviewAddLines();
|
||||
$('#burgsOverview').dialog();
|
||||
|
||||
if (modules.overviewBurgs) return;
|
||||
modules.overviewBurgs = true;
|
||||
|
||||
$('#burgsOverview').dialog({
|
||||
title: 'Burgs Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: exitAddBurgMode,
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById('burgsOverviewRefresh').addEventListener('click', refreshBurgsEditor);
|
||||
document.getElementById('burgsChart').addEventListener('click', showBurgsChart);
|
||||
document.getElementById('burgsFilterState').addEventListener('change', burgsOverviewAddLines);
|
||||
document.getElementById('burgsFilterCulture').addEventListener('change', burgsOverviewAddLines);
|
||||
document.getElementById('regenerateBurgNames').addEventListener('click', regenerateNames);
|
||||
document.getElementById('addNewBurg').addEventListener('click', enterAddBurgMode);
|
||||
document.getElementById('burgsExport').addEventListener('click', downloadBurgsData);
|
||||
document.getElementById('burgNamesImport').addEventListener('click', renameBurgsInBulk);
|
||||
document.getElementById('burgsListToLoad').addEventListener('change', function () {
|
||||
uploadFile(this, importBurgNames);
|
||||
});
|
||||
document.getElementById('burgsRemoveAll').addEventListener('click', triggerAllBurgsRemove);
|
||||
|
||||
function refreshBurgsEditor() {
|
||||
updateFilter();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function updateFilter() {
|
||||
const stateFilter = document.getElementById('burgsFilterState');
|
||||
const selectedState = stateFilter.value || 1;
|
||||
stateFilter.options.length = 0; // remove all options
|
||||
stateFilter.options.add(new Option(`all`, -1, false, selectedState == -1));
|
||||
stateFilter.options.add(new Option(pack.states[0].name, 0, false, !selectedState));
|
||||
const statesSorted = pack.states.filter((s) => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
statesSorted.forEach((s) => stateFilter.options.add(new Option(s.name, s.i, false, s.i == selectedState)));
|
||||
|
||||
const cultureFilter = document.getElementById('burgsFilterCulture');
|
||||
const selectedCulture = cultureFilter.value || -1;
|
||||
cultureFilter.options.length = 0; // remove all options
|
||||
cultureFilter.options.add(new Option(`all`, -1, false, selectedCulture == -1));
|
||||
cultureFilter.options.add(new Option(pack.cultures[0].name, 0, false, !selectedCulture));
|
||||
const culturesSorted = pack.cultures.filter((c) => c.i && !c.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
culturesSorted.forEach((c) => cultureFilter.options.add(new Option(c.name, c.i, false, c.i == selectedCulture)));
|
||||
}
|
||||
|
||||
// add line for each burg
|
||||
function burgsOverviewAddLines() {
|
||||
const selectedState = +document.getElementById('burgsFilterState').value;
|
||||
const selectedCulture = +document.getElementById('burgsFilterCulture').value;
|
||||
let filtered = pack.burgs.filter((b) => b.i && !b.removed); // all valid burgs
|
||||
if (selectedState != -1) filtered = filtered.filter((b) => b.state === selectedState); // filtered by state
|
||||
if (selectedCulture != -1) filtered = filtered.filter((b) => b.culture === selectedCulture); // filtered by culture
|
||||
|
||||
body.innerHTML = '';
|
||||
let lines = '',
|
||||
totalPopulation = 0;
|
||||
|
||||
for (const b of filtered) {
|
||||
const population = b.population * populationRate * urbanization;
|
||||
totalPopulation += population;
|
||||
const type = b.capital && b.port ? 'a-capital-port' : b.capital ? 'c-capital' : b.port ? 'p-port' : 'z-burg';
|
||||
const state = pack.states[b.state].name;
|
||||
const prov = pack.cells.province[b.cell];
|
||||
const province = prov ? pack.provinces[prov].name : '';
|
||||
const culture = pack.cultures[b.culture].name;
|
||||
|
||||
<<<<<<< HEAD
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${b.name}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
=======
|
||||
lines += `<div class="states" data-id=${b.i} data-name="${
|
||||
b.name
|
||||
}" data-state="${state}" data-province="${province}" data-culture="${culture}" data-population=${population} data-type="${type}">
|
||||
<span data-tip="Click to zoom into view" class="icon-dot-circled pointer"></span>
|
||||
>>>>>>> master
|
||||
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false">
|
||||
<input data-tip="Burg province" class="burgState" value="${province}" disabled>
|
||||
<input data-tip="Burg state" class="burgState" value="${state}" disabled>
|
||||
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(
|
||||
b.culture
|
||||
)}</select>
|
||||
<span data-tip="Burg population" class="icon-male"></span>
|
||||
<input data-tip="Burg population. Type to change" class="burgPopulation" value=${si(population)}>
|
||||
<div class="burgType">
|
||||
<<<<<<< HEAD
|
||||
<span data-tip="${b.capital ? ' This burg is a state capital' : 'Click to assign a capital status'}" class="icon-star-empty${b.capital ? '' : ' inactive pointer'}"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? '' : ' inactive'}" style="font-size:.9em"></span>
|
||||
</div>
|
||||
<span data-tip="Zoom to burg" class="icon-dot-circled pointer"></span>
|
||||
<span class="locks pointer ${b.lock ? 'icon-lock' : 'icon-lock-open inactive'}"></span>
|
||||
=======
|
||||
<span data-tip="${b.capital ? " This burg is a state capital" : "Click to assign a capital status"}" class="icon-star-empty${
|
||||
b.capital ? "" : " inactive pointer"
|
||||
}"></span>
|
||||
<span data-tip="Click to toggle port status" class="icon-anchor pointer${b.port ? "" : " inactive"}" style="font-size:.9em"></span>
|
||||
</div>
|
||||
<span data-tip="Edit burg" class="icon-pencil"></span>
|
||||
<span class="locks pointer ${b.lock ? "icon-lock" : "icon-lock-open inactive"}" onmouseover="showElementLockTip(event)"></span>
|
||||
>>>>>>> master
|
||||
<span data-tip="Remove burg" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.insertAdjacentHTML('beforeend', lines);
|
||||
|
||||
// update footer
|
||||
burgsFooterBurgs.innerHTML = filtered.length;
|
||||
burgsFooterPopulation.innerHTML = filtered.length ? si(totalPopulation / filtered.length) : 0;
|
||||
|
||||
// add listeners
|
||||
<<<<<<< HEAD
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => burgHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => burgHighlightOff(ev)));
|
||||
body.querySelectorAll('div > input.burgName').forEach((el) => el.addEventListener('input', changeBurgName));
|
||||
body.querySelectorAll('div > span.icon-dot-circled').forEach((el) => el.addEventListener('click', zoomIntoBurg));
|
||||
body.querySelectorAll('div > select.stateCulture').forEach((el) => el.addEventListener('change', changeBurgCulture));
|
||||
body.querySelectorAll('div > input.burgPopulation').forEach((el) => el.addEventListener('change', changeBurgPopulation));
|
||||
body.querySelectorAll('div > span.icon-star-empty').forEach((el) => el.addEventListener('click', toggleCapitalStatus));
|
||||
body.querySelectorAll('div > span.icon-anchor').forEach((el) => el.addEventListener('click', togglePortStatus));
|
||||
body.querySelectorAll('div > span.locks').forEach((el) => el.addEventListener('click', toggleBurgLockStatus));
|
||||
body.querySelectorAll('div > span.locks').forEach((el) => el.addEventListener('mouseover', showBurgOLockTip));
|
||||
body.querySelectorAll('div > span.icon-pencil').forEach((el) => el.addEventListener('click', openBurgEditor));
|
||||
body.querySelectorAll('div > span.icon-trash-empty').forEach((el) => el.addEventListener('click', triggerBurgRemove));
|
||||
=======
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => burgHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => burgHighlightOff(ev)));
|
||||
body.querySelectorAll("div > input.burgName").forEach(el => el.addEventListener("input", changeBurgName));
|
||||
body.querySelectorAll("div > span.icon-dot-circled").forEach(el => el.addEventListener("click", zoomIntoBurg));
|
||||
body.querySelectorAll("div > select.stateCulture").forEach(el => el.addEventListener("change", changeBurgCulture));
|
||||
body.querySelectorAll("div > input.burgPopulation").forEach(el => el.addEventListener("change", changeBurgPopulation));
|
||||
body.querySelectorAll("div > span.icon-star-empty").forEach(el => el.addEventListener("click", toggleCapitalStatus));
|
||||
body.querySelectorAll("div > span.icon-anchor").forEach(el => el.addEventListener("click", togglePortStatus));
|
||||
body.querySelectorAll("div > span.locks").forEach(el => el.addEventListener("click", toggleBurgLockStatus));
|
||||
body.querySelectorAll("div > span.icon-pencil").forEach(el => el.addEventListener("click", openBurgEditor));
|
||||
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", triggerBurgRemove));
|
||||
>>>>>>> master
|
||||
|
||||
applySorting(burgsHeader);
|
||||
}
|
||||
|
||||
function getCultureOptions(culture) {
|
||||
let options = '';
|
||||
pack.cultures.filter((c) => !c.removed).forEach((c) => (options += `<option ${c.i === culture ? 'selected' : ''} value="${c.i}">${c.name}</option>`));
|
||||
return options;
|
||||
}
|
||||
|
||||
function burgHighlightOn(event) {
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
const burg = +event.target.dataset.id;
|
||||
burgLabels.select("[data-id='" + burg + "']").classed('drag', true);
|
||||
}
|
||||
|
||||
function burgHighlightOff() {
|
||||
burgLabels.selectAll('text.drag').classed('drag', false);
|
||||
}
|
||||
|
||||
function changeBurgName() {
|
||||
if (this.value == '') tip('Please provide a name', false, 'error');
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
pack.burgs[burg].name = this.value;
|
||||
this.parentNode.dataset.name = this.value;
|
||||
const label = document.querySelector("#burgLabels [data-id='" + burg + "']");
|
||||
if (label) label.innerHTML = this.value;
|
||||
}
|
||||
|
||||
function zoomIntoBurg() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
const label = document.querySelector("#burgLabels [data-id='" + burg + "']");
|
||||
const x = +label.getAttribute('x'),
|
||||
y = +label.getAttribute('y');
|
||||
zoomTo(x, y, 8, 2000);
|
||||
}
|
||||
|
||||
function changeBurgCulture() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
const v = +this.value;
|
||||
pack.burgs[burg].culture = v;
|
||||
this.parentNode.dataset.culture = pack.cultures[v].name;
|
||||
}
|
||||
|
||||
function changeBurgPopulation() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
if (this.value == '' || isNaN(+this.value)) {
|
||||
tip('Please provide an integer number (like 10000, not 10K)', false, 'error');
|
||||
this.value = si(pack.burgs[burg].population * populationRate * urbanization);
|
||||
return;
|
||||
}
|
||||
pack.burgs[burg].population = this.value / populationRate / urbanization;
|
||||
this.parentNode.dataset.population = this.value;
|
||||
this.value = si(this.value);
|
||||
|
||||
const population = [];
|
||||
body.querySelectorAll(':scope > div').forEach((el) => population.push(+getInteger(el.dataset.population)));
|
||||
burgsFooterPopulation.innerHTML = si(d3.mean(population));
|
||||
}
|
||||
|
||||
function toggleCapitalStatus() {
|
||||
const burg = +this.parentNode.parentNode.dataset.id;
|
||||
toggleCapital(burg);
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
|
||||
function togglePortStatus() {
|
||||
const burg = +this.parentNode.parentNode.dataset.id;
|
||||
togglePort(burg);
|
||||
if (this.classList.contains('inactive')) this.classList.remove('inactive');
|
||||
else this.classList.add('inactive');
|
||||
}
|
||||
|
||||
function toggleBurgLockStatus() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
toggleBurgLock(burg);
|
||||
if (this.classList.contains('icon-lock')) {
|
||||
this.classList.remove('icon-lock');
|
||||
this.classList.add('icon-lock-open');
|
||||
this.classList.add('inactive');
|
||||
} else {
|
||||
this.classList.remove('icon-lock-open');
|
||||
this.classList.add('icon-lock');
|
||||
this.classList.remove('inactive');
|
||||
}
|
||||
}
|
||||
|
||||
function openBurgEditor() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
editBurg(burg);
|
||||
}
|
||||
|
||||
function triggerBurgRemove() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
if (pack.burgs[burg].capital) {
|
||||
tip('You cannot remove the capital. Please change the capital first', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = 'Are you sure you want to remove the burg? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
removeBurg(burg);
|
||||
burgsOverviewAddLines();
|
||||
};
|
||||
confirmationDialog({title: 'Remove burg', message, confirm: 'Remove', onConfirm});
|
||||
}
|
||||
|
||||
function regenerateNames() {
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
const burg = +el.dataset.id;
|
||||
//if (pack.burgs[burg].lock) return;
|
||||
const culture = pack.burgs[burg].culture;
|
||||
const name = Names.getCulture(culture);
|
||||
if (!pack.burgs[burg].lock) {
|
||||
el.querySelector('.burgName').value = name;
|
||||
pack.burgs[burg].name = el.dataset.name = name;
|
||||
burgLabels.select("[data-id='" + burg + "']").text(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function enterAddBurgMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
exitAddBurgMode();
|
||||
return;
|
||||
}
|
||||
customization = 3;
|
||||
this.classList.add('pressed');
|
||||
tip('Click on the map to create a new burg. Hold Shift to add multiple', true, 'warn');
|
||||
viewbox.style('cursor', 'crosshair').on('click', addBurgOnClick);
|
||||
}
|
||||
|
||||
function addBurgOnClick() {
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[cell] < 20) return tip('You cannot place state into the water. Please click on a land cell', false, 'error');
|
||||
if (pack.cells.burg[cell]) return tip('There is already a burg in this cell. Please select a free cell', false, 'error');
|
||||
|
||||
addBurg(point); // add new burg
|
||||
|
||||
if (d3.event.shiftKey === false) {
|
||||
exitAddBurgMode();
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
||||
function exitAddBurgMode() {
|
||||
customization = 0;
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
if (addBurgTool.classList.contains('pressed')) addBurgTool.classList.remove('pressed');
|
||||
if (addNewBurg.classList.contains('pressed')) addNewBurg.classList.remove('pressed');
|
||||
}
|
||||
|
||||
function showBurgsChart() {
|
||||
// build hierarchy tree
|
||||
const states = pack.states.map((s) => {
|
||||
const color = s.color ? s.color : '#ccc';
|
||||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, state: s.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const burgs = pack.burgs
|
||||
.filter((b) => b.i && !b.removed)
|
||||
.map((b) => {
|
||||
const id = b.i + states.length - 1;
|
||||
const population = b.population;
|
||||
const capital = b.capital;
|
||||
const province = pack.cells.province[b.cell];
|
||||
const parent = province ? province + states.length - 1 : b.state;
|
||||
return {id, i: b.i, state: b.state, culture: b.culture, province, parent, name: b.name, population, capital, x: b.x, y: b.y};
|
||||
});
|
||||
const data = states.concat(burgs);
|
||||
if (data.length < 2) return tip("No burgs to show", false, "error");
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.parentId((d) => d.state)(data)
|
||||
.sum((d) => d.population)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
const width = 150 + 200 * uiSizeOutput.value,
|
||||
height = 150 + 200 * uiSizeOutput.value;
|
||||
const margin = {top: 0, right: -50, bottom: -10, left: -50};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height - margin.top - margin.bottom;
|
||||
const treeLayout = d3.pack().size([w, h]).padding(3);
|
||||
|
||||
// prepare svg
|
||||
alertMessage.innerHTML = `<select id="burgsTreeType" style="display:block; margin-left:13px; font-size:11px">
|
||||
<option value="states" selected>Group by state</option>
|
||||
<option value="cultures">Group by culture</option>
|
||||
<option value="parent">Group by province and state</option>
|
||||
<option value="provinces">Group by province</option></select>`;
|
||||
alertMessage.innerHTML += `<div id='burgsInfo' class='chartInfo'>‍</div>`;
|
||||
const svg = d3
|
||||
.select('#alertMessage')
|
||||
.insert('svg', '#burgsInfo')
|
||||
.attr('id', 'burgsTree')
|
||||
.attr('width', width)
|
||||
.attr('height', height - 10)
|
||||
.attr('stroke-width', 2);
|
||||
const graph = svg.append('g').attr('transform', `translate(-50, -10)`);
|
||||
document.getElementById('burgsTreeType').addEventListener('change', updateChart);
|
||||
|
||||
treeLayout(root);
|
||||
|
||||
const node = graph
|
||||
.selectAll('circle')
|
||||
.data(root.leaves())
|
||||
.join('circle')
|
||||
.attr('data-id', (d) => d.data.i)
|
||||
.attr('r', (d) => d.r)
|
||||
.attr('fill', (d) => d.parent.data.color)
|
||||
.attr('cx', (d) => d.x)
|
||||
.attr('cy', (d) => d.y)
|
||||
.on('mouseenter', (d) => showInfo(event, d))
|
||||
.on('mouseleave', (d) => hideInfo(event, d))
|
||||
.on('click', (d) => zoomTo(d.data.x, d.data.y, 8, 2000));
|
||||
|
||||
function showInfo(ev, d) {
|
||||
d3.select(ev.target).transition().duration(1500).attr('stroke', '#c13119');
|
||||
const name = d.data.name;
|
||||
const parent = d.parent.data.name;
|
||||
const population = si(d.value * populationRate * urbanization);
|
||||
|
||||
burgsInfo.innerHTML = `${name}. ${parent}. Population: ${population}`;
|
||||
burgHighlightOn(ev);
|
||||
tip('Click to zoom into view');
|
||||
}
|
||||
|
||||
function hideInfo(ev) {
|
||||
burgHighlightOff(ev);
|
||||
if (!document.getElementById('burgsInfo')) return;
|
||||
burgsInfo.innerHTML = '‍';
|
||||
d3.select(ev.target).transition().attr('stroke', null);
|
||||
tip('');
|
||||
}
|
||||
|
||||
function updateChart() {
|
||||
const getStatesData = () =>
|
||||
pack.states.map((s) => {
|
||||
const color = s.color ? s.color : '#ccc';
|
||||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, state: s.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const getCulturesData = () =>
|
||||
pack.cultures.map((c) => {
|
||||
const color = c.color ? c.color : '#ccc';
|
||||
return {id: c.i, culture: c.i ? 0 : null, color, name: c.name};
|
||||
});
|
||||
|
||||
const getParentData = () => {
|
||||
const states = pack.states.map((s) => {
|
||||
const color = s.color ? s.color : '#ccc';
|
||||
const name = s.fullName ? s.fullName : s.name;
|
||||
return {id: s.i, parent: s.i ? 0 : null, color, name};
|
||||
});
|
||||
const provinces = pack.provinces
|
||||
.filter((p) => p.i && !p.removed)
|
||||
.map((p) => {
|
||||
return {id: p.i + states.length - 1, parent: p.state, color: p.color, name: p.fullName};
|
||||
});
|
||||
return states.concat(provinces);
|
||||
};
|
||||
|
||||
const getProvincesData = () =>
|
||||
pack.provinces.map((p) => {
|
||||
const color = p.color ? p.color : '#ccc';
|
||||
const name = p.fullName ? p.fullName : p.name;
|
||||
return {id: p.i ? p.i : 0, province: p.i ? 0 : null, color, name};
|
||||
});
|
||||
|
||||
const value = (d) => {
|
||||
if (this.value === 'states') return d.state;
|
||||
if (this.value === 'cultures') return d.culture;
|
||||
if (this.value === 'parent') return d.parent;
|
||||
if (this.value === 'provinces') return d.province;
|
||||
};
|
||||
|
||||
<<<<<<< HEAD
|
||||
const base = this.value === 'states' ? getStatesData() : this.value === 'cultures' ? getCulturesData() : this.value === 'parent' ? getParentData() : getProvincesData();
|
||||
burgs.forEach((b) => (b.id = b.i + base.length - 1));
|
||||
=======
|
||||
const base =
|
||||
this.value === "states"
|
||||
? getStatesData()
|
||||
: this.value === "cultures"
|
||||
? getCulturesData()
|
||||
: this.value === "parent"
|
||||
? getParentData()
|
||||
: getProvincesData();
|
||||
burgs.forEach(b => (b.id = b.i + base.length - 1));
|
||||
>>>>>>> master
|
||||
|
||||
const data = base.concat(burgs);
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.parentId((d) => value(d))(data)
|
||||
.sum((d) => d.population)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
node
|
||||
.data(treeLayout(root).leaves())
|
||||
.transition()
|
||||
.duration(2000)
|
||||
.attr('data-id', (d) => d.data.i)
|
||||
.attr('fill', (d) => d.parent.data.color)
|
||||
.attr('cx', (d) => d.x)
|
||||
.attr('cy', (d) => d.y)
|
||||
.attr('r', (d) => d.r);
|
||||
}
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'Burgs bubble chart',
|
||||
width: fitContent(),
|
||||
position: {my: 'left bottom', at: 'left+10 bottom-10', of: 'svg'},
|
||||
buttons: {},
|
||||
close: () => {
|
||||
alertMessage.innerHTML = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function downloadBurgsData() {
|
||||
<<<<<<< HEAD
|
||||
let data = 'Id,Burg,Province,State,Culture,Religion,Population,Longitude,Latitude,Elevation (' + heightUnit.value + '),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n'; // headers
|
||||
const valid = pack.burgs.filter((b) => b.i && !b.removed); // all valid burgs
|
||||
=======
|
||||
let data =
|
||||
"Id,Burg,Province,Province Full Name,State,State Full Name,Culture,Religion,Population,Longitude,Latitude,Elevation (" +
|
||||
heightUnit.value +
|
||||
"),Capital,Port,Citadel,Walls,Plaza,Temple,Shanty Town\n"; // headers
|
||||
const valid = pack.burgs.filter(b => b.i && !b.removed); // all valid burgs
|
||||
>>>>>>> master
|
||||
|
||||
valid.forEach((b) => {
|
||||
data += b.i + ',';
|
||||
data += b.name + ',';
|
||||
const province = pack.cells.province[b.cell];
|
||||
<<<<<<< HEAD
|
||||
data += province ? pack.provinces[province].fullName + ',' : ',';
|
||||
data += b.state ? pack.states[b.state].fullName + ',' : pack.states[b.state].name + ',';
|
||||
data += pack.cultures[b.culture].name + ',';
|
||||
data += pack.religions[pack.cells.religion[b.cell]].name + ',';
|
||||
data += rn(b.population * populationRate * urbanization) + ',';
|
||||
=======
|
||||
data += province ? pack.provinces[province].name + "," : ",";
|
||||
data += province ? pack.provinces[province].fullName + "," : ",";
|
||||
data += pack.states[b.state].name + ",";
|
||||
data += pack.states[b.state].fullName + ",";
|
||||
data += pack.cultures[b.culture].name + ",";
|
||||
data += pack.religions[pack.cells.religion[b.cell]].name + ",";
|
||||
data += rn(b.population * populationRate * urbanization) + ",";
|
||||
>>>>>>> master
|
||||
|
||||
// add geography data
|
||||
data += mapCoordinates.lonW + (b.x / graphWidth) * mapCoordinates.lonT + ',';
|
||||
data += mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT + ','; // this is inverted in QGIS otherwise
|
||||
data += parseInt(getHeight(pack.cells.h[b.cell])) + ',';
|
||||
|
||||
// add status data
|
||||
data += b.capital ? 'capital,' : ',';
|
||||
data += b.port ? 'port,' : ',';
|
||||
data += b.citadel ? 'citadel,' : ',';
|
||||
data += b.walls ? 'walls,' : ',';
|
||||
data += b.plaza ? 'plaza,' : ',';
|
||||
data += b.temple ? 'temple,' : ',';
|
||||
data += b.shanty ? 'shanty town\n' : '\n';
|
||||
});
|
||||
|
||||
const name = getFileName('Burgs') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function renameBurgsInBulk() {
|
||||
const message = `Download burgs list as a text file, make changes and re-upload the file.
|
||||
If you do not want to change the name, just leave it as is`;
|
||||
alertMessage.innerHTML = message;
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'Burgs bulk renaming',
|
||||
width: '22em',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Download: function () {
|
||||
const data = pack.burgs
|
||||
.filter((b) => b.i && !b.removed)
|
||||
.map((b) => b.name)
|
||||
.join('\r\n');
|
||||
const name = getFileName('Burg names') + '.txt';
|
||||
downloadFile(data, name);
|
||||
},
|
||||
Upload: () => burgsListToLoad.click(),
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function importBurgNames(dataLoaded) {
|
||||
if (!dataLoaded) return tip('Cannot load the file, please check the format', false, 'error');
|
||||
const data = dataLoaded.split('\r\n');
|
||||
if (!data.length) return tip('Cannot parse the list, please check the file format', false, 'error');
|
||||
|
||||
let change = [],
|
||||
message = `Burgs will be renamed as below. Please confirm`;
|
||||
message += `<table class="overflow-table"><tr><th>Id</th><th>Current name</th><th>New Name</th></tr>`;
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
for (let i = 0; i < data.length && i <= burgs.length; i++) {
|
||||
const v = data[i];
|
||||
if (!v || !burgs[i] || v == burgs[i].name) continue;
|
||||
change.push({id: burgs[i].i, name: v});
|
||||
message += `<tr><td style="width:20%">${burgs[i].i}</td><td style="width:40%">${burgs[i].name}</td><td style="width:40%">${v}</td></tr>`;
|
||||
}
|
||||
message += `</tr></table>`;
|
||||
if (!change.length) message = 'No changes found in the file. Please change some names to get a result';
|
||||
alertMessage.innerHTML = message;
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'Burgs bulk renaming',
|
||||
width: '22em',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Confirm: function () {
|
||||
for (let i = 0; i < change.length; i++) {
|
||||
const id = change[i].id;
|
||||
pack.burgs[id].name = change[i].name;
|
||||
burgLabels.select("[data-id='" + id + "']").text(change[i].name);
|
||||
}
|
||||
$(this).dialog('close');
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllBurgsRemove() {
|
||||
const message = 'Are you sure you want to remove all unlocked burgs except for capitals?<br><i>To remove a capital you have to remove the state first</i>';
|
||||
confirmationDialog({title: 'Remove all burgs', message, confirm: 'Remove', onConfirm: removeAllBurgs});
|
||||
}
|
||||
|
||||
function removeAllBurgs() {
|
||||
pack.burgs.filter((b) => b.i && !(b.capital || b.lock)).forEach((b) => removeBurg(b.i));
|
||||
burgsOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
|
@ -63,8 +63,8 @@ function editCultures() {
|
|||
function culturesEditorAddLines() {
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
let lines = '',
|
||||
totalArea = 0,
|
||||
totalPopulation = 0;
|
||||
let totalArea = 0;
|
||||
let totalPopulation = 0;
|
||||
|
||||
const emblemShapeGroup = document.getElementById('emblemShape').selectedOptions[0].parentNode.label;
|
||||
const selectShape = emblemShapeGroup === 'Diversiform';
|
||||
|
|
@ -84,7 +84,8 @@ function editCultures() {
|
|||
lines += `<div class="states" data-id=${c.i} data-name="${c.name}" data-color="" data-cells=${c.cells}
|
||||
data-area=${area} data-population=${population} data-base=${c.base} data-type="" data-expansionism="" data-emblems="${c.shield}">
|
||||
<svg width="9" height="9" class="placeholder"></svg>
|
||||
<input data-tip="Culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<input data-tip="Neutral culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<span class="icon-cw placeholder"></span>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span class="icon-resize-full placeholder hide"></span>
|
||||
|
|
@ -96,17 +97,22 @@ function editCultures() {
|
|||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ''}
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
</div>`;
|
||||
continue;
|
||||
}
|
||||
|
||||
lines += `<div class="states cultures" data-id=${c.i} data-name="${c.name}" data-color="${c.color}" data-cells=${c.cells}
|
||||
data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}">
|
||||
<svg data-tip="Culture fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
c.color
|
||||
}" class="fillRect pointer"></svg>
|
||||
<svg data-tip="Culture fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${c.color}" class="fillRect pointer">
|
||||
</svg>
|
||||
<input data-tip="Culture name. Click and type to change" class="cultureName" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span data-tip="Culture expansionism. Defines competitive size" class="icon-resize-full hide"></span>
|
||||
|
|
@ -120,7 +126,11 @@ function editCultures() {
|
|||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ''}
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
<span data-tip="Remove culture" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -141,6 +151,7 @@ function editCultures() {
|
|||
body.querySelectorAll('rect.fillRect').forEach((el) => el.addEventListener('click', cultureChangeColor));
|
||||
body.querySelectorAll('div > input.cultureName').forEach((el) => el.addEventListener('input', cultureChangeName));
|
||||
body.querySelectorAll('div > input.statePower').forEach((el) => el.addEventListener('input', cultureChangeExpansionism));
|
||||
body.querySelectorAll("div > span.icon-cw").forEach(el => el.addEventListener("click", cultureRegenerateName));
|
||||
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));
|
||||
|
|
@ -262,6 +273,13 @@ function editCultures() {
|
|||
);
|
||||
}
|
||||
|
||||
function cultureRegenerateName() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const name = Names.getCultureShort(culture);
|
||||
this.parentNode.querySelector("input.cultureName").value = name;
|
||||
pack.cultures[culture].name = name;
|
||||
}
|
||||
|
||||
function cultureChangeExpansionism() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
this.parentNode.dataset.expansionism = this.value;
|
||||
|
|
@ -534,6 +552,9 @@ function editCultures() {
|
|||
const graph = svg.append('g').attr('transform', `translate(10, -45)`);
|
||||
const links = graph.append('g').attr('fill', 'none').attr('stroke', '#aaaaaa');
|
||||
const nodes = graph.append('g');
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.style("text-anchor", "middle");
|
||||
|
||||
renderTree();
|
||||
function renderTree() {
|
||||
|
|
@ -683,6 +704,10 @@ function editCultures() {
|
|||
|
||||
tip('Click on culture to select, drag the circle to change culture', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectCultureOnMapClick).call(d3.drag().on('start', dragCultureBrush)).on('touchmove mousemove', moveCultureBrush);
|
||||
.style("cursor", "crosshair")
|
||||
.on("click", selectCultureOnMapClick)
|
||||
.call(d3.drag().on("start", dragCultureBrush))
|
||||
.on("touchmove mousemove", moveCultureBrush);
|
||||
|
||||
body.querySelector('div').classList.add('selected');
|
||||
}
|
||||
|
|
@ -733,7 +758,14 @@ function editCultures() {
|
|||
|
||||
// change of append new element
|
||||
if (exists.size()) exists.attr('data-culture', cultureNew).attr('fill', color).attr('stroke', color);
|
||||
else temp.append('polygon').attr('data-cell', i).attr('data-culture', cultureNew).attr('points', getPackPolygon(i)).attr('fill', color).attr('stroke', color);
|
||||
else
|
||||
temp
|
||||
.append("polygon")
|
||||
.attr("data-cell", i)
|
||||
.attr("data-culture", cultureNew)
|
||||
.attr("points", getPackPolygon(i))
|
||||
.attr("fill", color)
|
||||
.attr("stroke", color);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
965
modules/ui/cultures-editor.js.orig
Normal file
965
modules/ui/cultures-editor.js.orig
Normal file
|
|
@ -0,0 +1,965 @@
|
|||
'use strict';
|
||||
function editCultures() {
|
||||
if (customization) return;
|
||||
closeDialogs('#culturesEditor, .stable');
|
||||
if (!layerIsOn('toggleCultures')) toggleCultures();
|
||||
if (layerIsOn('toggleStates')) toggleStates();
|
||||
if (layerIsOn('toggleBiomes')) toggleBiomes();
|
||||
if (layerIsOn('toggleReligions')) toggleReligions();
|
||||
if (layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
|
||||
const body = document.getElementById('culturesBody');
|
||||
drawCultureCenters();
|
||||
refreshCulturesEditor();
|
||||
|
||||
if (modules.editCultures) return;
|
||||
modules.editCultures = true;
|
||||
|
||||
$('#culturesEditor').dialog({
|
||||
title: 'Cultures Editor',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: closeCulturesEditor,
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg'}
|
||||
});
|
||||
body.focus();
|
||||
|
||||
// add listeners
|
||||
document.getElementById('culturesEditorRefresh').addEventListener('click', refreshCulturesEditor);
|
||||
document.getElementById('culturesEditStyle').addEventListener('click', () => editStyle('cults'));
|
||||
document.getElementById('culturesLegend').addEventListener('click', toggleLegend);
|
||||
document.getElementById('culturesPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('culturesHeirarchy').addEventListener('click', showHierarchy);
|
||||
document.getElementById('culturesRecalculate').addEventListener('click', () => recalculateCultures(true));
|
||||
document.getElementById('culturesManually').addEventListener('click', enterCultureManualAssignent);
|
||||
document.getElementById('culturesManuallyApply').addEventListener('click', applyCultureManualAssignent);
|
||||
document.getElementById('culturesManuallyCancel').addEventListener('click', () => exitCulturesManualAssignment());
|
||||
document.getElementById('culturesEditNamesBase').addEventListener('click', editNamesbase);
|
||||
document.getElementById('culturesAdd').addEventListener('click', enterAddCulturesMode);
|
||||
document.getElementById('culturesExport').addEventListener('click', downloadCulturesData);
|
||||
|
||||
function refreshCulturesEditor() {
|
||||
culturesCollectStatistics();
|
||||
culturesEditorAddLines();
|
||||
drawCultureCenters();
|
||||
}
|
||||
|
||||
function culturesCollectStatistics() {
|
||||
const cells = pack.cells,
|
||||
cultures = pack.cultures;
|
||||
cultures.forEach((c) => (c.cells = c.area = c.rural = c.urban = 0));
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (cells.h[i] < 20) continue;
|
||||
const c = cells.culture[i];
|
||||
cultures[c].cells += 1;
|
||||
cultures[c].area += cells.area[i];
|
||||
cultures[c].rural += cells.pop[i];
|
||||
if (cells.burg[i]) cultures[c].urban += pack.burgs[cells.burg[i]].population;
|
||||
}
|
||||
}
|
||||
|
||||
// add line for each culture
|
||||
function culturesEditorAddLines() {
|
||||
<<<<<<< HEAD
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
let lines = '',
|
||||
totalArea = 0,
|
||||
totalPopulation = 0;
|
||||
=======
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
let lines = "";
|
||||
let totalArea = 0;
|
||||
let totalPopulation = 0;
|
||||
>>>>>>> master
|
||||
|
||||
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;
|
||||
const rural = c.rural * populationRate;
|
||||
const urban = c.urban * populationRate * urbanization;
|
||||
const population = rn(rural + urban);
|
||||
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to edit`;
|
||||
totalArea += area;
|
||||
totalPopulation += population;
|
||||
|
||||
if (!c.i) {
|
||||
// Uncultured (neutral) line
|
||||
lines += `<div class="states" data-id=${c.i} data-name="${c.name}" data-color="" data-cells=${c.cells}
|
||||
data-area=${area} data-population=${population} data-base=${c.base} data-type="" data-expansionism="" data-emblems="${c.shield}">
|
||||
<svg width="9" height="9" class="placeholder"></svg>
|
||||
<input data-tip="Neutral culture name. Click and type to change" class="cultureName italic" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<span class="icon-cw placeholder"></span>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span class="icon-resize-full placeholder hide"></span>
|
||||
<input class="statePower placeholder hide" type="number">
|
||||
<select class="cultureType placeholder">${getTypeOptions(c.type)}</select>
|
||||
<span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<div data-tip="Culture area" class="biomeArea hide">${si(area) + unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
<<<<<<< HEAD
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ''}
|
||||
=======
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
>>>>>>> master
|
||||
</div>`;
|
||||
continue;
|
||||
}
|
||||
|
||||
lines += `<div class="states cultures" data-id=${c.i} data-name="${c.name}" data-color="${c.color}" data-cells=${c.cells}
|
||||
data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}">
|
||||
<<<<<<< HEAD
|
||||
<svg data-tip="Culture fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
c.color
|
||||
}" class="fillRect pointer"></svg>
|
||||
=======
|
||||
<svg data-tip="Culture fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px">
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="${c.color}" class="fillRect pointer">
|
||||
</svg>
|
||||
>>>>>>> master
|
||||
<input data-tip="Culture name. Click and type to change" class="cultureName" value="${c.name}" autocorrect="off" spellcheck="false">
|
||||
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
|
||||
<span data-tip="Cells count" class="icon-check-empty hide"></span>
|
||||
<div data-tip="Cells count" class="stateCells hide">${c.cells}</div>
|
||||
<span data-tip="Culture expansionism. Defines competitive size" class="icon-resize-full hide"></span>
|
||||
<input data-tip="Culture expansionism. Defines competitive size. Click to change, then click Recalculate to apply change" class="statePower hide" type="number" min=0 max=99 step=.1 value=${
|
||||
c.expansionism
|
||||
}>
|
||||
<select data-tip="Culture type. Defines growth model. Click to change" class="cultureType">${getTypeOptions(c.type)}</select>
|
||||
<span data-tip="Culture area" style="padding-right: 4px" class="icon-map-o hide"></span>
|
||||
<div data-tip="Culture area" class="biomeArea hide">${si(area) + unit}</div>
|
||||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Click to re-generate names for burgs with this culture assigned" class="icon-arrows-cw hide"></span>
|
||||
<select data-tip="Culture namesbase. Click to change. Click on arrows to re-generate names" class="cultureBase">${getBaseOptions(c.base)}</select>
|
||||
<<<<<<< HEAD
|
||||
${selectShape ? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>` : ''}
|
||||
=======
|
||||
${
|
||||
selectShape
|
||||
? `<select data-tip="Emblem shape associated with culture. Click to change" class="cultureShape hide">${getShapeOptions(c.shield)}</select>`
|
||||
: ""
|
||||
}
|
||||
>>>>>>> master
|
||||
<span data-tip="Remove culture" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.innerHTML = lines;
|
||||
|
||||
// update footer
|
||||
culturesFooterCultures.innerHTML = pack.cultures.filter((c) => c.i && !c.removed).length;
|
||||
culturesFooterCells.innerHTML = pack.cells.h.filter((h) => h >= 20).length;
|
||||
culturesFooterArea.innerHTML = si(totalArea) + unit;
|
||||
culturesFooterPopulation.innerHTML = si(totalPopulation);
|
||||
culturesFooterArea.dataset.area = totalArea;
|
||||
culturesFooterPopulation.dataset.population = totalPopulation;
|
||||
|
||||
// add listeners
|
||||
<<<<<<< HEAD
|
||||
body.querySelectorAll('div.cultures').forEach((el) => el.addEventListener('mouseenter', (ev) => cultureHighlightOn(ev)));
|
||||
body.querySelectorAll('div.cultures').forEach((el) => el.addEventListener('mouseleave', (ev) => cultureHighlightOff(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('click', selectCultureOnLineClick));
|
||||
body.querySelectorAll('rect.fillRect').forEach((el) => el.addEventListener('click', cultureChangeColor));
|
||||
body.querySelectorAll('div > input.cultureName').forEach((el) => el.addEventListener('input', cultureChangeName));
|
||||
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));
|
||||
|
||||
culturesHeader.querySelector("div[data-sortby='emblems']").style.display = selectShape ? 'inline-block' : 'none';
|
||||
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
=======
|
||||
body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseenter", ev => cultureHighlightOn(ev)));
|
||||
body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseleave", ev => cultureHighlightOff(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectCultureOnLineClick));
|
||||
body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", cultureChangeColor));
|
||||
body.querySelectorAll("div > input.cultureName").forEach(el => el.addEventListener("input", cultureChangeName));
|
||||
body.querySelectorAll("div > span.icon-cw").forEach(el => el.addEventListener("click", cultureRegenerateName));
|
||||
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));
|
||||
|
||||
culturesHeader.querySelector("div[data-sortby='emblems']").style.display = selectShape ? "inline-block" : "none";
|
||||
|
||||
if (body.dataset.type === "percentage") {
|
||||
body.dataset.type = "absolute";
|
||||
>>>>>>> master
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(culturesHeader);
|
||||
$('#culturesEditor').dialog({width: fitContent()});
|
||||
}
|
||||
|
||||
function getTypeOptions(type) {
|
||||
let options = '';
|
||||
const types = ['Generic', 'River', 'Lake', 'Naval', 'Nomadic', 'Hunting', 'Highland'];
|
||||
types.forEach((t) => (options += `<option ${type === t ? 'selected' : ''} value="${t}">${t}</option>`));
|
||||
return options;
|
||||
}
|
||||
|
||||
function getBaseOptions(base) {
|
||||
let options = '';
|
||||
nameBases.forEach((n, i) => (options += `<option ${base === i ? 'selected' : ''} value="${i}">${n.name}</option>`));
|
||||
return options;
|
||||
}
|
||||
|
||||
function getShapeOptions(selected) {
|
||||
const shapes = Object.keys(COA.shields.types)
|
||||
.map((type) => Object.keys(COA.shields[type]))
|
||||
.flat();
|
||||
return shapes.map((shape) => `<option ${shape === selected ? 'selected' : ''} value="${shape}">${capitalize(shape)}</option>`);
|
||||
}
|
||||
|
||||
function cultureHighlightOn(event) {
|
||||
const culture = +event.target.dataset.id;
|
||||
const info = document.getElementById('cultureInfo');
|
||||
if (info) {
|
||||
d3.select('#hierarchy')
|
||||
.select("g[data-id='" + culture + "'] > path")
|
||||
.classed('selected', 1);
|
||||
const c = pack.cultures[culture];
|
||||
const rural = c.rural * populationRate;
|
||||
const urban = c.urban * populationRate * urbanization;
|
||||
const population = rural + urban > 0 ? si(rn(rural + urban)) + ' people' : 'Extinct';
|
||||
info.innerHTML = `${c.name} culture. ${c.type}. ${population}`;
|
||||
tip('Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation');
|
||||
}
|
||||
|
||||
if (!layerIsOn('toggleCultures')) return;
|
||||
if (customization) return;
|
||||
const animate = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||
cults
|
||||
.select('#culture' + culture)
|
||||
.raise()
|
||||
.transition(animate)
|
||||
.attr('stroke-width', 2.5)
|
||||
.attr('stroke', '#d0240f');
|
||||
debug
|
||||
.select('#cultureCenter' + culture)
|
||||
.raise()
|
||||
.transition(animate)
|
||||
.attr('r', 8)
|
||||
.attr('stroke', '#d0240f');
|
||||
}
|
||||
|
||||
function cultureHighlightOff(event) {
|
||||
const culture = +event.target.dataset.id;
|
||||
const info = document.getElementById('cultureInfo');
|
||||
if (info) {
|
||||
d3.select('#hierarchy')
|
||||
.select("g[data-id='" + culture + "'] > path")
|
||||
.classed('selected', 0);
|
||||
info.innerHTML = '‍';
|
||||
tip('');
|
||||
}
|
||||
|
||||
if (!layerIsOn('toggleCultures')) return;
|
||||
cults
|
||||
.select('#culture' + culture)
|
||||
.transition()
|
||||
.attr('stroke-width', null)
|
||||
.attr('stroke', null);
|
||||
debug
|
||||
.select('#cultureCenter' + culture)
|
||||
.transition()
|
||||
.attr('r', 6)
|
||||
.attr('stroke', null);
|
||||
}
|
||||
|
||||
function cultureChangeColor() {
|
||||
const el = this;
|
||||
const currentFill = el.getAttribute('fill');
|
||||
const culture = +el.parentNode.parentNode.dataset.id;
|
||||
|
||||
const callback = function (fill) {
|
||||
el.setAttribute('fill', fill);
|
||||
pack.cultures[culture].color = fill;
|
||||
cults
|
||||
.select('#culture' + culture)
|
||||
.attr('fill', fill)
|
||||
.attr('stroke', fill);
|
||||
debug.select('#cultureCenter' + culture).attr('fill', fill);
|
||||
};
|
||||
|
||||
openPicker(currentFill, callback);
|
||||
}
|
||||
|
||||
function cultureChangeName() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
this.parentNode.dataset.name = this.value;
|
||||
pack.cultures[culture].name = this.value;
|
||||
pack.cultures[culture].code = abbreviate(
|
||||
this.value,
|
||||
pack.cultures.map((c) => c.code)
|
||||
);
|
||||
}
|
||||
|
||||
function cultureRegenerateName() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const name = Names.getCultureShort(culture);
|
||||
this.parentNode.querySelector("input.cultureName").value = name;
|
||||
pack.cultures[culture].name = name;
|
||||
}
|
||||
|
||||
function cultureChangeExpansionism() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
this.parentNode.dataset.expansionism = this.value;
|
||||
pack.cultures[culture].expansionism = +this.value;
|
||||
recalculateCultures();
|
||||
}
|
||||
|
||||
function cultureChangeType() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
this.parentNode.dataset.type = this.value;
|
||||
pack.cultures[culture].type = this.value;
|
||||
recalculateCultures();
|
||||
}
|
||||
|
||||
function cultureChangeBase() {
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const v = +this.value;
|
||||
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];
|
||||
if (!c.cells) {
|
||||
tip('Culture does not have any cells, cannot change population', false, 'error');
|
||||
return;
|
||||
}
|
||||
const rural = rn(c.rural * populationRate);
|
||||
const urban = rn(c.urban * populationRate * urbanization);
|
||||
const total = rural + urban;
|
||||
const l = (n) => Number(n).toLocaleString();
|
||||
const burgs = pack.burgs.filter((b) => !b.removed && b.culture === culture);
|
||||
|
||||
alertMessage.innerHTML = `
|
||||
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
|
||||
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? '' : 'disabled'}>
|
||||
<p>Total population: ${l(total)} ⇒ <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
|
||||
|
||||
const update = function () {
|
||||
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
|
||||
if (isNaN(totalNew)) return;
|
||||
totalPop.innerHTML = l(totalNew);
|
||||
totalPopPerc.innerHTML = rn((totalNew / total) * 100);
|
||||
};
|
||||
|
||||
ruralPop.oninput = () => update();
|
||||
urbanPop.oninput = () => update();
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Change culture population',
|
||||
width: '24em',
|
||||
buttons: {
|
||||
Apply: function () {
|
||||
applyPopulationChange();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
position: {my: 'center', at: 'center', of: 'svg'}
|
||||
});
|
||||
|
||||
function applyPopulationChange() {
|
||||
const ruralChange = ruralPop.value / rural;
|
||||
if (isFinite(ruralChange) && ruralChange !== 1) {
|
||||
const cells = pack.cells.i.filter((i) => pack.cells.culture[i] === culture);
|
||||
cells.forEach((i) => (pack.cells.pop[i] *= ruralChange));
|
||||
}
|
||||
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
|
||||
const points = ruralPop.value / populationRate;
|
||||
const cells = pack.cells.i.filter((i) => pack.cells.culture[i] === culture);
|
||||
const pop = rn(points / cells.length);
|
||||
cells.forEach((i) => (pack.cells.pop[i] = pop));
|
||||
}
|
||||
|
||||
const urbanChange = urbanPop.value / urban;
|
||||
if (isFinite(urbanChange) && urbanChange !== 1) {
|
||||
burgs.forEach((b) => (b.population = rn(b.population * urbanChange, 4)));
|
||||
}
|
||||
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
|
||||
const points = urbanPop.value / populationRate / urbanization;
|
||||
const population = rn(points / burgs.length, 4);
|
||||
burgs.forEach((b) => (b.population = population));
|
||||
}
|
||||
|
||||
refreshCulturesEditor();
|
||||
}
|
||||
}
|
||||
|
||||
function cultureRegenerateBurgs() {
|
||||
if (customization === 4) return;
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
const cBurgs = pack.burgs.filter((b) => b.culture === culture && !b.lock);
|
||||
cBurgs.forEach((b) => {
|
||||
b.name = Names.getCulture(culture);
|
||||
labels.select("[data-id='" + b.i + "']").text(b.name);
|
||||
});
|
||||
tip(`Names for ${cBurgs.length} burgs are regenerated`, false, 'success');
|
||||
}
|
||||
|
||||
function cultureRemove() {
|
||||
if (customization === 4) return;
|
||||
const culture = +this.parentNode.dataset.id;
|
||||
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the culture? <br>This action cannot be reverted';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove culture',
|
||||
buttons: {
|
||||
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();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function drawCultureCenters() {
|
||||
const tooltip = 'Drag to move the culture center (ancestral home)';
|
||||
debug.select('#cultureCenters').remove();
|
||||
const cultureCenters = debug.append('g').attr('id', 'cultureCenters').attr('stroke-width', 2).attr('stroke', '#444444').style('cursor', 'move');
|
||||
|
||||
const data = pack.cultures.filter((c) => c.i && !c.removed);
|
||||
cultureCenters
|
||||
.selectAll('circle')
|
||||
.data(data)
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('id', (d) => 'cultureCenter' + d.i)
|
||||
.attr('data-id', (d) => d.i)
|
||||
.attr('r', 6)
|
||||
.attr('fill', (d) => d.color)
|
||||
.attr('cx', (d) => pack.cells.p[d.center][0])
|
||||
.attr('cy', (d) => pack.cells.p[d.center][1])
|
||||
.on('mouseenter', (d) => {
|
||||
tip(tooltip, true);
|
||||
body.querySelector(`div[data-id='${d.i}']`).classList.add('selected');
|
||||
cultureHighlightOn(event);
|
||||
})
|
||||
.on('mouseleave', (d) => {
|
||||
tip('', true);
|
||||
body.querySelector(`div[data-id='${d.i}']`).classList.remove('selected');
|
||||
cultureHighlightOff(event);
|
||||
})
|
||||
.call(d3.drag().on('start', cultureCenterDrag));
|
||||
}
|
||||
|
||||
function cultureCenterDrag() {
|
||||
const el = d3.select(this);
|
||||
const c = +this.id.slice(13);
|
||||
d3.event.on('drag', () => {
|
||||
el.attr('cx', d3.event.x).attr('cy', d3.event.y);
|
||||
const cell = findCell(d3.event.x, d3.event.y);
|
||||
if (pack.cells.h[cell] < 20) return; // ignore dragging on water
|
||||
pack.cultures[c].center = cell;
|
||||
recalculateCultures();
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
if (legend.selectAll('*').size()) {
|
||||
clearLegend();
|
||||
return;
|
||||
} // hide legend
|
||||
const data = pack.cultures
|
||||
.filter((c) => c.i && !c.removed && c.cells)
|
||||
.sort((a, b) => b.area - a.area)
|
||||
.map((c) => [c.i, c.color, c.name]);
|
||||
drawLegend('Cultures', data);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const totalCells = +culturesFooterCells.innerHTML;
|
||||
const totalArea = +culturesFooterArea.dataset.area;
|
||||
const totalPopulation = +culturesFooterPopulation.dataset.population;
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
el.querySelector('.stateCells').innerHTML = rn((+el.dataset.cells / totalCells) * 100) + '%';
|
||||
el.querySelector('.biomeArea').innerHTML = rn((+el.dataset.area / totalArea) * 100) + '%';
|
||||
el.querySelector('.culturePopulation').innerHTML = rn((+el.dataset.population / totalPopulation) * 100) + '%';
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = 'absolute';
|
||||
culturesEditorAddLines();
|
||||
}
|
||||
}
|
||||
|
||||
function showHierarchy() {
|
||||
// build hierarchy tree
|
||||
pack.cultures[0].origin = null;
|
||||
const cultures = pack.cultures.filter((c) => !c.removed);
|
||||
if (cultures.length < 3) {
|
||||
tip('Not enough cultures to show hierarchy', false, 'error');
|
||||
return;
|
||||
}
|
||||
const root = d3
|
||||
.stratify()
|
||||
.id((d) => d.i)
|
||||
.parentId((d) => d.origin)(cultures);
|
||||
const treeWidth = root.leaves().length;
|
||||
const treeHeight = root.height;
|
||||
const width = treeWidth * 40,
|
||||
height = treeHeight * 60;
|
||||
|
||||
const margin = {top: 10, right: 10, bottom: -5, left: 10};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height + 30 - margin.top - margin.bottom;
|
||||
const treeLayout = d3.tree().size([w, h]);
|
||||
|
||||
// prepare svg
|
||||
alertMessage.innerHTML = "<div id='cultureInfo' class='chartInfo'>‍</div>";
|
||||
<<<<<<< HEAD
|
||||
const svg = d3.select('#alertMessage').insert('svg', '#cultureInfo').attr('id', 'hierarchy').attr('width', width).attr('height', height).style('text-anchor', 'middle');
|
||||
const graph = svg.append('g').attr('transform', `translate(10, -45)`);
|
||||
const links = graph.append('g').attr('fill', 'none').attr('stroke', '#aaaaaa');
|
||||
const nodes = graph.append('g');
|
||||
=======
|
||||
const svg = d3
|
||||
.select("#alertMessage")
|
||||
.insert("svg", "#cultureInfo")
|
||||
.attr("id", "hierarchy")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.style("text-anchor", "middle");
|
||||
const graph = svg.append("g").attr("transform", `translate(10, -45)`);
|
||||
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
|
||||
const nodes = graph.append("g");
|
||||
>>>>>>> master
|
||||
|
||||
renderTree();
|
||||
function renderTree() {
|
||||
treeLayout(root);
|
||||
links
|
||||
.selectAll('path')
|
||||
.data(root.links())
|
||||
.enter()
|
||||
<<<<<<< HEAD
|
||||
.append('path')
|
||||
.attr('d', (d) => {
|
||||
return (
|
||||
'M' +
|
||||
d.source.x +
|
||||
',' +
|
||||
d.source.y +
|
||||
'C' +
|
||||
d.source.x +
|
||||
',' +
|
||||
(d.source.y * 3 + d.target.y) / 4 +
|
||||
' ' +
|
||||
d.target.x +
|
||||
',' +
|
||||
(d.source.y * 2 + d.target.y) / 3 +
|
||||
' ' +
|
||||
d.target.x +
|
||||
',' +
|
||||
=======
|
||||
.append("path")
|
||||
.attr("d", d => {
|
||||
return (
|
||||
"M" +
|
||||
d.source.x +
|
||||
"," +
|
||||
d.source.y +
|
||||
"C" +
|
||||
d.source.x +
|
||||
"," +
|
||||
(d.source.y * 3 + d.target.y) / 4 +
|
||||
" " +
|
||||
d.target.x +
|
||||
"," +
|
||||
(d.source.y * 2 + d.target.y) / 3 +
|
||||
" " +
|
||||
d.target.x +
|
||||
"," +
|
||||
>>>>>>> master
|
||||
d.target.y
|
||||
);
|
||||
});
|
||||
|
||||
const node = nodes
|
||||
.selectAll('g')
|
||||
.data(root.descendants())
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('data-id', (d) => d.data.i)
|
||||
.attr('stroke', '#333333')
|
||||
.attr('transform', (d) => `translate(${d.x}, ${d.y})`)
|
||||
.on('mouseenter', () => cultureHighlightOn(event))
|
||||
.on('mouseleave', () => cultureHighlightOff(event))
|
||||
.call(d3.drag().on('start', (d) => dragToReorigin(d)));
|
||||
|
||||
node
|
||||
.append('path')
|
||||
.attr('d', (d) => {
|
||||
if (!d.data.i) return 'M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0';
|
||||
// small circle
|
||||
else if (d.data.type === 'Generic') return 'M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0';
|
||||
// circle
|
||||
else if (d.data.type === 'River') return 'M0,-14L14,0L0,14L-14,0Z';
|
||||
// diamond
|
||||
else if (d.data.type === 'Lake') return 'M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z';
|
||||
// hexagon
|
||||
else if (d.data.type === 'Naval') return 'M-11,-11h22v22h-22Z'; // square
|
||||
if (d.data.type === 'Highland') return 'M-11,-11l11,2l11,-2l-2,11l2,11l-11,-2l-11,2l2,-11Z'; // concave square
|
||||
if (d.data.type === 'Nomadic') return 'M-4.97,-12.01 l9.95,0 l7.04,7.04 l0,9.95 l-7.04,7.04 l-9.95,0 l-7.04,-7.04 l0,-9.95Z'; // octagon
|
||||
if (d.data.type === 'Hunting') return 'M0,-14l14,11l-6,14h-16l-6,-14Z'; // pentagon
|
||||
return 'M-11,-11h22v22h-22Z'; // square
|
||||
})
|
||||
.attr('fill', (d) => (d.data.i ? d.data.color : '#ffffff'))
|
||||
.attr('stroke-dasharray', (d) => (d.data.cells ? 'null' : '1'));
|
||||
|
||||
node
|
||||
.append('text')
|
||||
.attr('dy', '.35em')
|
||||
.text((d) => (d.data.i ? d.data.code : ''));
|
||||
}
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'Cultures tree',
|
||||
width: fitContent(),
|
||||
resizable: false,
|
||||
position: {my: 'left center', at: 'left+10 center', of: 'svg'},
|
||||
buttons: {},
|
||||
close: () => {
|
||||
alertMessage.innerHTML = '';
|
||||
}
|
||||
});
|
||||
|
||||
function dragToReorigin(d) {
|
||||
if (isCtrlClick(d3.event.sourceEvent)) {
|
||||
changeCode(d);
|
||||
return;
|
||||
}
|
||||
|
||||
const originLine = graph.append('path').attr('class', 'dragLine').attr('d', `M${d.x},${d.y}L${d.x},${d.y}`);
|
||||
|
||||
d3.event.on('drag', () => {
|
||||
originLine.attr('d', `M${d.x},${d.y}L${d3.event.x},${d3.event.y}`);
|
||||
});
|
||||
|
||||
d3.event.on('end', () => {
|
||||
originLine.remove();
|
||||
const selected = graph.select('path.selected');
|
||||
if (!selected.size()) return;
|
||||
const culture = d.data.i;
|
||||
const oldOrigin = d.data.origin;
|
||||
let newOrigin = selected.datum().data.i;
|
||||
if (newOrigin == oldOrigin) return; // already a child of the selected node
|
||||
if (newOrigin == culture) newOrigin = 0; // move to top
|
||||
if (newOrigin && d.descendants().some((node) => node.id == newOrigin)) return; // cannot be a child of its own child
|
||||
pack.cultures[culture].origin = d.data.origin = newOrigin; // change data
|
||||
showHierarchy(); // update hierarchy
|
||||
});
|
||||
}
|
||||
|
||||
function changeCode(d) {
|
||||
prompt(`Please provide an abbreviation for culture: ${d.data.name}`, {default: d.data.code}, (v) => {
|
||||
pack.cultures[d.data.i].code = v;
|
||||
nodes
|
||||
.select("g[data-id='" + d.data.i + "']")
|
||||
.select('text')
|
||||
.text(v);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function recalculateCultures(must) {
|
||||
if (!must && !culturesAutoChange.checked) return;
|
||||
|
||||
pack.cells.culture = new Uint16Array(pack.cells.i.length);
|
||||
pack.cultures.forEach(function (c) {
|
||||
if (!c.i || c.removed) return;
|
||||
pack.cells.culture[c.center] = c.i;
|
||||
});
|
||||
Cultures.expand();
|
||||
drawCultures();
|
||||
pack.burgs.forEach((b) => (b.culture = pack.cells.culture[b.cell]));
|
||||
refreshCulturesEditor();
|
||||
document.querySelector('input.statePower').focus(); // to not trigger hotkeys
|
||||
}
|
||||
|
||||
function enterCultureManualAssignent() {
|
||||
if (!layerIsOn('toggleCultures')) toggleCultures();
|
||||
customization = 4;
|
||||
cults.append('g').attr('id', 'temp');
|
||||
document.querySelectorAll('#culturesBottom > *').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('culturesManuallyButtons').style.display = 'inline-block';
|
||||
debug.select('#cultureCenters').style('display', 'none');
|
||||
|
||||
culturesEditor.querySelectorAll('.hide').forEach((el) => el.classList.add('hidden'));
|
||||
culturesHeader.querySelector("div[data-sortby='type']").style.left = '8.8em';
|
||||
culturesHeader.querySelector("div[data-sortby='base']").style.left = '13.6em';
|
||||
culturesFooter.style.display = 'none';
|
||||
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'}});
|
||||
|
||||
<<<<<<< HEAD
|
||||
tip('Click on culture to select, drag the circle to change culture', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectCultureOnMapClick).call(d3.drag().on('start', dragCultureBrush)).on('touchmove mousemove', moveCultureBrush);
|
||||
=======
|
||||
tip("Click on culture to select, drag the circle to change culture", true);
|
||||
viewbox
|
||||
.style("cursor", "crosshair")
|
||||
.on("click", selectCultureOnMapClick)
|
||||
.call(d3.drag().on("start", dragCultureBrush))
|
||||
.on("touchmove mousemove", moveCultureBrush);
|
||||
>>>>>>> master
|
||||
|
||||
body.querySelector('div').classList.add('selected');
|
||||
}
|
||||
|
||||
function selectCultureOnLineClick(i) {
|
||||
if (customization !== 4) return;
|
||||
body.querySelector('div.selected').classList.remove('selected');
|
||||
this.classList.add('selected');
|
||||
}
|
||||
|
||||
function selectCultureOnMapClick() {
|
||||
const point = d3.mouse(this);
|
||||
const i = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[i] < 20) return;
|
||||
|
||||
const assigned = cults.select('#temp').select("polygon[data-cell='" + i + "']");
|
||||
const culture = assigned.size() ? +assigned.attr('data-culture') : pack.cells.culture[i];
|
||||
|
||||
body.querySelector('div.selected').classList.remove('selected');
|
||||
body.querySelector("div[data-id='" + culture + "']").classList.add('selected');
|
||||
}
|
||||
|
||||
function dragCultureBrush() {
|
||||
const r = +culturesManuallyBrush.value;
|
||||
|
||||
d3.event.on('drag', () => {
|
||||
if (!d3.event.dx && !d3.event.dy) return;
|
||||
const p = d3.mouse(this);
|
||||
moveCircle(p[0], p[1], r);
|
||||
|
||||
const found = r > 5 ? findAll(p[0], p[1], r) : [findCell(p[0], p[1], r)];
|
||||
const selection = found.filter(isLand);
|
||||
if (selection) changeCultureForSelection(selection);
|
||||
});
|
||||
}
|
||||
|
||||
function changeCultureForSelection(selection) {
|
||||
const temp = cults.select('#temp');
|
||||
const selected = body.querySelector('div.selected');
|
||||
|
||||
const cultureNew = +selected.dataset.id;
|
||||
const color = pack.cultures[cultureNew].color || '#ffffff';
|
||||
|
||||
selection.forEach(function (i) {
|
||||
const exists = temp.select("polygon[data-cell='" + i + "']");
|
||||
const cultureOld = exists.size() ? +exists.attr('data-culture') : pack.cells.culture[i];
|
||||
if (cultureNew === cultureOld) return;
|
||||
|
||||
// change of append new element
|
||||
<<<<<<< HEAD
|
||||
if (exists.size()) exists.attr('data-culture', cultureNew).attr('fill', color).attr('stroke', color);
|
||||
else temp.append('polygon').attr('data-cell', i).attr('data-culture', cultureNew).attr('points', getPackPolygon(i)).attr('fill', color).attr('stroke', color);
|
||||
=======
|
||||
if (exists.size()) exists.attr("data-culture", cultureNew).attr("fill", color).attr("stroke", color);
|
||||
else
|
||||
temp
|
||||
.append("polygon")
|
||||
.attr("data-cell", i)
|
||||
.attr("data-culture", cultureNew)
|
||||
.attr("points", getPackPolygon(i))
|
||||
.attr("fill", color)
|
||||
.attr("stroke", color);
|
||||
>>>>>>> master
|
||||
});
|
||||
}
|
||||
|
||||
function moveCultureBrush() {
|
||||
showMainTip();
|
||||
const point = d3.mouse(this);
|
||||
const radius = +culturesManuallyBrush.value;
|
||||
moveCircle(point[0], point[1], radius);
|
||||
}
|
||||
|
||||
function applyCultureManualAssignent() {
|
||||
const changed = cults.select('#temp').selectAll('polygon');
|
||||
changed.each(function () {
|
||||
const i = +this.dataset.cell;
|
||||
const c = +this.dataset.culture;
|
||||
pack.cells.culture[i] = c;
|
||||
if (pack.cells.burg[i]) pack.burgs[pack.cells.burg[i]].culture = c;
|
||||
});
|
||||
|
||||
if (changed.size()) {
|
||||
drawCultures();
|
||||
refreshCulturesEditor();
|
||||
}
|
||||
exitCulturesManualAssignment();
|
||||
}
|
||||
|
||||
function exitCulturesManualAssignment(close) {
|
||||
customization = 0;
|
||||
cults.select('#temp').remove();
|
||||
removeCircle();
|
||||
document.querySelectorAll('#culturesBottom > *').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('culturesManuallyButtons').style.display = 'none';
|
||||
|
||||
culturesEditor.querySelectorAll('.hide').forEach((el) => el.classList.remove('hidden'));
|
||||
culturesHeader.querySelector("div[data-sortby='type']").style.left = '18.6em';
|
||||
culturesHeader.querySelector("div[data-sortby='base']").style.left = '35.8em';
|
||||
culturesFooter.style.display = 'block';
|
||||
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'}});
|
||||
|
||||
debug.select('#cultureCenters').style('display', null);
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
const selected = body.querySelector('div.selected');
|
||||
if (selected) selected.classList.remove('selected');
|
||||
}
|
||||
|
||||
function enterAddCulturesMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
exitAddCultureMode();
|
||||
return;
|
||||
}
|
||||
customization = 9;
|
||||
this.classList.add('pressed');
|
||||
tip('Click on the map to add a new culture', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', addCulture);
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'none'));
|
||||
}
|
||||
|
||||
function exitAddCultureMode() {
|
||||
customization = 0;
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
body.querySelectorAll('div > input, select, span, svg').forEach((e) => (e.style.pointerEvents = 'all'));
|
||||
if (culturesAdd.classList.contains('pressed')) culturesAdd.classList.remove('pressed');
|
||||
}
|
||||
|
||||
function addCulture() {
|
||||
const point = d3.mouse(this);
|
||||
const center = findCell(point[0], point[1]);
|
||||
if (pack.cells.h[center] < 20) {
|
||||
tip('You cannot place culture center into the water. Please click on a land cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
const occupied = pack.cultures.some((c) => !c.removed && c.center === center);
|
||||
if (occupied) {
|
||||
tip('This cell is already a culture center. Please select a different cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (d3.event.shiftKey === false) exitAddCultureMode();
|
||||
Cultures.add(center);
|
||||
|
||||
drawCultureCenters();
|
||||
culturesEditorAddLines();
|
||||
}
|
||||
|
||||
function downloadCulturesData() {
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
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 + ',';
|
||||
data += el.dataset.name + ',';
|
||||
data += el.dataset.color + ',';
|
||||
data += el.dataset.cells + ',';
|
||||
data += el.dataset.expansionism + ',';
|
||||
data += el.dataset.type + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + ',';
|
||||
const base = +el.dataset.base;
|
||||
data += nameBases[base].name + ',';
|
||||
data += el.dataset.emblems + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName('Cultures') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function closeCulturesEditor() {
|
||||
debug.select('#cultureCenters').remove();
|
||||
exitCulturesManualAssignment('close');
|
||||
exitAddCultureMode();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +1,57 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
function editDiplomacy() {
|
||||
if (customization) return;
|
||||
if (pack.states.filter(s => s.i && !s.removed).length < 2) {
|
||||
tip("There should be at least 2 states to edit the diplomacy", false, "error");
|
||||
if (pack.states.filter((s) => s.i && !s.removed).length < 2) {
|
||||
tip('There should be at least 2 states to edit the diplomacy', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
closeDialogs("#diplomacyEditor, .stable");
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
if (!layerIsOn("toggleBorders")) toggleBorders();
|
||||
if (layerIsOn("toggleProvinces")) toggleProvinces();
|
||||
if (layerIsOn("toggleCultures")) toggleCultures();
|
||||
if (layerIsOn("toggleBiomes")) toggleBiomes();
|
||||
if (layerIsOn("toggleReligions")) toggleReligions();
|
||||
closeDialogs('#diplomacyEditor, .stable');
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
if (layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
if (layerIsOn('toggleCultures')) toggleCultures();
|
||||
if (layerIsOn('toggleBiomes')) toggleBiomes();
|
||||
if (layerIsOn('toggleReligions')) toggleReligions();
|
||||
|
||||
const body = document.getElementById("diplomacyBodySection");
|
||||
const statuses = ["Ally", "Friendly", "Neutral", "Suspicion", "Enemy", "Unknown", "Rival", "Vassal", "Suzerain"];
|
||||
const description = [" is an ally of ", " is friendly to ", " is neutral to ", " is suspicious of ",
|
||||
" is at war with ", " does not know about ", " is a rival of ", " is a vassal of ", " is suzerain to "];
|
||||
const colors = ["#00b300", "#d4f8aa", "#edeee8", "#eeafaa", "#e64b40", "#a9a9a9", "#ad5a1f", "#87CEFA", "#00008B"];
|
||||
const body = document.getElementById('diplomacyBodySection');
|
||||
const statuses = ['Ally', 'Friendly', 'Neutral', 'Suspicion', 'Enemy', 'Unknown', 'Rival', 'Vassal', 'Suzerain'];
|
||||
const description = [
|
||||
' is an ally of ',
|
||||
' is friendly to ',
|
||||
' is neutral to ',
|
||||
' is suspicious of ',
|
||||
' is at war with ',
|
||||
' does not know about ',
|
||||
' is a rival of ',
|
||||
' is a vassal of ',
|
||||
' is suzerain to '
|
||||
];
|
||||
const colors = ['#00b300', '#d4f8aa', '#edeee8', '#eeafaa', '#e64b40', '#a9a9a9', '#ad5a1f', '#87CEFA', '#00008B'];
|
||||
refreshDiplomacyEditor();
|
||||
|
||||
tip("Click on a state to see its diplomatic relations", false, "warning");
|
||||
viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick);
|
||||
tip('Click on a state to see its diplomatic relations', false, 'warning');
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectStateOnMapClick);
|
||||
|
||||
if (modules.editDiplomacy) return;
|
||||
modules.editDiplomacy = true;
|
||||
|
||||
$("#diplomacyEditor").dialog({
|
||||
title: "Diplomacy Editor", resizable: false, width: fitContent(), close: closeDiplomacyEditor,
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
$('#diplomacyEditor').dialog({
|
||||
title: 'Diplomacy Editor',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: closeDiplomacyEditor,
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor);
|
||||
document.getElementById("diplomacyEditStyle").addEventListener("click", () => editStyle("regions"));
|
||||
document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations);
|
||||
document.getElementById("diplomacyMatrix").addEventListener("click", showRelationsMatrix);
|
||||
document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory);
|
||||
document.getElementById("diplomacyExport").addEventListener("click", downloadDiplomacyData);
|
||||
document.getElementById("diplomacySelect").addEventListener("mouseup", diplomacyChangeRelations);
|
||||
document.getElementById('diplomacyEditorRefresh').addEventListener('click', refreshDiplomacyEditor);
|
||||
document.getElementById('diplomacyEditStyle').addEventListener('click', () => editStyle('regions'));
|
||||
document.getElementById('diplomacyRegenerate').addEventListener('click', regenerateRelations);
|
||||
document.getElementById('diplomacyMatrix').addEventListener('click', showRelationsMatrix);
|
||||
document.getElementById('diplomacyHistory').addEventListener('click', showRelationsHistory);
|
||||
document.getElementById('diplomacyExport').addEventListener('click', downloadDiplomacyData);
|
||||
document.getElementById('diplomacySelect').addEventListener('mouseup', diplomacyChangeRelations);
|
||||
|
||||
function refreshDiplomacyEditor() {
|
||||
diplomacyEditorAddLines();
|
||||
|
|
@ -49,12 +61,12 @@ function editDiplomacy() {
|
|||
// add line for each state
|
||||
function diplomacyEditorAddLines() {
|
||||
const states = pack.states;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
|
||||
const selectedLine = body.querySelector('div.Self');
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : states.find((s) => s.i && !s.removed).i;
|
||||
const selName = states[sel].fullName;
|
||||
diplomacySelect.style.display = "none";
|
||||
diplomacySelect.style.display = 'none';
|
||||
|
||||
COArenderer.trigger("stateCOA"+sel, states[sel].coa);
|
||||
COArenderer.trigger('stateCOA' + sel, states[sel].coa);
|
||||
let lines = `<div class="states Self" data-id=${sel} data-tip="List below shows relations to ${selName}">
|
||||
<div style="width: max-content">${selName}</div>
|
||||
<svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${sel}"></use></svg>
|
||||
|
|
@ -68,7 +80,7 @@ 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);
|
||||
COArenderer.trigger('stateCOA' + s.i, s.coa);
|
||||
|
||||
lines += `<div class="states" data-id=${s.i} data-name="${s.fullName}" data-relations="${relation}">
|
||||
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
|
||||
|
|
@ -82,57 +94,61 @@ function editDiplomacy() {
|
|||
body.innerHTML = lines;
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectStateOnLineClick));
|
||||
body.querySelectorAll(".changeRelations").forEach(el => el.addEventListener("click", toggleDiplomacySelect));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => stateHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => stateHighlightOff(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('click', selectStateOnLineClick));
|
||||
body.querySelectorAll('.changeRelations').forEach((el) => el.addEventListener('click', toggleDiplomacySelect));
|
||||
|
||||
applySorting(diplomacyHeader);
|
||||
$("#diplomacyEditor").dialog();
|
||||
$('#diplomacyEditor').dialog();
|
||||
}
|
||||
|
||||
function stateHighlightOn(event) {
|
||||
if (!layerIsOn("toggleStates")) return;
|
||||
if (!layerIsOn('toggleStates')) return;
|
||||
const state = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
const d = regions.select("#state"+state).attr("d");
|
||||
const d = regions.select('#state' + state).attr('d');
|
||||
|
||||
const path = debug.append("path").attr("class", "highlight").attr("d", d)
|
||||
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)");
|
||||
const path = debug.append('path').attr('class', 'highlight').attr('d', d).attr('fill', 'none').attr('stroke', 'red').attr('stroke-width', 1).attr('opacity', 1).attr('filter', 'url(#blur1)');
|
||||
|
||||
const l = path.node().getTotalLength(), dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString("0," + l, l + "," + l);
|
||||
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)});
|
||||
const l = path.node().getTotalLength(),
|
||||
dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString('0,' + l, l + ',' + l);
|
||||
path
|
||||
.transition()
|
||||
.duration(dur)
|
||||
.attrTween('stroke-dasharray', function () {
|
||||
return (t) => i(t);
|
||||
});
|
||||
}
|
||||
|
||||
function stateHighlightOff(event) {
|
||||
debug.selectAll(".highlight").each(function() {
|
||||
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
|
||||
debug.selectAll('.highlight').each(function () {
|
||||
d3.select(this).transition().duration(1000).attr('opacity', 0).remove();
|
||||
});
|
||||
}
|
||||
|
||||
function showStateRelations() {
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : pack.states.find(s => s.i && !s.removed).i;
|
||||
const selectedLine = body.querySelector('div.Self');
|
||||
const sel = selectedLine ? +selectedLine.dataset.id : pack.states.find((s) => s.i && !s.removed).i;
|
||||
if (!sel) return;
|
||||
if (!layerIsOn("toggleStates")) toggleStates();
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
|
||||
statesBody.selectAll("path").each(function() {
|
||||
if (this.id.slice(0, 9) === "state-gap") return; // exclude state gap element
|
||||
statesBody.selectAll('path').each(function () {
|
||||
if (this.id.slice(0, 9) === 'state-gap') return; // exclude state gap element
|
||||
const id = +this.id.slice(5); // state id
|
||||
const index = statuses.indexOf(pack.states[id].diplomacy[sel]); // status index
|
||||
const clr = index !== -1 ? colors[index] : "#4682b4"; // Self (bluish)
|
||||
this.setAttribute("fill", clr);
|
||||
statesBody.select("#state-gap"+id).attr("stroke", clr);
|
||||
statesHalo.select("#state-border"+id).attr("stroke", d3.color(clr).darker().hex());
|
||||
const clr = index !== -1 ? colors[index] : '#4682b4'; // Self (bluish)
|
||||
this.setAttribute('fill', clr);
|
||||
statesBody.select('#state-gap' + id).attr('stroke', clr);
|
||||
statesHalo.select('#state-border' + id).attr('stroke', d3.color(clr).darker().hex());
|
||||
});
|
||||
}
|
||||
|
||||
function selectStateOnLineClick() {
|
||||
if (this.classList.contains("Self")) return;
|
||||
body.querySelector("div.Self").classList.remove("Self");
|
||||
this.classList.add("Self");
|
||||
if (this.classList.contains('Self')) return;
|
||||
body.querySelector('div.Self').classList.remove('Self');
|
||||
this.classList.add('Self');
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
||||
|
|
@ -141,35 +157,39 @@ function editDiplomacy() {
|
|||
const i = findCell(point[0], point[1]);
|
||||
const state = pack.cells.state[i];
|
||||
if (!state) return;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const selectedLine = body.querySelector('div.Self');
|
||||
if (+selectedLine.dataset.id === state) return;
|
||||
|
||||
selectedLine.classList.remove("Self");
|
||||
body.querySelector("div[data-id='"+state+"']").classList.add("Self");
|
||||
selectedLine.classList.remove('Self');
|
||||
body.querySelector("div[data-id='" + state + "']").classList.add('Self');
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
||||
function toggleDiplomacySelect(event) {
|
||||
event.stopPropagation();
|
||||
const select = document.getElementById("diplomacySelect");
|
||||
const show = select.style.display === "none";
|
||||
if (!show) {select.style.display = "none"; return;}
|
||||
select.style.display = "block";
|
||||
const input = event.target.closest("div").querySelector("input");
|
||||
select.style.left = input.getBoundingClientRect().left + "px";
|
||||
select.style.top = input.getBoundingClientRect().bottom + "px";
|
||||
body.dataset.state = event.target.closest("div.states").dataset.id;
|
||||
const select = document.getElementById('diplomacySelect');
|
||||
const show = select.style.display === 'none';
|
||||
if (!show) {
|
||||
select.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
select.style.display = 'block';
|
||||
const input = event.target.closest('div').querySelector('input');
|
||||
select.style.left = input.getBoundingClientRect().left + 'px';
|
||||
select.style.top = input.getBoundingClientRect().bottom + 'px';
|
||||
body.dataset.state = event.target.closest('div.states').dataset.id;
|
||||
}
|
||||
|
||||
function diplomacyChangeRelations(event) {
|
||||
event.stopPropagation();
|
||||
diplomacySelect.style.display = "none";
|
||||
diplomacySelect.style.display = 'none';
|
||||
const subject = +body.dataset.state;
|
||||
const rel = event.target.innerHTML;
|
||||
|
||||
const states = pack.states, chronicle = states[0].diplomacy;
|
||||
const selectedLine = body.querySelector("div.Self");
|
||||
const object = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
|
||||
const states = pack.states,
|
||||
chronicle = states[0].diplomacy;
|
||||
const selectedLine = body.querySelector('div.Self');
|
||||
const object = selectedLine ? +selectedLine.dataset.id : states.find((s) => s.i && !s.removed).i;
|
||||
if (!object) return;
|
||||
const objectName = states[object].name; // object of relations change
|
||||
const subjectName = states[subject].name; // subject of relations change - actor
|
||||
|
|
@ -177,7 +197,7 @@ function editDiplomacy() {
|
|||
const oldRel = states[subject].diplomacy[object];
|
||||
if (rel === oldRel) return;
|
||||
states[subject].diplomacy[object] = rel;
|
||||
states[object].diplomacy[subject] = rel === "Vassal" ? "Suzerain" : rel === "Suzerain" ? "Vassal" : rel;
|
||||
states[object].diplomacy[subject] = rel === 'Vassal' ? 'Suzerain' : rel === 'Suzerain' ? 'Vassal' : rel;
|
||||
|
||||
// update relation history
|
||||
const change = () => [`Relations change`, `${subjectName}-${getAdjective(objectName)} relations changed to ${rel.toLowerCase()}`];
|
||||
|
|
@ -189,22 +209,18 @@ function editDiplomacy() {
|
|||
const war = () => [`War declaration`, `${subjectName} declared a war on its enemy ${objectName}`];
|
||||
const peace = () => {
|
||||
const treaty = `${subjectName} and ${objectName} agreed to cease fire and signed a peace treaty`;
|
||||
const changed = rel === "Ally" ? ally()
|
||||
: rel === "Vassal" ? vassal()
|
||||
: rel === "Suzerain" ? suzerain()
|
||||
: rel === "Unknown" ? unknown()
|
||||
: change();
|
||||
const changed = rel === 'Ally' ? ally() : rel === 'Vassal' ? vassal() : rel === 'Suzerain' ? suzerain() : rel === 'Unknown' ? unknown() : change();
|
||||
return [`War termination`, treaty, changed[1]];
|
||||
}
|
||||
};
|
||||
|
||||
if (oldRel === "Enemy") chronicle.push(peace()); else
|
||||
if (rel === "Enemy") chronicle.push(war()); else
|
||||
if (rel === "Vassal") chronicle.push(vassal()); else
|
||||
if (rel === "Suzerain") chronicle.push(suzerain()); else
|
||||
if (rel === "Ally") chronicle.push(ally()); else
|
||||
if (rel === "Unknown") chronicle.push(unknown()); else
|
||||
if (rel === "Rival") chronicle.push(rival()); else
|
||||
chronicle.push(change());
|
||||
if (oldRel === 'Enemy') chronicle.push(peace());
|
||||
else if (rel === 'Enemy') chronicle.push(war());
|
||||
else if (rel === 'Vassal') chronicle.push(vassal());
|
||||
else if (rel === 'Suzerain') chronicle.push(suzerain());
|
||||
else if (rel === 'Ally') chronicle.push(ally());
|
||||
else if (rel === 'Unknown') chronicle.push(unknown());
|
||||
else if (rel === 'Rival') chronicle.push(rival());
|
||||
else chronicle.push(change());
|
||||
|
||||
refreshDiplomacyEditor();
|
||||
}
|
||||
|
|
@ -216,79 +232,95 @@ function editDiplomacy() {
|
|||
|
||||
function showRelationsHistory() {
|
||||
const chronicle = pack.states[0].diplomacy;
|
||||
if (!chronicle.length) {tip("Relations history is blank", false, "error"); return;}
|
||||
if (!chronicle.length) {
|
||||
tip('Relations history is blank', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
let message = `<div autocorrect="off" spellcheck="false">`;
|
||||
chronicle.forEach((e, d) => {
|
||||
message += `<div>`;
|
||||
e.forEach((l, i) => message += `<div contenteditable="true" data-id="${d}-${i}"${i ? "" : " style='font-weight:bold'"}>${l}</div>`);
|
||||
e.forEach((l, i) => (message += `<div contenteditable="true" data-id="${d}-${i}"${i ? '' : " style='font-weight:bold'"}>${l}</div>`));
|
||||
message += `‍</div>`;
|
||||
});
|
||||
alertMessage.innerHTML = message + `</div><i id="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</i>`;
|
||||
alertMessage.querySelectorAll("div[contenteditable='true']").forEach(el => el.addEventListener("input", changeReliationsHistory));
|
||||
alertMessage.querySelectorAll("div[contenteditable='true']").forEach((el) => el.addEventListener('input', changeReliationsHistory));
|
||||
|
||||
$("#alert").dialog({title: "Relations history", position: {my: "center", at: "center", of: "svg"},
|
||||
$('#alert').dialog({
|
||||
title: 'Relations history',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Save: function() {
|
||||
const data = this.querySelector("div").innerText.split("\n").join("\r\n");
|
||||
const name = getFileName("Relations history") + ".txt";
|
||||
Save: function () {
|
||||
const data = this.querySelector('div').innerText.split('\n').join('\r\n');
|
||||
const name = getFileName('Relations history') + '.txt';
|
||||
downloadFile(data, name);
|
||||
},
|
||||
Clear: function() {pack.states[0].diplomacy = []; $(this).dialog("close");},
|
||||
Close: function() {$(this).dialog("close");}
|
||||
Clear: function () {
|
||||
pack.states[0].diplomacy = [];
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Close: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function changeReliationsHistory() {
|
||||
const i = this.dataset.id.split("-");
|
||||
const i = this.dataset.id.split('-');
|
||||
const group = pack.states[0].diplomacy[i[0]];
|
||||
if (this.innerHTML === "") {
|
||||
if (this.innerHTML === '') {
|
||||
group.splice(i[1], 1);
|
||||
this.remove();
|
||||
} else group[i[1]] = this.innerHTML;
|
||||
}
|
||||
|
||||
function showRelationsMatrix() {
|
||||
const states = pack.states.filter(s => s.i && !s.removed);
|
||||
const valid = states.map(s => s.i);
|
||||
const states = pack.states.filter((s) => s.i && !s.removed);
|
||||
const valid = states.map((s) => s.i);
|
||||
|
||||
let message = `<table class="matrix-table"><tr><th data-tip='‍'></th>`;
|
||||
message += states.map(s => `<th data-tip='See relations to ${s.fullName}'>${s.name}</th>`).join("") + `</tr>`; // headers
|
||||
states.forEach(s => {
|
||||
message += `<tr><th data-tip='See relations of ${s.fullName}'>${s.name}</th>` + s.diplomacy
|
||||
.filter((v, i) => valid.includes(i)).map((r, i) => {
|
||||
const desc = description[statuses.indexOf(r)];
|
||||
const tip = desc ? s.fullName + desc + pack.states[valid[i]].fullName : '‍';
|
||||
return `<td data-tip='${tip}' class='${r}'>${r}</td>`
|
||||
}).join("") + "</tr>";
|
||||
message += states.map((s) => `<th data-tip='See relations to ${s.fullName}'>${s.name}</th>`).join('') + `</tr>`; // headers
|
||||
states.forEach((s) => {
|
||||
message +=
|
||||
`<tr><th data-tip='See relations of ${s.fullName}'>${s.name}</th>` +
|
||||
s.diplomacy
|
||||
.filter((v, i) => valid.includes(i))
|
||||
.map((r, i) => {
|
||||
const desc = description[statuses.indexOf(r)];
|
||||
const tip = desc ? s.fullName + desc + pack.states[valid[i]].fullName : '‍';
|
||||
return `<td data-tip='${tip}' class='${r}'>${r}</td>`;
|
||||
})
|
||||
.join('') +
|
||||
'</tr>';
|
||||
});
|
||||
message += `</table>`;
|
||||
alertMessage.innerHTML = message;
|
||||
|
||||
$("#alert").dialog({title: "Relations matrix", width: fitContent(), position: {my: "center", at: "center", of: "svg"}, buttons: {}});
|
||||
$('#alert').dialog({title: 'Relations matrix', width: fitContent(), position: {my: 'center', at: 'center', of: 'svg'}, buttons: {}});
|
||||
}
|
||||
|
||||
function downloadDiplomacyData() {
|
||||
const states = pack.states.filter(s => s.i && !s.removed);
|
||||
const valid = states.map(s => s.i);
|
||||
const states = pack.states.filter((s) => s.i && !s.removed);
|
||||
const valid = states.map((s) => s.i);
|
||||
|
||||
let data = "," + states.map(s => s.name).join(",") + "\n"; // headers
|
||||
states.forEach(s => {
|
||||
let data = ',' + states.map((s) => s.name).join(',') + '\n'; // headers
|
||||
states.forEach((s) => {
|
||||
const rels = s.diplomacy.filter((v, i) => valid.includes(i));
|
||||
data += s.name + "," + rels.join(",") + "\n";
|
||||
data += s.name + ',' + rels.join(',') + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName("Relations") + ".csv";
|
||||
const name = getFileName('Relations') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function closeDiplomacyEditor() {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
const selected = body.querySelector("div.Self");
|
||||
if (selected) selected.classList.remove("Self");
|
||||
if (layerIsOn("toggleStates")) drawStates(); else toggleStates();
|
||||
debug.selectAll(".highlight").remove();
|
||||
const selected = body.querySelector('div.Self');
|
||||
if (selected) selected.classList.remove('Self');
|
||||
if (layerIsOn('toggleStates')) drawStates();
|
||||
else toggleStates();
|
||||
debug.selectAll('.highlight').remove();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// module stub to store common functions for ui editors
|
||||
'use strict';
|
||||
|
||||
modules.editors = true;
|
||||
restoreDefaultEvents(); // apply default viewbox events on load
|
||||
|
||||
// restore default viewbox events
|
||||
|
|
@ -264,15 +265,8 @@ function toggleBurgLock(burg) {
|
|||
b.lock = b.lock ? 0 : 1;
|
||||
}
|
||||
|
||||
function showBurgLockTip(burg) {
|
||||
const b = pack.burgs[burg];
|
||||
if (b.lock) {
|
||||
tip('Click to unlock burg and allow it to be change by regeneration tools');
|
||||
} else {
|
||||
tip('Click to lock burg and prevent changes by regeneration tools');
|
||||
}
|
||||
}
|
||||
|
||||
// draw legend box
|
||||
function drawLegend(name, data) {
|
||||
legend.selectAll('*').remove(); // fully redraw every time
|
||||
|
|
@ -385,6 +379,14 @@ function createPicker() {
|
|||
|
||||
const contaiter = d3.select('body').append('svg').attr('id', 'pickerContainer').attr('width', '100%').attr('height', '100%');
|
||||
contaiter.append('rect').attr('x', 0).attr('y', 0).attr('width', '100%').attr('height', '100%').attr('opacity', 0.2).on('mousemove', cl).on('click', closePicker);
|
||||
.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", "100%")
|
||||
.attr("height", "100%")
|
||||
.attr("opacity", 0.2)
|
||||
.on("mousemove", cl)
|
||||
.on("click", closePicker);
|
||||
const picker = contaiter
|
||||
.append('g')
|
||||
.attr('id', 'picker')
|
||||
|
|
@ -489,6 +491,17 @@ function createPicker() {
|
|||
picker.insert('text', ':first-child').attr('x', 12).attr('y', -10).attr('id', 'pickerLabel').text('Color Picker').on('mousemove', pos);
|
||||
picker.insert('rect', ':first-child').attr('x', 0).attr('y', -30).attr('width', width).attr('height', 30).attr('id', 'pickerHeader').on('mousemove', pos);
|
||||
picker.attr('transform', `translate(${(svgWidth - width) / 2},${(svgHeight - height) / 2})`);
|
||||
.attr("fill", "#ffffff")
|
||||
.attr("stroke", "#5d4651")
|
||||
.on("mousemove", pos);
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", 288)
|
||||
.attr("y", -21)
|
||||
.attr("id", "pickerCloseRect")
|
||||
.attr("width", 14)
|
||||
.attr("height", 14)
|
||||
.on("mousemove", cl)
|
||||
.on("click", closePicker);
|
||||
}
|
||||
|
||||
function updateSelectedRect(fill) {
|
||||
|
|
@ -693,23 +706,32 @@ function uploadFile(el, callback) {
|
|||
fileReader.onload = (loaded) => callback(loaded.target.result);
|
||||
}
|
||||
|
||||
function highlightElement(element) {
|
||||
function getBBox(element) {
|
||||
if (debug.select('.highlighted').size()) return; // allow only 1 highlight element simultaniosly
|
||||
const box = element.getBBox();
|
||||
const y = +element.getAttribute("y");
|
||||
const transform = element.getAttribute('transform') || null;
|
||||
const height = +element.getAttribute("height");
|
||||
return {x, y, width, height};
|
||||
}
|
||||
|
||||
function highlightElement(element, zoom) {
|
||||
if (debug.select(".highlighted").size()) return; // allow only 1 highlight element simultaneously
|
||||
const box = element.tagName === "svg" ? getBBox(element) : element.getBBox();
|
||||
const enter = d3.transition().duration(1000).ease(d3.easeBounceOut);
|
||||
const exit = d3.transition().duration(500).ease(d3.easeLinear);
|
||||
|
||||
const highlight = debug.append('rect').attr('x', box.x).attr('y', box.y).attr('width', box.width).attr('height', box.height).attr('transform', transform);
|
||||
|
||||
highlight.classed("highlighted", 1).attr("transform", transform);
|
||||
highlight.classed('highlighted', 1).transition(enter).style('outline-offset', '0px').transition(exit).style('outline-color', 'transparent').delay(1000).remove();
|
||||
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
zoomTo(x, y, scale > 2 ? scale : 3, 1600);
|
||||
if (zoom) {
|
||||
const tr = parseTransform(transform);
|
||||
let x = box.x + box.width / 2;
|
||||
if (tr[0]) x += tr[0];
|
||||
let y = box.y + box.height / 2;
|
||||
if (tr[1]) y += tr[1];
|
||||
zoomTo(x, y, scale > 2 ? scale : zoom, 1600);
|
||||
}
|
||||
}
|
||||
|
||||
function selectIcon(initial, callback) {
|
||||
|
|
@ -945,6 +967,37 @@ function selectIcon(initial, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
function confirmationDialog(options) {
|
||||
const {
|
||||
title = "Confirm action",
|
||||
message = "Are you sure you want to continue? <br>The action cannot be reverted",
|
||||
cancel = "Cancel",
|
||||
confirm = "Continue",
|
||||
onCancel,
|
||||
onConfirm
|
||||
} = options;
|
||||
|
||||
const buttons = {
|
||||
[confirm]: function () {
|
||||
if (onConfirm) onConfirm();
|
||||
$(this).dialog("close");
|
||||
},
|
||||
[cancel]: function () {
|
||||
if (onCancel) onCancel();
|
||||
$(this).dialog("close");
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById("alertMessage").innerHTML = message;
|
||||
$("#alert").dialog({resizable: false, title, buttons});
|
||||
}
|
||||
|
||||
// add and register event listeners to clean up on editor closure
|
||||
function listen(element, event, handler) {
|
||||
element.addEventListener(event, handler);
|
||||
return () => element.removeEventListener(event, handler);
|
||||
}
|
||||
|
||||
// Calls the refresh for all currently open editors
|
||||
function refreshAllEditors() {
|
||||
TIME && console.time('refreshAllEditors');
|
||||
|
|
|
|||
1098
modules/ui/editors.js.orig
Normal file
1098
modules/ui/editors.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,12 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
function showEPForRoute(node) {
|
||||
const points = [];
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
const i = findCell(this.getAttribute("cx"), this.getAttribute("cy"));
|
||||
const i = findCell(this.getAttribute('cx'), this.getAttribute('cy'));
|
||||
points.push(i);
|
||||
});
|
||||
|
||||
|
|
@ -17,10 +17,10 @@ function showEPForRoute(node) {
|
|||
function showEPForRiver(node) {
|
||||
const points = [];
|
||||
debug
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
const i = findCell(this.getAttribute("cx"), this.getAttribute("cy"));
|
||||
const i = findCell(this.getAttribute('cx'), this.getAttribute('cy'));
|
||||
points.push(i);
|
||||
});
|
||||
|
||||
|
|
@ -30,16 +30,16 @@ function showEPForRiver(node) {
|
|||
|
||||
function showElevationProfile(data, routeLen, isRiver) {
|
||||
// data is an array of cell indexes, routeLen is the distance (in actual metres/feet), isRiver should be true for rivers, false otherwise
|
||||
document.getElementById("epScaleRange").addEventListener("change", draw);
|
||||
document.getElementById("epCurve").addEventListener("change", draw);
|
||||
document.getElementById("epSave").addEventListener("click", downloadCSV);
|
||||
document.getElementById('epScaleRange').addEventListener('change', draw);
|
||||
document.getElementById('epCurve').addEventListener('change', draw);
|
||||
document.getElementById('epSave').addEventListener('click', downloadCSV);
|
||||
|
||||
$("#elevationProfile").dialog({
|
||||
title: "Elevation profile",
|
||||
$('#elevationProfile').dialog({
|
||||
title: 'Elevation profile',
|
||||
resizable: false,
|
||||
width: window.width,
|
||||
close: closeElevationProfile,
|
||||
position: {my: "left top", at: "left+20 bottom-500", of: window, collision: "fit"}
|
||||
position: {my: 'left top', at: 'left+20 bottom-500', of: window, collision: 'fit'}
|
||||
});
|
||||
|
||||
// prevent river graphs from showing rivers as flowing uphill - remember the general slope
|
||||
|
|
@ -67,7 +67,7 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
let h = pack.cells.h[cell];
|
||||
if (h < 20) {
|
||||
const f = pack.features[pack.cells.f[cell]];
|
||||
if (f.type === "lake") h = f.height;
|
||||
if (f.type === 'lake') h = f.height;
|
||||
else h = 20;
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
chartData.burg[i] = b;
|
||||
chartData.cell[i] = cell;
|
||||
let sh = getHeight(h);
|
||||
chartData.height[i] = parseInt(sh.substr(0, sh.indexOf(" ")));
|
||||
chartData.height[i] = parseInt(sh.substr(0, sh.indexOf(' ')));
|
||||
chartData.mih = Math.min(chartData.mih, h);
|
||||
chartData.mah = Math.max(chartData.mah, h);
|
||||
chartData.mi = Math.min(chartData.mi, chartData.height[i]);
|
||||
|
|
@ -109,7 +109,7 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
draw();
|
||||
|
||||
function downloadCSV() {
|
||||
let data = "Point,X,Y,Cell,Height,Height value,Population,Burg,Burg population,Biome,Biome color,Culture,Culture color,Religion,Religion color,Province,Province color,State,State color\n"; // headers
|
||||
let data = 'Point,X,Y,Cell,Height,Height value,Population,Burg,Burg population,Biome,Biome color,Culture,Culture color,Religion,Religion color,Province,Province color,State,State color\n'; // headers
|
||||
|
||||
for (let k = 0; k < chartData.points.length; k++) {
|
||||
let cell = chartData.cell[k];
|
||||
|
|
@ -122,34 +122,34 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
let pop = pack.cells.pop[cell];
|
||||
let h = pack.cells.h[cell];
|
||||
|
||||
data += k + 1 + ",";
|
||||
data += chartData.points[k][0] + ",";
|
||||
data += chartData.points[k][1] + ",";
|
||||
data += cell + ",";
|
||||
data += getHeight(h) + ",";
|
||||
data += h + ",";
|
||||
data += rn(pop * populationRate) + ",";
|
||||
data += k + 1 + ',';
|
||||
data += chartData.points[k][0] + ',';
|
||||
data += chartData.points[k][1] + ',';
|
||||
data += cell + ',';
|
||||
data += getHeight(h) + ',';
|
||||
data += h + ',';
|
||||
data += rn(pop * populationRate) + ',';
|
||||
if (burg) {
|
||||
data += pack.burgs[burg].name + ",";
|
||||
data += pack.burgs[burg].population * populationRate * urbanization + ",";
|
||||
data += pack.burgs[burg].name + ',';
|
||||
data += pack.burgs[burg].population * populationRate * urbanization + ',';
|
||||
} else {
|
||||
data += ",0,";
|
||||
data += ',0,';
|
||||
}
|
||||
data += biomesData.name[biome] + ",";
|
||||
data += biomesData.color[biome] + ",";
|
||||
data += pack.cultures[culture].name + ",";
|
||||
data += pack.cultures[culture].color + ",";
|
||||
data += pack.religions[religion].name + ",";
|
||||
data += pack.religions[religion].color + ",";
|
||||
data += pack.provinces[province].name + ",";
|
||||
data += pack.provinces[province].color + ",";
|
||||
data += pack.states[state].name + ",";
|
||||
data += pack.states[state].color + ",";
|
||||
data += biomesData.name[biome] + ',';
|
||||
data += biomesData.color[biome] + ',';
|
||||
data += pack.cultures[culture].name + ',';
|
||||
data += pack.cultures[culture].color + ',';
|
||||
data += pack.religions[religion].name + ',';
|
||||
data += pack.religions[religion].color + ',';
|
||||
data += pack.provinces[province].name + ',';
|
||||
data += pack.provinces[province].color + ',';
|
||||
data += pack.states[state].name + ',';
|
||||
data += pack.states[state].color + ',';
|
||||
|
||||
data = data + "\n";
|
||||
data = data + '\n';
|
||||
}
|
||||
|
||||
const name = getFileName("elevation profile") + ".csv";
|
||||
const name = getFileName('elevation profile') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
|
|
@ -169,37 +169,48 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
chartData.points.push([xscale(i) + xOffset, yscale(chartData.height[i]) + yOffset]);
|
||||
}
|
||||
|
||||
document.getElementById("elevationGraph").innerHTML = "";
|
||||
document.getElementById('elevationGraph').innerHTML = '';
|
||||
|
||||
const chart = d3
|
||||
.select("#elevationGraph")
|
||||
.append("svg")
|
||||
.attr("width", chartWidth + 120)
|
||||
.attr("height", chartHeight + yOffset + biomesHeight)
|
||||
.attr("id", "elevationSVG")
|
||||
.attr("class", "epbackground");
|
||||
.select('#elevationGraph')
|
||||
.append('svg')
|
||||
.attr('width', chartWidth + 120)
|
||||
.attr('height', chartHeight + yOffset + biomesHeight)
|
||||
.attr('id', 'elevationSVG')
|
||||
.attr('class', 'epbackground');
|
||||
// arrow-head definition
|
||||
chart.append("defs").append("marker").attr("id", "arrowhead").attr("orient", "auto").attr("markerWidth", "2").attr("markerHeight", "4").attr("refX", "0.1").attr("refY", "2").append("path").attr("d", "M0,0 V4 L2,2 Z").attr("fill", "darkgray");
|
||||
chart
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', 'arrowhead')
|
||||
.attr('orient', 'auto')
|
||||
.attr('markerWidth', '2')
|
||||
.attr('markerHeight', '4')
|
||||
.attr('refX', '0.1')
|
||||
.attr('refY', '2')
|
||||
.append('path')
|
||||
.attr('d', 'M0,0 V4 L2,2 Z')
|
||||
.attr('fill', 'darkgray');
|
||||
|
||||
let colors = getColorScheme();
|
||||
const landdef = chart.select("defs").append("linearGradient").attr("id", "landdef").attr("x1", "0%").attr("y1", "0%").attr("x2", "0%").attr("y2", "100%");
|
||||
const landdef = chart.select('defs').append('linearGradient').attr('id', 'landdef').attr('x1', '0%').attr('y1', '0%').attr('x2', '0%').attr('y2', '100%');
|
||||
|
||||
if (chartData.mah == chartData.mih) {
|
||||
landdef
|
||||
.append("stop")
|
||||
.attr("offset", "0%")
|
||||
.attr("style", "stop-color:" + getColor(chartData.mih, colors) + ";stop-opacity:1");
|
||||
.append('stop')
|
||||
.attr('offset', '0%')
|
||||
.attr('style', 'stop-color:' + getColor(chartData.mih, colors) + ';stop-opacity:1');
|
||||
landdef
|
||||
.append("stop")
|
||||
.attr("offset", "100%")
|
||||
.attr("style", "stop-color:" + getColor(chartData.mah, colors) + ";stop-opacity:1");
|
||||
.append('stop')
|
||||
.attr('offset', '100%')
|
||||
.attr('style', 'stop-color:' + getColor(chartData.mah, colors) + ';stop-opacity:1');
|
||||
} else {
|
||||
for (let k = chartData.mah; k >= chartData.mih; k--) {
|
||||
let perc = 1 - (k - chartData.mih) / (chartData.mah - chartData.mih);
|
||||
landdef
|
||||
.append("stop")
|
||||
.attr("offset", perc * 100 + "%")
|
||||
.attr("style", "stop-color:" + getColor(k, colors) + ";stop-opacity:1");
|
||||
.append('stop')
|
||||
.attr('offset', perc * 100 + '%')
|
||||
.attr('style', 'stop-color:' + getColor(k, colors) + ';stop-opacity:1');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,14 +242,14 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
let extra = chartData.points.slice();
|
||||
let path = curve(extra);
|
||||
// this completes the right-hand side and bottom of our land "polygon"
|
||||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(extra[extra.length - 1][1]);
|
||||
path += " L" + parseInt(xscale(extra.length) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||
path += " L" + parseInt(xscale(0) + +xOffset) + "," + parseInt(yscale(0) + +yOffset);
|
||||
path += "Z";
|
||||
chart.append("g").attr("id", "epland").append("path").attr("d", path).attr("stroke", "purple").attr("stroke-width", "0").attr("fill", "url(#landdef)");
|
||||
path += ' L' + parseInt(xscale(extra.length) + +xOffset) + ',' + parseInt(extra[extra.length - 1][1]);
|
||||
path += ' L' + parseInt(xscale(extra.length) + +xOffset) + ',' + parseInt(yscale(0) + +yOffset);
|
||||
path += ' L' + parseInt(xscale(0) + +xOffset) + ',' + parseInt(yscale(0) + +yOffset);
|
||||
path += 'Z';
|
||||
chart.append('g').attr('id', 'epland').append('path').attr('d', path).attr('stroke', 'purple').attr('stroke-width', '0').attr('fill', 'url(#landdef)');
|
||||
|
||||
// biome / heights
|
||||
let g = chart.append("g").attr("id", "epbiomes");
|
||||
let g = chart.append('g').attr('id', 'epbiomes');
|
||||
const hu = heightUnit.value;
|
||||
for (let k = 0; k < chartData.points.length; k++) {
|
||||
const x = chartData.points[k][0];
|
||||
|
|
@ -257,65 +268,82 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
|
||||
const populationDesc = rn(pop * populationRate);
|
||||
|
||||
const provinceDesc = province ? ", " + pack.provinces[province].name : "";
|
||||
const dataTip = biomesData.name[chartData.biome[k]] + provinceDesc + ", " + pack.states[state].name + ", " + pack.religions[religion].name + ", " + pack.cultures[culture].name + " (height: " + chartData.height[k] + " " + hu + ", population " + populationDesc + ", cell " + chartData.cell[k] + ")";
|
||||
const provinceDesc = province ? ', ' + pack.provinces[province].name : '';
|
||||
const dataTip =
|
||||
biomesData.name[chartData.biome[k]] +
|
||||
provinceDesc +
|
||||
', ' +
|
||||
pack.states[state].name +
|
||||
', ' +
|
||||
pack.religions[religion].name +
|
||||
', ' +
|
||||
pack.cultures[culture].name +
|
||||
' (height: ' +
|
||||
chartData.height[k] +
|
||||
' ' +
|
||||
hu +
|
||||
', population ' +
|
||||
populationDesc +
|
||||
', cell ' +
|
||||
chartData.cell[k] +
|
||||
')';
|
||||
|
||||
g.append("rect").attr("stroke", c).attr("fill", c).attr("x", x).attr("y", y).attr("width", xscale(1)).attr("height", 15).attr("data-tip", dataTip);
|
||||
g.append('rect').attr('stroke', c).attr('fill', c).attr('x', x).attr('y', y).attr('width', xscale(1)).attr('height', 15).attr('data-tip', dataTip);
|
||||
}
|
||||
|
||||
const xAxis = d3
|
||||
.axisBottom(xscale)
|
||||
.ticks(10)
|
||||
.tickFormat(function (d) {
|
||||
return rn((d / chartData.points.length) * routeLen) + " " + distanceUnitInput.value;
|
||||
return rn((d / chartData.points.length) * routeLen) + ' ' + distanceUnitInput.value;
|
||||
});
|
||||
const yAxis = d3
|
||||
.axisLeft(yscale)
|
||||
.ticks(5)
|
||||
.tickFormat(function (d) {
|
||||
return d + " " + hu;
|
||||
return d + ' ' + hu;
|
||||
});
|
||||
|
||||
const xGrid = d3.axisBottom(xscale).ticks(10).tickSize(-chartHeight).tickFormat("");
|
||||
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth).tickFormat("");
|
||||
const xGrid = d3.axisBottom(xscale).ticks(10).tickSize(-chartHeight).tickFormat('');
|
||||
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth).tickFormat('');
|
||||
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epxaxis")
|
||||
.attr("transform", "translate(" + xOffset + "," + parseInt(chartHeight + +yOffset + 20) + ")")
|
||||
.append('g')
|
||||
.attr('id', 'epxaxis')
|
||||
.attr('transform', 'translate(' + xOffset + ',' + parseInt(chartHeight + +yOffset + 20) + ')')
|
||||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.style("text-anchor", "center")
|
||||
.attr("transform", function (d) {
|
||||
return "rotate(0)"; // used to rotate labels, - anti-clockwise, + clockwise
|
||||
.selectAll('text')
|
||||
.style('text-anchor', 'center')
|
||||
.attr('transform', function (d) {
|
||||
return 'rotate(0)'; // used to rotate labels, - anti-clockwise, + clockwise
|
||||
});
|
||||
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epyaxis")
|
||||
.attr("transform", "translate(" + parseInt(+xOffset - 10) + "," + parseInt(+yOffset) + ")")
|
||||
.append('g')
|
||||
.attr('id', 'epyaxis')
|
||||
.attr('transform', 'translate(' + parseInt(+xOffset - 10) + ',' + parseInt(+yOffset) + ')')
|
||||
.call(yAxis);
|
||||
|
||||
// add the X gridlines
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epxgrid")
|
||||
.attr("class", "epgrid")
|
||||
.attr("stroke-dasharray", "4 1")
|
||||
.attr("transform", "translate(" + xOffset + "," + parseInt(chartHeight + +yOffset) + ")")
|
||||
.append('g')
|
||||
.attr('id', 'epxgrid')
|
||||
.attr('class', 'epgrid')
|
||||
.attr('stroke-dasharray', '4 1')
|
||||
.attr('transform', 'translate(' + xOffset + ',' + parseInt(chartHeight + +yOffset) + ')')
|
||||
.call(xGrid);
|
||||
|
||||
// add the Y gridlines
|
||||
chart
|
||||
.append("g")
|
||||
.attr("id", "epygrid")
|
||||
.attr("class", "epgrid")
|
||||
.attr("stroke-dasharray", "4 1")
|
||||
.attr("transform", "translate(" + xOffset + "," + yOffset + ")")
|
||||
.append('g')
|
||||
.attr('id', 'epygrid')
|
||||
.attr('class', 'epgrid')
|
||||
.attr('stroke-dasharray', '4 1')
|
||||
.attr('transform', 'translate(' + xOffset + ',' + yOffset + ')')
|
||||
.call(yGrid);
|
||||
|
||||
// draw city labels - try to avoid putting labels over one another
|
||||
g = chart.append("g").attr("id", "epburglabels");
|
||||
g = chart.append('g').attr('id', 'epburglabels');
|
||||
let y1 = 0;
|
||||
const add = 15;
|
||||
|
||||
|
|
@ -331,31 +359,31 @@ function showElevationProfile(data, routeLen, isRiver) {
|
|||
if (y1 >= yOffset) y1 = add;
|
||||
|
||||
// burg name
|
||||
g.append("text")
|
||||
.attr("id", "ep" + b)
|
||||
.attr("class", "epburglabel")
|
||||
.attr("x", x1)
|
||||
.attr("y", y1)
|
||||
.attr("text-anchor", "middle");
|
||||
document.getElementById("ep" + b).innerHTML = pack.burgs[b].name;
|
||||
g.append('text')
|
||||
.attr('id', 'ep' + b)
|
||||
.attr('class', 'epburglabel')
|
||||
.attr('x', x1)
|
||||
.attr('y', y1)
|
||||
.attr('text-anchor', 'middle');
|
||||
document.getElementById('ep' + b).innerHTML = pack.burgs[b].name;
|
||||
|
||||
// arrow from burg name to graph line
|
||||
g.append("path")
|
||||
.attr("id", "eparrow" + b)
|
||||
.attr("d", "M" + x1.toString() + "," + (y1 + 3).toString() + "L" + x1.toString() + "," + parseInt(chartData.points[k][1] - 3).toString())
|
||||
.attr("stroke", "darkgray")
|
||||
.attr("fill", "lightgray")
|
||||
.attr("stroke-width", "1")
|
||||
.attr("marker-end", "url(#arrowhead)");
|
||||
g.append('path')
|
||||
.attr('id', 'eparrow' + b)
|
||||
.attr('d', 'M' + x1.toString() + ',' + (y1 + 3).toString() + 'L' + x1.toString() + ',' + parseInt(chartData.points[k][1] - 3).toString())
|
||||
.attr('stroke', 'darkgray')
|
||||
.attr('fill', 'lightgray')
|
||||
.attr('stroke-width', '1')
|
||||
.attr('marker-end', 'url(#arrowhead)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeElevationProfile() {
|
||||
document.getElementById("epScaleRange").removeEventListener("change", draw);
|
||||
document.getElementById("epCurve").removeEventListener("change", draw);
|
||||
document.getElementById("epSave").removeEventListener("click", downloadCSV);
|
||||
document.getElementById("elevationGraph").innerHTML = "";
|
||||
document.getElementById('epScaleRange').removeEventListener('change', draw);
|
||||
document.getElementById('epCurve').removeEventListener('change', draw);
|
||||
document.getElementById('epSave').removeEventListener('click', downloadCSV);
|
||||
document.getElementById('elevationGraph').innerHTML = '';
|
||||
modules.elevation = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Module to store general UI functions
|
||||
'use strict';
|
||||
// Module to store general UI functions
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
$(window).resize(function (e) {
|
||||
window.addEventListener("resize", function (e) {
|
||||
if (localStorage.getItem('mapWidth') && localStorage.getItem('mapHeight')) return;
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
mapHeightInput.value = window.innerHeight;
|
||||
|
|
@ -10,6 +10,7 @@ $(window).resize(function (e) {
|
|||
});
|
||||
|
||||
window.onbeforeunload = () => 'Are you sure you want to navigate away?';
|
||||
}
|
||||
|
||||
// Tooltips
|
||||
const tooltip = document.getElementById('tooltip');
|
||||
|
|
@ -19,12 +20,6 @@ document.getElementById('dialogs').addEventListener('mousemove', showDataTip);
|
|||
document.getElementById('optionsContainer').addEventListener('mousemove', showDataTip);
|
||||
document.getElementById('exitCustomization').addEventListener('mousemove', showDataTip);
|
||||
|
||||
/**
|
||||
* @param {string} tip Tooltip text
|
||||
* @param {boolean} main Show above other tooltips
|
||||
* @param {string} type Message type (color): error / warn / success
|
||||
* @param {number} time Timeout to auto hide, ms
|
||||
*/
|
||||
function tip(tip = 'Tip is undefined', main, type, time) {
|
||||
tooltip.innerHTML = tip;
|
||||
tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)';
|
||||
|
|
@ -32,11 +27,15 @@ function tip(tip = 'Tip is undefined', main, type, time) {
|
|||
else if (type === 'warn') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)';
|
||||
else if (type === 'success') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)';
|
||||
|
||||
if (main) tooltip.dataset.main = tip; // set main tip
|
||||
if (main) {
|
||||
if (time) setTimeout(() => (tooltip.dataset.main = ''), time); // clear main in some time
|
||||
tooltip.dataset.color = tooltip.style.background;
|
||||
}
|
||||
if (time) setTimeout(() => clearMainTip(), time);
|
||||
}
|
||||
|
||||
function showMainTip() {
|
||||
tooltip.style.background = tooltip.dataset.color;
|
||||
tooltip.innerHTML = tooltip.dataset.main;
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +54,15 @@ function showDataTip(e) {
|
|||
tip(dataTip);
|
||||
}
|
||||
|
||||
function showElementLockTip(event) {
|
||||
const locked = event?.target?.classList?.contains("icon-lock");
|
||||
if (locked) {
|
||||
tip("Click to unlock the element and allow it to be changed by regeneration tools");
|
||||
} else {
|
||||
tip("Click to lock the element and prevent changes to it by regeneration tools");
|
||||
}
|
||||
}
|
||||
|
||||
const moved = debounce(mouseMove, 100);
|
||||
function mouseMove() {
|
||||
const point = d3.mouse(this);
|
||||
|
|
@ -79,7 +87,7 @@ function showNotes(e, i) {
|
|||
document.getElementById('notes').style.display = 'block';
|
||||
document.getElementById('notesHeader').innerHTML = note.name;
|
||||
document.getElementById('notesBody').innerHTML = note.legend;
|
||||
} else if (!options.pinNotes) {
|
||||
} else if (!options.pinNotes && !markerEditor.offsetParent) {
|
||||
document.getElementById('notes').style.display = 'none';
|
||||
document.getElementById('notesHeader').innerHTML = '';
|
||||
document.getElementById('notesBody').innerHTML = '';
|
||||
|
|
@ -101,6 +109,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
if (group === 'emblems' && e.target.tagName === 'use') {
|
||||
const parent = e.target.parentNode;
|
||||
const [g, type] = parent.id === 'burgEmblems' ? [pack.burgs, 'burg'] : parent.id === 'provinceEmblems' ? [pack.provinces, 'province'] : [pack.states, 'state'];
|
||||
parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
||||
const i = +e.target.dataset.i;
|
||||
if (event.shiftKey) highlightEmblemElement(type, g[i]);
|
||||
|
||||
|
|
@ -497,229 +506,7 @@ function showInfo() {
|
|||
});
|
||||
}
|
||||
|
||||
// prevent default browser behavior for FMG-used hotkeys
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.altKey && event.keyCode !== 18) event.preventDefault(); // disallow alt key combinations
|
||||
if (event.ctrlKey && event.code === 'KeyS') event.preventDefault(); // disallow CTRL + C
|
||||
if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab
|
||||
});
|
||||
|
||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||
document.addEventListener('keyup', (event) => {
|
||||
if (!window.closeDialogs) return; // not all modules are loaded
|
||||
const canvas3d = document.getElementById('canvas3d'); // check if 3d mode is active
|
||||
const active = document.activeElement.tagName;
|
||||
if (active === 'INPUT' || active === 'SELECT' || active === 'TEXTAREA') return; // don't trigger if user inputs a text
|
||||
if (active === 'DIV' && document.activeElement.contentEditable === 'true') return; // don't trigger if user inputs a text
|
||||
event.stopPropagation();
|
||||
|
||||
const key = event.keyCode;
|
||||
const ctrl = event.ctrlKey || event.metaKey || key === 17;
|
||||
const shift = event.shiftKey || key === 16;
|
||||
const alt = event.altKey || key === 18;
|
||||
|
||||
if (key === 112) showInfo();
|
||||
// "F1" to show info
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for new map
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for a new map
|
||||
else if (key === 117) quickSave();
|
||||
// "F6" for quick save
|
||||
else if (key === 120) quickLoad();
|
||||
// "F9" for quick load
|
||||
else if (key === 9) toggleOptions(event);
|
||||
// Tab to toggle options
|
||||
else if (key === 27) {
|
||||
closeDialogs();
|
||||
hideOptions();
|
||||
} // Escape to close all dialogs
|
||||
else if (key === 46) removeElementOnKey();
|
||||
// "Delete" to remove the selected element
|
||||
else if (key === 79 && canvas3d) toggle3dOptions();
|
||||
// "O" to toggle 3d options
|
||||
else if (ctrl && key === 81) toggleSaveReminder();
|
||||
// Ctrl + "Q" to toggle save reminder
|
||||
else if (ctrl && key === 83) saveMap();
|
||||
// Ctrl + "S" to save .map file
|
||||
else if (undo.offsetParent && ctrl && key === 90) undo.click();
|
||||
// Ctrl + "Z" to undo
|
||||
else if (redo.offsetParent && ctrl && key === 89) redo.click();
|
||||
// Ctrl + "Y" to redo
|
||||
else if (shift && key === 72) editHeightmap();
|
||||
// Shift + "H" to edit Heightmap
|
||||
else if (shift && key === 66) editBiomes();
|
||||
// Shift + "B" to edit Biomes
|
||||
else if (shift && key === 83) editStates();
|
||||
// Shift + "S" to edit States
|
||||
else if (shift && key === 80) editProvinces();
|
||||
// Shift + "P" to edit Provinces
|
||||
else if (shift && key === 68) editDiplomacy();
|
||||
// Shift + "D" to edit Diplomacy
|
||||
else if (shift && key === 67) editCultures();
|
||||
// Shift + "C" to edit Cultures
|
||||
else if (shift && key === 78) editNamesbase();
|
||||
// Shift + "N" to edit Namesbase
|
||||
else if (shift && key === 90) editZones();
|
||||
// Shift + "Z" to edit Zones
|
||||
else if (shift && key === 82) editReligions();
|
||||
// Shift + "R" to edit Religions
|
||||
else if (shift && key === 81) editResources();
|
||||
// Shift + "Q" to edit Resources
|
||||
else if (shift && key === 89) openEmblemEditor();
|
||||
// Shift + "Y" to edit Emblems
|
||||
else if (shift && key === 87) editUnits();
|
||||
// Shift + "W" to edit Units
|
||||
else if (shift && key === 79) editNotes();
|
||||
// Shift + "O" to edit Notes
|
||||
else if (shift && key === 84) overviewBurgs();
|
||||
// Shift + "T" to open Burgs overview
|
||||
else if (shift && key === 86) overviewRivers();
|
||||
// Shift + "V" to open Rivers overview
|
||||
else if (shift && key === 77) overviewMilitary();
|
||||
// Shift + "M" to open Military overview
|
||||
else if (shift && key === 69) viewCellDetails();
|
||||
// Shift + "E" to open Cell Details
|
||||
else if (shift && key === 49) toggleAddBurg();
|
||||
// Shift + "1" to click to add Burg
|
||||
else if (shift && key === 50) toggleAddLabel();
|
||||
// Shift + "2" to click to add Label
|
||||
else if (shift && key === 51) toggleAddRiver();
|
||||
// Shift + "3" to click to add River
|
||||
else if (shift && key === 52) toggleAddRoute();
|
||||
// Shift + "4" to click to add Route
|
||||
else if (shift && key === 53) toggleAddMarker();
|
||||
// Shift + "5" to click to add Marker
|
||||
else if (alt && key === 66) console.table(pack.burgs);
|
||||
// Alt + "B" to log burgs data
|
||||
else if (alt && key === 83) console.table(pack.states);
|
||||
// Alt + "S" to log states data
|
||||
else if (alt && key === 67) console.table(pack.cultures);
|
||||
// Alt + "C" to log cultures data
|
||||
else if (alt && key === 82) console.table(pack.religions);
|
||||
// Alt + "R" to log religions data
|
||||
else if (alt && key === 70) console.table(pack.features);
|
||||
// Alt + "F" to log features data
|
||||
else if (key === 88) toggleTexture();
|
||||
// "X" to toggle Texture layer
|
||||
else if (key === 72) toggleHeight();
|
||||
// "H" to toggle Heightmap layer
|
||||
else if (key === 66) toggleBiomes();
|
||||
// "B" to toggle Biomes layer
|
||||
else if (key === 69) toggleCells();
|
||||
// "E" to toggle Cells layer
|
||||
else if (key === 71) toggleGrid();
|
||||
// "G" to toggle Grid layer
|
||||
else if (key === 79) toggleCoordinates();
|
||||
// "O" to toggle Coordinates layer
|
||||
else if (key === 87) toggleCompass();
|
||||
// "W" to toggle Compass Rose layer
|
||||
else if (key === 86) toggleRivers();
|
||||
// "V" to toggle Rivers layer
|
||||
else if (key === 70) toggleRelief();
|
||||
// "F" to toggle Relief icons layer
|
||||
else if (key === 67) toggleCultures();
|
||||
// "C" to toggle Cultures layer
|
||||
else if (key === 83) toggleStates();
|
||||
// "S" to toggle States layer
|
||||
else if (key === 80) toggleProvinces();
|
||||
// "P" to toggle Provinces layer
|
||||
else if (key === 90) toggleZones();
|
||||
// "Z" to toggle Zones
|
||||
else if (key === 68) toggleBorders();
|
||||
// "D" to toggle Borders layer
|
||||
else if (key === 82) toggleReligions();
|
||||
// "R" to toggle Religions layer
|
||||
else if (key === 85) toggleRoutes();
|
||||
// "U" to toggle Routes layer
|
||||
else if (key === 84) toggleTemp();
|
||||
// "T" to toggle Temperature layer
|
||||
else if (key === 78) togglePopulation();
|
||||
// "N" to toggle Population layer
|
||||
else if (key === 74) toggleIce();
|
||||
// "J" to toggle Ice layer
|
||||
else if (key === 65) togglePrec();
|
||||
// "A" to toggle Precipitation layer
|
||||
else if (key === 81) toggleResources();
|
||||
// "Q" to toggle Resources layer
|
||||
else if (key === 89) toggleEmblems();
|
||||
// "Y" to toggle Emblems layer
|
||||
else if (key === 76) toggleLabels();
|
||||
// "L" to toggle Labels layer
|
||||
else if (key === 73) toggleIcons();
|
||||
// "I" to toggle Icons layer
|
||||
else if (key === 77) toggleMilitary();
|
||||
// "M" to toggle Military layer
|
||||
else if (key === 75) toggleMarkers();
|
||||
// "K" to toggle Markers layer
|
||||
else if (key === 187) toggleRulers();
|
||||
// Equal (=) to toggle Rulers
|
||||
else if (key === 189) toggleScaleBar();
|
||||
// Minus (-) to toggle Scale bar
|
||||
else if (key === 37) zoom.translateBy(svg, 10, 0);
|
||||
// Left to scroll map left
|
||||
else if (key === 39) zoom.translateBy(svg, -10, 0);
|
||||
// Right to scroll map right
|
||||
else if (key === 38) zoom.translateBy(svg, 0, 10);
|
||||
// Up to scroll map up
|
||||
else if (key === 40) zoom.translateBy(svg, 0, -10);
|
||||
// Up to scroll map up
|
||||
else if (key === 107 || key === 109) pressNumpadSign(key);
|
||||
// Numpad Plus/Minus to zoom map or change brush size
|
||||
else if (key === 48 || key === 96) resetZoom(1000);
|
||||
// 0 to reset zoom
|
||||
else if (key === 49 || key === 97) zoom.scaleTo(svg, 1);
|
||||
// 1 to zoom to 1
|
||||
else if (key === 50 || key === 98) zoom.scaleTo(svg, 2);
|
||||
// 2 to zoom to 2
|
||||
else if (key === 51 || key === 99) zoom.scaleTo(svg, 3);
|
||||
// 3 to zoom to 3
|
||||
else if (key === 52 || key === 100) zoom.scaleTo(svg, 4);
|
||||
// 4 to zoom to 4
|
||||
else if (key === 53 || key === 101) zoom.scaleTo(svg, 5);
|
||||
// 5 to zoom to 5
|
||||
else if (key === 54 || key === 102) zoom.scaleTo(svg, 6);
|
||||
// 6 to zoom to 6
|
||||
else if (key === 55 || key === 103) zoom.scaleTo(svg, 7);
|
||||
// 7 to zoom to 7
|
||||
else if (key === 56 || key === 104) zoom.scaleTo(svg, 8);
|
||||
// 8 to zoom to 8
|
||||
else if (key === 57 || key === 105) zoom.scaleTo(svg, 9);
|
||||
// 9 to zoom to 9
|
||||
else if (ctrl) pressControl(); // Control to toggle mode
|
||||
});
|
||||
|
||||
function pressNumpadSign(key) {
|
||||
// if brush sliders are displayed, decrease brush size
|
||||
let brush = null;
|
||||
const d = key === 107 ? 1 : -1;
|
||||
|
||||
if (brushRadius.offsetParent) brush = document.getElementById('brushRadius');
|
||||
else if (biomesManuallyBrush.offsetParent) brush = document.getElementById('biomesManuallyBrush');
|
||||
else if (statesManuallyBrush.offsetParent) brush = document.getElementById('statesManuallyBrush');
|
||||
else if (provincesManuallyBrush.offsetParent) brush = document.getElementById('provincesManuallyBrush');
|
||||
else if (culturesManuallyBrush.offsetParent) brush = document.getElementById('culturesManuallyBrush');
|
||||
else if (zonesBrush.offsetParent) brush = document.getElementById('zonesBrush');
|
||||
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById('religionsManuallyBrush');
|
||||
|
||||
if (brush) {
|
||||
const value = Math.max(Math.min(+brush.value + d, +brush.max), +brush.min);
|
||||
brush.value = document.getElementById(brush.id + 'Number').value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = key === 107 ? 1.2 : 0.8;
|
||||
zoom.scaleBy(svg, scaleBy); // if no, zoom map
|
||||
}
|
||||
|
||||
function pressControl() {
|
||||
if (zonesRemove.offsetParent) {
|
||||
zonesRemove.classList.contains('pressed') ? zonesRemove.classList.remove('pressed') : zonesRemove.classList.add('pressed');
|
||||
}
|
||||
}
|
||||
|
||||
// trigger trash button click on "Delete" keypress
|
||||
function removeElementOnKey() {
|
||||
$('.dialog:visible .fastDelete').click();
|
||||
$("button:visible:contains('Remove')").click();
|
||||
}
|
||||
|
|
|
|||
810
modules/ui/general.js.orig
Normal file
810
modules/ui/general.js.orig
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
<<<<<<< HEAD
|
||||
// Module to store general UI functions
|
||||
'use strict';
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
$(window).resize(function (e) {
|
||||
if (localStorage.getItem('mapWidth') && localStorage.getItem('mapHeight')) return;
|
||||
=======
|
||||
"use strict";
|
||||
// Module to store general UI functions
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
window.addEventListener("resize", function (e) {
|
||||
if (localStorage.getItem("mapWidth") && localStorage.getItem("mapHeight")) return;
|
||||
>>>>>>> master
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
mapHeightInput.value = window.innerHeight;
|
||||
changeMapSize();
|
||||
});
|
||||
|
||||
<<<<<<< HEAD
|
||||
window.onbeforeunload = () => 'Are you sure you want to navigate away?';
|
||||
=======
|
||||
if (location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1") {
|
||||
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
||||
}
|
||||
>>>>>>> master
|
||||
|
||||
// Tooltips
|
||||
const tooltip = document.getElementById('tooltip');
|
||||
|
||||
// show tip for non-svg elemets with data-tip
|
||||
document.getElementById('dialogs').addEventListener('mousemove', showDataTip);
|
||||
document.getElementById('optionsContainer').addEventListener('mousemove', showDataTip);
|
||||
document.getElementById('exitCustomization').addEventListener('mousemove', showDataTip);
|
||||
|
||||
<<<<<<< HEAD
|
||||
/**
|
||||
* @param {string} tip Tooltip text
|
||||
* @param {boolean} main Show above other tooltips
|
||||
* @param {string} type Message type (color): error / warn / success
|
||||
* @param {number} time Timeout to auto hide, ms
|
||||
*/
|
||||
function tip(tip = 'Tip is undefined', main, type, time) {
|
||||
=======
|
||||
function tip(tip = "Tip is undefined", main, type, time) {
|
||||
>>>>>>> master
|
||||
tooltip.innerHTML = tip;
|
||||
tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)';
|
||||
if (type === 'error') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)';
|
||||
else if (type === 'warn') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)';
|
||||
else if (type === 'success') tooltip.style.background = 'linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)';
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (main) tooltip.dataset.main = tip; // set main tip
|
||||
if (time) setTimeout(() => (tooltip.dataset.main = ''), time); // clear main in some time
|
||||
=======
|
||||
if (main) {
|
||||
tooltip.dataset.main = tip;
|
||||
tooltip.dataset.color = tooltip.style.background;
|
||||
}
|
||||
if (time) setTimeout(() => clearMainTip(), time);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function showMainTip() {
|
||||
tooltip.style.background = tooltip.dataset.color;
|
||||
tooltip.innerHTML = tooltip.dataset.main;
|
||||
}
|
||||
|
||||
function clearMainTip() {
|
||||
<<<<<<< HEAD
|
||||
tooltip.dataset.main = '';
|
||||
tooltip.innerHTML = '';
|
||||
=======
|
||||
tooltip.dataset.color = "";
|
||||
tooltip.dataset.main = "";
|
||||
tooltip.innerHTML = "";
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
// show tip at the bottom of the screen, consider possible translation
|
||||
function showDataTip(e) {
|
||||
if (!e.target) return;
|
||||
let dataTip = e.target.dataset.tip;
|
||||
if (!dataTip && e.target.parentNode.dataset.tip) dataTip = e.target.parentNode.dataset.tip;
|
||||
if (!dataTip) return;
|
||||
//const tooltip = lang === "en" ? dataTip : translate(e.target.dataset.t || e.target.parentNode.dataset.t, dataTip);
|
||||
tip(dataTip);
|
||||
}
|
||||
|
||||
function showElementLockTip(event) {
|
||||
const locked = event?.target?.classList?.contains("icon-lock");
|
||||
if (locked) {
|
||||
tip("Click to unlock the element and allow it to be changed by regeneration tools");
|
||||
} else {
|
||||
tip("Click to lock the element and prevent changes to it by regeneration tools");
|
||||
}
|
||||
}
|
||||
|
||||
const moved = debounce(mouseMove, 100);
|
||||
function mouseMove() {
|
||||
const point = d3.mouse(this);
|
||||
const i = findCell(point[0], point[1]); // pack cell id
|
||||
if (i === undefined) return;
|
||||
showNotes(d3.event, i);
|
||||
const g = findGridCell(point[0], point[1]); // grid cell id
|
||||
if (tooltip.dataset.main) showMainTip();
|
||||
else showMapTooltip(point, d3.event, i, g);
|
||||
if (cellInfo.offsetParent) updateCellInfo(point, i, g);
|
||||
}
|
||||
|
||||
// show note box on hover (if any)
|
||||
function showNotes(e, i) {
|
||||
if (notesEditor.offsetParent) return;
|
||||
let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id;
|
||||
<<<<<<< HEAD
|
||||
if (e.target.parentNode.parentNode.id === 'burgLabels') id = 'burg' + e.target.dataset.id;
|
||||
else if (e.target.parentNode.parentNode.id === 'burgIcons') id = 'burg' + e.target.dataset.id;
|
||||
|
||||
const note = notes.find((note) => note.id === id);
|
||||
if (note !== undefined && note.legend !== '') {
|
||||
document.getElementById('notes').style.display = 'block';
|
||||
document.getElementById('notesHeader').innerHTML = note.name;
|
||||
document.getElementById('notesBody').innerHTML = note.legend;
|
||||
} else if (!options.pinNotes) {
|
||||
document.getElementById('notes').style.display = 'none';
|
||||
document.getElementById('notesHeader').innerHTML = '';
|
||||
document.getElementById('notesBody').innerHTML = '';
|
||||
=======
|
||||
if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id;
|
||||
else if (e.target.parentNode.parentNode.id === "burgIcons") id = "burg" + e.target.dataset.id;
|
||||
|
||||
const note = notes.find(note => note.id === id);
|
||||
if (note !== undefined && note.legend !== "") {
|
||||
document.getElementById("notes").style.display = "block";
|
||||
document.getElementById("notesHeader").innerHTML = note.name;
|
||||
document.getElementById("notesBody").innerHTML = note.legend;
|
||||
} else if (!options.pinNotes && !markerEditor.offsetParent) {
|
||||
document.getElementById("notes").style.display = "none";
|
||||
document.getElementById("notesHeader").innerHTML = "";
|
||||
document.getElementById("notesBody").innerHTML = "";
|
||||
>>>>>>> master
|
||||
}
|
||||
}
|
||||
|
||||
// show viewbox tooltip if main tooltip is blank
|
||||
function showMapTooltip(point, e, i, g) {
|
||||
tip(''); // clear tip
|
||||
const path = e.composedPath ? e.composedPath() : getComposedPath(e.target); // apply polyfill
|
||||
if (!path[path.length - 8]) return;
|
||||
const group = path[path.length - 7].id;
|
||||
const subgroup = path[path.length - 8].id;
|
||||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
// specific elements
|
||||
if (group === 'armies') return tip(e.target.parentNode.dataset.name + '. Click to edit');
|
||||
|
||||
if (group === 'emblems' && e.target.tagName === 'use') {
|
||||
const parent = e.target.parentNode;
|
||||
<<<<<<< HEAD
|
||||
const [g, type] = parent.id === 'burgEmblems' ? [pack.burgs, 'burg'] : parent.id === 'provinceEmblems' ? [pack.provinces, 'province'] : [pack.states, 'state'];
|
||||
=======
|
||||
const [g, type] =
|
||||
parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"];
|
||||
>>>>>>> master
|
||||
const i = +e.target.dataset.i;
|
||||
if (event.shiftKey) highlightEmblemElement(type, g[i]);
|
||||
|
||||
d3.select(e.target).raise();
|
||||
d3.select(parent).raise();
|
||||
|
||||
const name = g[i].fullName || g[i].name;
|
||||
tip(`${name} ${type} emblem. Click to edit. Hold Shift to show associated area or place`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'goods') {
|
||||
const id = +e.target.dataset.i;
|
||||
const resource = pack.resources.find((resource) => resource.i === id);
|
||||
tip('Resource: ' + resource.name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'rivers') {
|
||||
const river = +e.target.id.slice(5);
|
||||
const r = pack.rivers.find((r) => r.i === river);
|
||||
const name = r ? r.name + ' ' + r.type : '';
|
||||
tip(name + '. Click to edit');
|
||||
if (riversOverview.offsetParent) highlightEditorLine(riversOverview, river, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'routes') return tip('Click to edit the Route');
|
||||
|
||||
if (group === 'terrain') return tip('Click to edit the Relief Icon');
|
||||
|
||||
if (subgroup === 'burgLabels' || subgroup === 'burgIcons') {
|
||||
const burg = +path[path.length - 10].dataset.id;
|
||||
const b = pack.burgs[burg];
|
||||
const population = si(b.population * populationRate * urbanization);
|
||||
tip(`${b.name}. Population: ${population}. Click to edit`);
|
||||
if (burgsOverview.offsetParent) highlightEditorLine(burgsOverview, burg, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === 'labels') return tip('Click to edit the Label');
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (group === 'markers') return tip('Click to edit the Marker');
|
||||
=======
|
||||
if (group === "markers") return tip("Click to edit the Marker and pin the marker note");
|
||||
>>>>>>> master
|
||||
|
||||
if (group === 'ruler') {
|
||||
const tag = e.target.tagName;
|
||||
const className = e.target.getAttribute('class');
|
||||
if (tag === 'circle' && className === 'edge') return tip('Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point');
|
||||
if (tag === 'circle' && className === 'control') return tip('Drag to adjust. Hold Shift and drag to keep axial direction. Click to remove the point');
|
||||
if (tag === 'circle') return tip('Drag to adjust the measurer');
|
||||
if (tag === 'polyline') return tip('Click on drag to add a control point');
|
||||
if (tag === 'path') return tip('Drag to move the measurer');
|
||||
if (tag === 'text') return tip('Drag to move, click to remove the measurer');
|
||||
}
|
||||
|
||||
if (subgroup === 'burgIcons') return tip('Click to edit the Burg');
|
||||
|
||||
if (subgroup === 'burgLabels') return tip('Click to edit the Burg');
|
||||
|
||||
if (group === 'lakes' && !land) {
|
||||
const lakeId = +e.target.dataset.f;
|
||||
const name = pack.features[lakeId]?.name;
|
||||
const fullName = subgroup === 'freshwater' ? name : name + ' ' + subgroup;
|
||||
tip(`${fullName} lake. Click to edit`);
|
||||
return;
|
||||
}
|
||||
if (group === 'coastline') return tip('Click to edit the coastline');
|
||||
|
||||
if (group === 'zones') {
|
||||
const zone = path[path.length - 8];
|
||||
tip(zone.dataset.description);
|
||||
if (zonesEditor.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'ice') return tip('Click to edit the Ice');
|
||||
|
||||
// covering elements
|
||||
if (layerIsOn('togglePrec') && land) tip('Annual Precipitation: ' + getFriendlyPrecipitation(i));
|
||||
else if (layerIsOn('togglePopulation')) tip(getPopulationTip(i));
|
||||
else if (layerIsOn('toggleTemp')) tip('Temperature: ' + convertTemperature(grid.cells.temp[g]));
|
||||
else if (layerIsOn('toggleBiomes') && pack.cells.biome[i]) {
|
||||
const biome = pack.cells.biome[i];
|
||||
tip('Biome: ' + biomesData.name[biome]);
|
||||
if (biomesEditor.offsetParent) highlightEditorLine(biomesEditor, biome);
|
||||
} else if (layerIsOn('toggleReligions') && pack.cells.religion[i]) {
|
||||
const religion = pack.cells.religion[i];
|
||||
const r = pack.religions[religion];
|
||||
const type = r.type === 'Cult' || r.type == 'Heresy' ? r.type : r.type + ' religion';
|
||||
tip(type + ': ' + r.name);
|
||||
if (religionsEditor.offsetParent) highlightEditorLine(religionsEditor, religion);
|
||||
} else if (pack.cells.state[i] && (layerIsOn('toggleProvinces') || layerIsOn('toggleStates'))) {
|
||||
const state = pack.cells.state[i];
|
||||
const stateName = pack.states[state].fullName;
|
||||
const province = pack.cells.province[i];
|
||||
const prov = province ? pack.provinces[province].fullName + ', ' : '';
|
||||
tip(prov + stateName);
|
||||
if (statesEditor.offsetParent) highlightEditorLine(statesEditor, state);
|
||||
if (diplomacyEditor.offsetParent) highlightEditorLine(diplomacyEditor, state);
|
||||
if (militaryOverview.offsetParent) highlightEditorLine(militaryOverview, state);
|
||||
if (provincesEditor.offsetParent) highlightEditorLine(provincesEditor, province);
|
||||
} else if (layerIsOn('toggleCultures') && pack.cells.culture[i]) {
|
||||
const culture = pack.cells.culture[i];
|
||||
tip('Culture: ' + pack.cultures[culture].name);
|
||||
if (culturesEditor.offsetParent) highlightEditorLine(culturesEditor, culture);
|
||||
} else if (layerIsOn('toggleHeight')) tip('Height: ' + getFriendlyHeight(point));
|
||||
}
|
||||
|
||||
function highlightEditorLine(editor, id, timeout = 15000) {
|
||||
Array.from(editor.getElementsByClassName('states hovered')).forEach((el) => el.classList.remove('hovered')); // clear all hovered
|
||||
const hovered = Array.from(editor.querySelectorAll('div')).find((el) => el.dataset.id == id);
|
||||
if (hovered) hovered.classList.add('hovered'); // add hovered class
|
||||
if (timeout)
|
||||
setTimeout(() => {
|
||||
hovered && hovered.classList.remove('hovered');
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
// get cell info on mouse move
|
||||
function updateCellInfo(point, i, g) {
|
||||
const cells = pack.cells;
|
||||
const x = (infoX.innerHTML = rn(point[0]));
|
||||
const y = (infoY.innerHTML = rn(point[1]));
|
||||
const f = cells.f[i];
|
||||
infoLat.innerHTML = toDMS(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT, 'lat');
|
||||
infoLon.innerHTML = toDMS(mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT, 'lon');
|
||||
|
||||
infoCell.innerHTML = i;
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
infoArea.innerHTML = cells.area[i] ? si(cells.area[i] * distanceScaleInput.value ** 2) + unit : 'n/a';
|
||||
infoEvelation.innerHTML = getElevation(pack.features[f], pack.cells.h[i]);
|
||||
infoDepth.innerHTML = getDepth(pack.features[f], pack.cells.h[i], point);
|
||||
infoTemp.innerHTML = convertTemperature(grid.cells.temp[g]);
|
||||
infoPrec.innerHTML = cells.h[i] >= 20 ? getFriendlyPrecipitation(i) : 'n/a';
|
||||
infoRiver.innerHTML = cells.h[i] >= 20 && cells.r[i] ? getRiverInfo(cells.r[i]) : 'no';
|
||||
infoState.innerHTML = cells.h[i] >= 20 ? (cells.state[i] ? `${pack.states[cells.state[i]].fullName} (${cells.state[i]})` : 'neutral lands (0)') : 'no';
|
||||
infoProvince.innerHTML = cells.province[i] ? `${pack.provinces[cells.province[i]].fullName} (${cells.province[i]})` : 'no';
|
||||
infoCulture.innerHTML = cells.culture[i] ? `${pack.cultures[cells.culture[i]].name} (${cells.culture[i]})` : 'no';
|
||||
infoReligion.innerHTML = cells.religion[i] ? `${pack.religions[cells.religion[i]].name} (${cells.religion[i]})` : 'no';
|
||||
infoPopulation.innerHTML = getFriendlyPopulation(i);
|
||||
infoBurg.innerHTML = cells.burg[i] ? pack.burgs[cells.burg[i]].name + ' (' + cells.burg[i] + ')' : 'no';
|
||||
infoFeature.innerHTML = f ? pack.features[f].group + ' (' + f + ')' : 'n/a';
|
||||
infoBiome.innerHTML = biomesData.name[cells.biome[i]];
|
||||
}
|
||||
|
||||
// convert coordinate to DMS format
|
||||
function toDMS(coord, c) {
|
||||
const degrees = Math.floor(Math.abs(coord));
|
||||
const minutesNotTruncated = (Math.abs(coord) - degrees) * 60;
|
||||
const minutes = Math.floor(minutesNotTruncated);
|
||||
const seconds = Math.floor((minutesNotTruncated - minutes) * 60);
|
||||
const cardinal = c === 'lat' ? (coord >= 0 ? 'N' : 'S') : coord >= 0 ? 'E' : 'W';
|
||||
return degrees + '° ' + minutes + '′ ' + seconds + '″ ' + cardinal;
|
||||
}
|
||||
|
||||
// get surface elevation
|
||||
function getElevation(f, h) {
|
||||
if (f.land) return getHeight(h) + ' (' + h + ')'; // land: usual height
|
||||
if (f.border) return '0 ' + heightUnit.value; // ocean: 0
|
||||
if (f.type === 'lake') return getHeight(f.height) + ' (' + f.height + ')'; // lake: defined on river generation
|
||||
}
|
||||
|
||||
// get water depth
|
||||
function getDepth(f, h, p) {
|
||||
if (f.land) return '0 ' + heightUnit.value; // land: 0
|
||||
|
||||
// lake: difference between surface and bottom
|
||||
const gridH = grid.cells.h[findGridCell(p[0], p[1])];
|
||||
if (f.type === 'lake') {
|
||||
const depth = gridH === 19 ? f.height / 2 : gridH;
|
||||
return getHeight(depth, 'abs');
|
||||
}
|
||||
|
||||
return getHeight(gridH, 'abs'); // ocean: grid height
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) height value from map data
|
||||
function getFriendlyHeight(p) {
|
||||
const packH = pack.cells.h[findCell(p[0], p[1])];
|
||||
const gridH = grid.cells.h[findGridCell(p[0], p[1])];
|
||||
const h = packH < 20 ? gridH : packH;
|
||||
return getHeight(h);
|
||||
}
|
||||
|
||||
function getHeight(h, abs) {
|
||||
const unit = heightUnit.value;
|
||||
let unitRatio = 3.281; // default calculations are in feet
|
||||
if (unit === 'm') unitRatio = 1;
|
||||
// if meter
|
||||
else if (unit === 'f') unitRatio = 0.5468; // if fathom
|
||||
|
||||
let height = -990;
|
||||
if (h >= 20) height = Math.pow(h - 18, +heightExponentInput.value);
|
||||
else if (h < 20 && h > 0) height = ((h - 20) / h) * 50;
|
||||
|
||||
if (abs) height = Math.abs(height);
|
||||
return rn(height * unitRatio) + ' ' + unit;
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) precipitation value from map data
|
||||
function getFriendlyPrecipitation(i) {
|
||||
const prec = grid.cells.prec[pack.cells.g[i]];
|
||||
return prec * 100 + ' mm';
|
||||
}
|
||||
|
||||
function getRiverInfo(id) {
|
||||
const r = pack.rivers.find((r) => r.i == id);
|
||||
return r ? `${r.name} ${r.type} (${id})` : 'n/a';
|
||||
}
|
||||
|
||||
function getCellPopulation(i) {
|
||||
const rural = pack.cells.pop[i] * populationRate;
|
||||
const urban = pack.cells.burg[i] ? pack.burgs[pack.cells.burg[i]].population * populationRate * urbanization : 0;
|
||||
return [rural, urban];
|
||||
}
|
||||
|
||||
// get user-friendly (real-world) population value from map data
|
||||
function getFriendlyPopulation(i) {
|
||||
const [rural, urban] = getCellPopulation(i);
|
||||
return `${si(rural + urban)} (${si(rural)} rural, urban ${si(urban)})`;
|
||||
}
|
||||
|
||||
function getPopulationTip(i) {
|
||||
const [rural, urban] = getCellPopulation(i);
|
||||
return `Cell population: ${si(rural + urban)}; Rural: ${si(rural)}; Urban: ${si(urban)}`;
|
||||
}
|
||||
|
||||
function highlightEmblemElement(type, el) {
|
||||
const i = el.i,
|
||||
cells = pack.cells;
|
||||
const animation = d3.transition().duration(1000).ease(d3.easeSinIn);
|
||||
|
||||
if (type === 'burg') {
|
||||
const {x, y} = el;
|
||||
debug
|
||||
<<<<<<< HEAD
|
||||
.append('circle')
|
||||
.attr('cx', x)
|
||||
.attr('cy', y)
|
||||
.attr('r', 0)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#d0240f')
|
||||
.attr('stroke-width', 1)
|
||||
.attr('opacity', 1)
|
||||
.transition(animation)
|
||||
.attr('r', 20)
|
||||
.attr('opacity', 0.1)
|
||||
.attr('stroke-width', 0)
|
||||
=======
|
||||
.append("circle")
|
||||
.attr("cx", x)
|
||||
.attr("cy", y)
|
||||
.attr("r", 0)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "#d0240f")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.transition(animation)
|
||||
.attr("r", 20)
|
||||
.attr("opacity", 0.1)
|
||||
.attr("stroke-width", 0)
|
||||
>>>>>>> master
|
||||
.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const [x, y] = el.pole || pack.cells.p[el.center];
|
||||
const obj = type === 'state' ? cells.state : cells.province;
|
||||
const borderCells = cells.i.filter((id) => obj[id] === i && cells.c[id].some((n) => obj[n] !== i));
|
||||
const data = Array.from(borderCells)
|
||||
.filter((c, i) => !(i % 2))
|
||||
.map((i) => cells.p[i])
|
||||
.map((i) => [i[0], i[1], Math.hypot(i[0] - x, i[1] - y)]);
|
||||
|
||||
debug
|
||||
.selectAll('line')
|
||||
.data(data)
|
||||
.enter()
|
||||
.append('line')
|
||||
.attr('x1', x)
|
||||
.attr('y1', y)
|
||||
.attr('x2', (d) => d[0])
|
||||
.attr('y2', (d) => d[1])
|
||||
.attr('stroke', '#d0240f')
|
||||
.attr('stroke-width', 0.5)
|
||||
.attr('opacity', 0.2)
|
||||
.attr('stroke-dashoffset', (d) => d[2])
|
||||
.attr('stroke-dasharray', (d) => d[2])
|
||||
.transition(animation)
|
||||
.attr('stroke-dashoffset', 0)
|
||||
.attr('opacity', 1)
|
||||
.transition(animation)
|
||||
.delay(1000)
|
||||
.attr('stroke-dashoffset', (d) => d[2])
|
||||
.attr('opacity', 0)
|
||||
.remove();
|
||||
}
|
||||
|
||||
// assign lock behavior
|
||||
document.querySelectorAll('[data-locked]').forEach(function (e) {
|
||||
e.addEventListener('mouseover', function (event) {
|
||||
if (this.className === 'icon-lock') tip('Click to unlock the option and allow it to be randomized on new map generation');
|
||||
else tip('Click to lock the option and always use the current value on new map generation');
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
e.addEventListener('click', function () {
|
||||
const id = this.id.slice(5);
|
||||
if (this.className === 'icon-lock') unlock(id);
|
||||
else lock(id);
|
||||
});
|
||||
});
|
||||
|
||||
// lock option
|
||||
function lock(id) {
|
||||
const input = document.querySelector("[data-stored='" + id + "']");
|
||||
if (input) localStorage.setItem(id, input.value);
|
||||
const el = document.getElementById('lock_' + id);
|
||||
if (!el) return;
|
||||
el.dataset.locked = 1;
|
||||
el.className = 'icon-lock';
|
||||
}
|
||||
|
||||
// unlock option
|
||||
function unlock(id) {
|
||||
localStorage.removeItem(id);
|
||||
const el = document.getElementById('lock_' + id);
|
||||
if (!el) return;
|
||||
el.dataset.locked = 0;
|
||||
el.className = 'icon-lock-open';
|
||||
}
|
||||
|
||||
// check if option is locked
|
||||
function locked(id) {
|
||||
const lockEl = document.getElementById('lock_' + id);
|
||||
return lockEl.dataset.locked == 1;
|
||||
}
|
||||
|
||||
// check if option is stored in localStorage
|
||||
function stored(option) {
|
||||
return localStorage.getItem(option);
|
||||
}
|
||||
|
||||
// assign skeaker behaviour
|
||||
Array.from(document.getElementsByClassName('speaker')).forEach((el) => {
|
||||
const input = el.previousElementSibling;
|
||||
el.addEventListener('click', () => speak(input.value));
|
||||
});
|
||||
|
||||
function speak(text) {
|
||||
const speaker = new SpeechSynthesisUtterance(text);
|
||||
const voices = speechSynthesis.getVoices();
|
||||
if (voices.length) {
|
||||
const voiceId = +document.getElementById('speakerVoice').value;
|
||||
speaker.voice = voices[voiceId];
|
||||
}
|
||||
speechSynthesis.speak(speaker);
|
||||
}
|
||||
|
||||
// apply drop-down menu option. If the value is not in options, add it
|
||||
function applyOption(select, id, name = id) {
|
||||
const custom = !Array.from(select.options).some((o) => o.value == id);
|
||||
if (custom) select.options.add(new Option(name, id));
|
||||
select.value = id;
|
||||
}
|
||||
|
||||
// show info about the generator in a popup
|
||||
function showInfo() {
|
||||
const Discord = link('https://discordapp.com/invite/X7E84HU', 'Discord');
|
||||
const Reddit = link('https://www.reddit.com/r/FantasyMapGenerator', 'Reddit');
|
||||
const Patreon = link('https://www.patreon.com/azgaar', 'Patreon');
|
||||
const Trello = link('https://trello.com/b/7x832DG4/fantasy-map-generator', 'Trello');
|
||||
const Armoria = link('https://azgaar.github.io/Armoria', 'Armoria');
|
||||
|
||||
const QuickStart = link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial', 'Quick start tutorial');
|
||||
const QAA = link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A', 'Q&A page');
|
||||
|
||||
alertMessage.innerHTML = `
|
||||
<b>Fantasy Map Generator</b> (FMG) is an open-source application, it means the code is published an anyone can use it.
|
||||
In case of FMG is also means that you own all created maps and can use them as you wish, you can even sell them.
|
||||
|
||||
<p>The development is supported by community, you can donate on ${Patreon}.
|
||||
You can also help creating overviews, tutorials and spreding the word about the Generator.</p>
|
||||
|
||||
<p>The best way to get help is to contact the community on ${Discord} and ${Reddit}.
|
||||
Before asking questions, please check out the ${QuickStart} and the ${QAA}.</p>
|
||||
|
||||
<p>Track the development process on ${Trello}.</p>
|
||||
|
||||
<p>Check out our new project: ${Armoria}, heraldry generator and editor.</p>
|
||||
|
||||
<b>Links:</b>
|
||||
<ul style="columns:2">
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator', 'GitHub repository')}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE', 'License')}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog', 'Changelog')}</li>
|
||||
<li>${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys', 'Hotkeys')}</li>
|
||||
</ul>`;
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: document.title,
|
||||
width: '28em',
|
||||
buttons: {
|
||||
OK: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
position: {my: 'center', at: 'center', of: 'svg'}
|
||||
});
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
|
||||
// prevent default browser behavior for FMG-used hotkeys
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.altKey && event.keyCode !== 18) event.preventDefault(); // disallow alt key combinations
|
||||
if (event.ctrlKey && event.code === 'KeyS') event.preventDefault(); // disallow CTRL + C
|
||||
if ([112, 113, 117, 120, 9].includes(event.keyCode)) event.preventDefault(); // F1, F2, F6, F9, Tab
|
||||
});
|
||||
|
||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||
document.addEventListener('keyup', (event) => {
|
||||
if (!window.closeDialogs) return; // not all modules are loaded
|
||||
const canvas3d = document.getElementById('canvas3d'); // check if 3d mode is active
|
||||
const active = document.activeElement.tagName;
|
||||
if (active === 'INPUT' || active === 'SELECT' || active === 'TEXTAREA') return; // don't trigger if user inputs a text
|
||||
if (active === 'DIV' && document.activeElement.contentEditable === 'true') return; // don't trigger if user inputs a text
|
||||
event.stopPropagation();
|
||||
|
||||
const key = event.keyCode;
|
||||
const ctrl = event.ctrlKey || event.metaKey || key === 17;
|
||||
const shift = event.shiftKey || key === 16;
|
||||
const alt = event.altKey || key === 18;
|
||||
|
||||
if (key === 112) showInfo();
|
||||
// "F1" to show info
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for new map
|
||||
else if (key === 113) regeneratePrompt();
|
||||
// "F2" for a new map
|
||||
else if (key === 117) quickSave();
|
||||
// "F6" for quick save
|
||||
else if (key === 120) quickLoad();
|
||||
// "F9" for quick load
|
||||
else if (key === 9) toggleOptions(event);
|
||||
// Tab to toggle options
|
||||
else if (key === 27) {
|
||||
closeDialogs();
|
||||
hideOptions();
|
||||
} // Escape to close all dialogs
|
||||
else if (key === 46) removeElementOnKey();
|
||||
// "Delete" to remove the selected element
|
||||
else if (key === 79 && canvas3d) toggle3dOptions();
|
||||
// "O" to toggle 3d options
|
||||
else if (ctrl && key === 81) toggleSaveReminder();
|
||||
// Ctrl + "Q" to toggle save reminder
|
||||
else if (ctrl && key === 83) saveMap();
|
||||
// Ctrl + "S" to save .map file
|
||||
else if (undo.offsetParent && ctrl && key === 90) undo.click();
|
||||
// Ctrl + "Z" to undo
|
||||
else if (redo.offsetParent && ctrl && key === 89) redo.click();
|
||||
// Ctrl + "Y" to redo
|
||||
else if (shift && key === 72) editHeightmap();
|
||||
// Shift + "H" to edit Heightmap
|
||||
else if (shift && key === 66) editBiomes();
|
||||
// Shift + "B" to edit Biomes
|
||||
else if (shift && key === 83) editStates();
|
||||
// Shift + "S" to edit States
|
||||
else if (shift && key === 80) editProvinces();
|
||||
// Shift + "P" to edit Provinces
|
||||
else if (shift && key === 68) editDiplomacy();
|
||||
// Shift + "D" to edit Diplomacy
|
||||
else if (shift && key === 67) editCultures();
|
||||
// Shift + "C" to edit Cultures
|
||||
else if (shift && key === 78) editNamesbase();
|
||||
// Shift + "N" to edit Namesbase
|
||||
else if (shift && key === 90) editZones();
|
||||
// Shift + "Z" to edit Zones
|
||||
else if (shift && key === 82) editReligions();
|
||||
// Shift + "R" to edit Religions
|
||||
else if (shift && key === 81) editResources();
|
||||
// Shift + "Q" to edit Resources
|
||||
else if (shift && key === 89) openEmblemEditor();
|
||||
// Shift + "Y" to edit Emblems
|
||||
else if (shift && key === 87) editUnits();
|
||||
// Shift + "W" to edit Units
|
||||
else if (shift && key === 79) editNotes();
|
||||
// Shift + "O" to edit Notes
|
||||
else if (shift && key === 84) overviewBurgs();
|
||||
// Shift + "T" to open Burgs overview
|
||||
else if (shift && key === 86) overviewRivers();
|
||||
// Shift + "V" to open Rivers overview
|
||||
else if (shift && key === 77) overviewMilitary();
|
||||
// Shift + "M" to open Military overview
|
||||
else if (shift && key === 69) viewCellDetails();
|
||||
// Shift + "E" to open Cell Details
|
||||
else if (shift && key === 49) toggleAddBurg();
|
||||
// Shift + "1" to click to add Burg
|
||||
else if (shift && key === 50) toggleAddLabel();
|
||||
// Shift + "2" to click to add Label
|
||||
else if (shift && key === 51) toggleAddRiver();
|
||||
// Shift + "3" to click to add River
|
||||
else if (shift && key === 52) toggleAddRoute();
|
||||
// Shift + "4" to click to add Route
|
||||
else if (shift && key === 53) toggleAddMarker();
|
||||
// Shift + "5" to click to add Marker
|
||||
else if (alt && key === 66) console.table(pack.burgs);
|
||||
// Alt + "B" to log burgs data
|
||||
else if (alt && key === 83) console.table(pack.states);
|
||||
// Alt + "S" to log states data
|
||||
else if (alt && key === 67) console.table(pack.cultures);
|
||||
// Alt + "C" to log cultures data
|
||||
else if (alt && key === 82) console.table(pack.religions);
|
||||
// Alt + "R" to log religions data
|
||||
else if (alt && key === 70) console.table(pack.features);
|
||||
// Alt + "F" to log features data
|
||||
else if (key === 88) toggleTexture();
|
||||
// "X" to toggle Texture layer
|
||||
else if (key === 72) toggleHeight();
|
||||
// "H" to toggle Heightmap layer
|
||||
else if (key === 66) toggleBiomes();
|
||||
// "B" to toggle Biomes layer
|
||||
else if (key === 69) toggleCells();
|
||||
// "E" to toggle Cells layer
|
||||
else if (key === 71) toggleGrid();
|
||||
// "G" to toggle Grid layer
|
||||
else if (key === 79) toggleCoordinates();
|
||||
// "O" to toggle Coordinates layer
|
||||
else if (key === 87) toggleCompass();
|
||||
// "W" to toggle Compass Rose layer
|
||||
else if (key === 86) toggleRivers();
|
||||
// "V" to toggle Rivers layer
|
||||
else if (key === 70) toggleRelief();
|
||||
// "F" to toggle Relief icons layer
|
||||
else if (key === 67) toggleCultures();
|
||||
// "C" to toggle Cultures layer
|
||||
else if (key === 83) toggleStates();
|
||||
// "S" to toggle States layer
|
||||
else if (key === 80) toggleProvinces();
|
||||
// "P" to toggle Provinces layer
|
||||
else if (key === 90) toggleZones();
|
||||
// "Z" to toggle Zones
|
||||
else if (key === 68) toggleBorders();
|
||||
// "D" to toggle Borders layer
|
||||
else if (key === 82) toggleReligions();
|
||||
// "R" to toggle Religions layer
|
||||
else if (key === 85) toggleRoutes();
|
||||
// "U" to toggle Routes layer
|
||||
else if (key === 84) toggleTemp();
|
||||
// "T" to toggle Temperature layer
|
||||
else if (key === 78) togglePopulation();
|
||||
// "N" to toggle Population layer
|
||||
else if (key === 74) toggleIce();
|
||||
// "J" to toggle Ice layer
|
||||
else if (key === 65) togglePrec();
|
||||
// "A" to toggle Precipitation layer
|
||||
else if (key === 81) toggleResources();
|
||||
// "Q" to toggle Resources layer
|
||||
else if (key === 89) toggleEmblems();
|
||||
// "Y" to toggle Emblems layer
|
||||
else if (key === 76) toggleLabels();
|
||||
// "L" to toggle Labels layer
|
||||
else if (key === 73) toggleIcons();
|
||||
// "I" to toggle Icons layer
|
||||
else if (key === 77) toggleMilitary();
|
||||
// "M" to toggle Military layer
|
||||
else if (key === 75) toggleMarkers();
|
||||
// "K" to toggle Markers layer
|
||||
else if (key === 187) toggleRulers();
|
||||
// Equal (=) to toggle Rulers
|
||||
else if (key === 189) toggleScaleBar();
|
||||
// Minus (-) to toggle Scale bar
|
||||
else if (key === 37) zoom.translateBy(svg, 10, 0);
|
||||
// Left to scroll map left
|
||||
else if (key === 39) zoom.translateBy(svg, -10, 0);
|
||||
// Right to scroll map right
|
||||
else if (key === 38) zoom.translateBy(svg, 0, 10);
|
||||
// Up to scroll map up
|
||||
else if (key === 40) zoom.translateBy(svg, 0, -10);
|
||||
// Up to scroll map up
|
||||
else if (key === 107 || key === 109) pressNumpadSign(key);
|
||||
// Numpad Plus/Minus to zoom map or change brush size
|
||||
else if (key === 48 || key === 96) resetZoom(1000);
|
||||
// 0 to reset zoom
|
||||
else if (key === 49 || key === 97) zoom.scaleTo(svg, 1);
|
||||
// 1 to zoom to 1
|
||||
else if (key === 50 || key === 98) zoom.scaleTo(svg, 2);
|
||||
// 2 to zoom to 2
|
||||
else if (key === 51 || key === 99) zoom.scaleTo(svg, 3);
|
||||
// 3 to zoom to 3
|
||||
else if (key === 52 || key === 100) zoom.scaleTo(svg, 4);
|
||||
// 4 to zoom to 4
|
||||
else if (key === 53 || key === 101) zoom.scaleTo(svg, 5);
|
||||
// 5 to zoom to 5
|
||||
else if (key === 54 || key === 102) zoom.scaleTo(svg, 6);
|
||||
// 6 to zoom to 6
|
||||
else if (key === 55 || key === 103) zoom.scaleTo(svg, 7);
|
||||
// 7 to zoom to 7
|
||||
else if (key === 56 || key === 104) zoom.scaleTo(svg, 8);
|
||||
// 8 to zoom to 8
|
||||
else if (key === 57 || key === 105) zoom.scaleTo(svg, 9);
|
||||
// 9 to zoom to 9
|
||||
else if (ctrl) pressControl(); // Control to toggle mode
|
||||
});
|
||||
|
||||
function pressNumpadSign(key) {
|
||||
// if brush sliders are displayed, decrease brush size
|
||||
let brush = null;
|
||||
const d = key === 107 ? 1 : -1;
|
||||
|
||||
if (brushRadius.offsetParent) brush = document.getElementById('brushRadius');
|
||||
else if (biomesManuallyBrush.offsetParent) brush = document.getElementById('biomesManuallyBrush');
|
||||
else if (statesManuallyBrush.offsetParent) brush = document.getElementById('statesManuallyBrush');
|
||||
else if (provincesManuallyBrush.offsetParent) brush = document.getElementById('provincesManuallyBrush');
|
||||
else if (culturesManuallyBrush.offsetParent) brush = document.getElementById('culturesManuallyBrush');
|
||||
else if (zonesBrush.offsetParent) brush = document.getElementById('zonesBrush');
|
||||
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById('religionsManuallyBrush');
|
||||
|
||||
if (brush) {
|
||||
const value = Math.max(Math.min(+brush.value + d, +brush.max), +brush.min);
|
||||
brush.value = document.getElementById(brush.id + 'Number').value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = key === 107 ? 1.2 : 0.8;
|
||||
zoom.scaleBy(svg, scaleBy); // if no, zoom map
|
||||
}
|
||||
|
||||
function pressControl() {
|
||||
if (zonesRemove.offsetParent) {
|
||||
zonesRemove.classList.contains('pressed') ? zonesRemove.classList.remove('pressed') : zonesRemove.classList.add('pressed');
|
||||
}
|
||||
}
|
||||
|
||||
// trigger trash button click on "Delete" keypress
|
||||
function removeElementOnKey() {
|
||||
$('.dialog:visible .fastDelete').click();
|
||||
$("button:visible:contains('Remove')").click();
|
||||
}
|
||||
=======
|
||||
>>>>>>> master
|
||||
|
|
@ -7,8 +7,8 @@ function editHeightmap() {
|
|||
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
|
||||
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
|
||||
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p>
|
||||
<p>Please <span class="pseudoLink" onclick=saveMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>
|
||||
<p>Check out ${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization', 'wiki')} for guidance.</p>`;
|
||||
<p>Please <span class="pseudoLink" onclick=dowloadMap(); editHeightmap();>save the map</span> before editing the heightmap!</p>
|
||||
<p style="margin-bottom: 0">Check out ${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization", "wiki")} for guidance.</p>`;
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
|
|
@ -222,7 +222,7 @@ function editHeightmap() {
|
|||
Lakes.generateName();
|
||||
|
||||
Military.generate();
|
||||
addMarkers();
|
||||
Markers.generate();
|
||||
addZones();
|
||||
TIME && console.timeEnd('regenerateErasedData');
|
||||
INFO && console.groupEnd('Edit Heightmap');
|
||||
|
|
@ -334,10 +334,10 @@ function editHeightmap() {
|
|||
|
||||
for (const i of pack.cells.i) {
|
||||
const g = pack.cells.g[i];
|
||||
const land = pack.cells.h[i] >= 20;
|
||||
const isLand = pack.cells.h[i] >= 20;
|
||||
|
||||
// check biome
|
||||
pack.cells.biome[i] = land && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], pack.cells.h[i]);
|
||||
pack.cells.biome[i] = isLand && biome[g] ? biome[g] : getBiomeId(grid.cells.prec[g], grid.cells.temp[g], pack.cells.h[i]);
|
||||
|
||||
// rivers data
|
||||
if (!erosionAllowed) {
|
||||
|
|
@ -346,7 +346,7 @@ function editHeightmap() {
|
|||
pack.cells.fl[i] = fl[g];
|
||||
}
|
||||
|
||||
if (!land) continue;
|
||||
if (!isLand) continue;
|
||||
pack.cells.culture[i] = culture[g];
|
||||
pack.cells.pop[i] = pop[g];
|
||||
pack.cells.road[i] = road[g];
|
||||
|
|
@ -614,7 +614,7 @@ function editHeightmap() {
|
|||
const interpolate = d3.interpolateRound(power, 1);
|
||||
const land = changeOnlyLand.checked;
|
||||
function lim(v) {
|
||||
return Math.max(Math.min(v, 100), land ? 20 : 0);
|
||||
return minmax(v, land ? 20 : 0, 100);
|
||||
}
|
||||
const h = grid.cells.h;
|
||||
|
||||
|
|
@ -626,6 +626,8 @@ function editHeightmap() {
|
|||
else if (brush === 'brushAlign') s.forEach((i) => (h[i] = lim(h[start])));
|
||||
else if (brush === 'brushSmooth') s.forEach((i) => (h[i] = rn((d3.mean(grid.cells.c[i].filter((i) => (land ? h[i] >= 20 : 1)).map((c) => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1)));
|
||||
else if (brush === 'brushDisrupt') s.forEach((i) => (h[i] = h[i] < 15 ? h[i] : lim(h[i] + power / 1.6 - Math.random() * power)));
|
||||
i => (h[i] = rn((d3.mean(grid.cells.c[i].filter(i => (land ? h[i] >= 20 : 1)).map(c => h[c])) + h[i] * (10 - power) + 0.6) / (11 - power), 1))
|
||||
);
|
||||
|
||||
mockHeightmapSelection(s);
|
||||
// updateHistory(); uncomment to update history every step
|
||||
|
|
@ -775,6 +777,7 @@ function editHeightmap() {
|
|||
const TempX = `<span>x:<input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${arg4 || '15-85'}></span>`;
|
||||
const Height = `<span>h:<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${arg3 || '40-50'}></span>`;
|
||||
const Count = `<span>n:<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${count || '1-2'}></span>`;
|
||||
}></span>`;
|
||||
const blob = `${common}${TempY}${TempX}${Height}${Count}</div>`;
|
||||
|
||||
if (type === 'Hill' || type === 'Pit' || type === 'Range' || type === 'Trough') return blob;
|
||||
|
|
@ -792,6 +795,8 @@ function editHeightmap() {
|
|||
} min=0 max=10 step=.1></span></div>`;
|
||||
if (type === 'Smooth')
|
||||
return `${common}<span>f:<input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc." type="number" min=1 max=10 value=${count || 2}></span></div>`;
|
||||
count || 2
|
||||
}></span></div>`;
|
||||
}
|
||||
|
||||
function setRange(event) {
|
||||
|
|
@ -853,31 +858,27 @@ function editHeightmap() {
|
|||
const steps = body.querySelectorAll('#templateBody > div');
|
||||
if (!steps.length) return;
|
||||
|
||||
const {addHill, addPit, addRange, addTrough, addStrait, modify, smooth} = HeightmapGenerator;
|
||||
grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights
|
||||
|
||||
for (const s of steps) {
|
||||
if (s.style.opacity == 0.5) continue;
|
||||
const type = s.dataset.type;
|
||||
for (const step of steps) {
|
||||
if (step.style.opacity === "0.5") continue;
|
||||
const type = step.dataset.type;
|
||||
|
||||
const elCount = s.querySelector('.templateCount') || '';
|
||||
const elHeight = s.querySelector('.templateHeight') || '';
|
||||
const count = step.querySelector(".templateCount")?.value || "";
|
||||
const height = step.querySelector(".templateHeight")?.value || "";
|
||||
const dist = step.querySelector(".templateDist")?.value || null;
|
||||
const x = step.querySelector(".templateX")?.value || null;
|
||||
const y = step.querySelector(".templateY")?.value || null;
|
||||
|
||||
const elDist = s.querySelector('.templateDist');
|
||||
const dist = elDist ? elDist.value : null;
|
||||
|
||||
const templateX = s.querySelector('.templateX');
|
||||
const x = templateX ? templateX.value : null;
|
||||
const templateY = s.querySelector('.templateY');
|
||||
const y = templateY ? templateY.value : null;
|
||||
|
||||
if (type === 'Hill') HeightmapGenerator.addHill(elCount.value, elHeight.value, x, y);
|
||||
else if (type === 'Pit') HeightmapGenerator.addPit(elCount.value, elHeight.value, x, y);
|
||||
else if (type === 'Range') HeightmapGenerator.addRange(elCount.value, elHeight.value, x, y);
|
||||
else if (type === 'Trough') HeightmapGenerator.addTrough(elCount.value, elHeight.value, x, y);
|
||||
else if (type === 'Strait') HeightmapGenerator.addStrait(elCount.value, dist);
|
||||
else if (type === 'Add') HeightmapGenerator.modify(dist, +elCount.value, 1);
|
||||
else if (type === 'Multiply') HeightmapGenerator.modify(dist, 0, +elCount.value);
|
||||
else if (type === 'Smooth') HeightmapGenerator.smooth(+elCount.value);
|
||||
if (type === "Hill") addHill(count, height, x, y);
|
||||
else if (type === "Pit") addPit(count, height, x, y);
|
||||
else if (type === "Range") addRange(count, height, x, y);
|
||||
else if (type === "Trough") addTrough(count, height, x, y);
|
||||
else if (type === "Strait") addStrait(count, dist);
|
||||
else if (type === "Add") modify(dist, +count, 1);
|
||||
else if (type === "Multiply") modify(dist, 0, +count);
|
||||
else if (type === "Smooth") smooth(+count);
|
||||
|
||||
updateHistory('noStat'); // update history every step
|
||||
}
|
||||
|
|
@ -896,17 +897,13 @@ function editHeightmap() {
|
|||
|
||||
let data = '';
|
||||
for (const s of steps) {
|
||||
if (s.style.opacity == 0.5) continue;
|
||||
const type = s.getAttribute('data-type');
|
||||
if (s.style.opacity === "0.5") continue;
|
||||
|
||||
const elCount = s.querySelector('.templateCount');
|
||||
const count = elCount ? elCount.value : '0';
|
||||
const elHeight = s.querySelector('.templateHeight');
|
||||
const elDist = s.querySelector('.templateDist');
|
||||
const arg3 = elHeight ? elHeight.value : elDist ? elDist.value : '0';
|
||||
const templateX = s.querySelector('.templateX');
|
||||
const x = templateX ? templateX.value : '0';
|
||||
const templateY = s.querySelector('.templateY');
|
||||
const y = templateY ? templateY.value : '0';
|
||||
const count = s.querySelector(".templateCount")?.value || "0";
|
||||
const arg3 = s.querySelector(".templateHeight")?.value || s.querySelector(".templateDist")?.value || "0";
|
||||
const x = s.querySelector(".templateX")?.value || "0";
|
||||
const y = s.querySelector(".templateY")?.value || "0";
|
||||
data += `${type} ${count} ${arg3} ${x} ${y}\r\n`;
|
||||
}
|
||||
|
||||
|
|
@ -1194,10 +1191,14 @@ function editHeightmap() {
|
|||
}
|
||||
|
||||
function setConvertColorsNumber() {
|
||||
prompt(`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`, {default: +convertColors.value, step: 1, min: 3, max: 255}, (number) => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
});
|
||||
prompt(
|
||||
`Please set maximum number of colors. <br>An actual number is usually lower and depends on color scheme`,
|
||||
{default: +convertColors.value, step: 1, min: 3, max: 255},
|
||||
number => {
|
||||
convertColors.value = number;
|
||||
heightsFromImage(number);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function setOverlayOpacity(v) {
|
||||
|
|
|
|||
1452
modules/ui/heightmap-editor.js.orig
Normal file
1452
modules/ui/heightmap-editor.js.orig
Normal file
File diff suppressed because it is too large
Load diff
153
modules/ui/hotkeys.js
Normal file
153
modules/ui/hotkeys.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
'use strict';
|
||||
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
document.addEventListener('keyup', handleKeyup);
|
||||
|
||||
function handleKeydown(event) {
|
||||
const {code, ctrlKey, altKey} = event;
|
||||
if (altKey && !ctrlKey) event.preventDefault(); // disallow alt key combinations
|
||||
if (ctrlKey && ['KeyS', 'KeyC'].includes(code)) event.preventDefault(); // disallow CTRL + S and CTRL + C
|
||||
if (['F1', 'F2', 'F6', 'F9', 'Tab'].includes(code)) event.preventDefault(); // disallow default Fn and Tab
|
||||
}
|
||||
|
||||
function handleKeyup(event) {
|
||||
if (!modules.editors) return; // if editors are not loaded, do nothing
|
||||
|
||||
const {tagName, contentEditable} = document.activeElement;
|
||||
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(tagName)) return; // don't trigger if user inputs text
|
||||
if (tagName === 'DIV' && contentEditable === 'true') return; // don't trigger if user inputs a text
|
||||
if (document.getSelection().toString()) return; // don't trigger if user selects text
|
||||
event.stopPropagation();
|
||||
|
||||
const {code, key, ctrlKey, metaKey, shiftKey, altKey} = event;
|
||||
const ctrl = ctrlKey || metaKey || key === 'Control';
|
||||
const shift = shiftKey || key === 'Shift';
|
||||
const alt = altKey || key === 'Alt';
|
||||
|
||||
if (code === 'F1') showInfo();
|
||||
else if (code === 'F2') regeneratePrompt('hotkey');
|
||||
else if (code === 'F6') quickSave();
|
||||
else if (code === 'F9') quickLoad();
|
||||
else if (code === 'Tab') toggleOptions(event);
|
||||
else if (code === 'Escape') closeAllDialogs();
|
||||
else if (code === 'Delete') removeElementOnKey();
|
||||
else if (code === 'KeyO' && document.getElementById('canvas3d')) toggle3dOptions();
|
||||
else if (ctrl && code === 'KeyQ') toggleSaveReminder();
|
||||
else if (ctrl && code === 'KeyS') dowloadMap();
|
||||
else if (ctrl && code === 'KeyC') saveToDropbox();
|
||||
else if (ctrl && code === 'KeyZ' && undo.offsetParent) undo.click();
|
||||
else if (ctrl && code === 'KeyY' && redo.offsetParent) redo.click();
|
||||
else if (shift && code === 'KeyH') editHeightmap();
|
||||
else if (shift && code === 'KeyB') editBiomes();
|
||||
else if (shift && code === 'KeyS') editStates();
|
||||
else if (shift && code === 'KeyP') editProvinces();
|
||||
else if (shift && code === 'KeyD') editDiplomacy();
|
||||
else if (shift && code === 'KeyC') editCultures();
|
||||
else if (shift && code === 'KeyN') editNamesbase();
|
||||
else if (shift && code === 'KeyZ') editZones();
|
||||
else if (shift && code === 'KeyR') editReligions();
|
||||
else if (shift && code === 'KeyY') openEmblemEditor();
|
||||
else if (shift && code === 'KeyQ') editUnits();
|
||||
else if (shift && code === 'KeyO') editNotes();
|
||||
else if (shift && code === 'KeyT') overviewBurgs();
|
||||
else if (shift && code === 'KeyV') overviewRivers();
|
||||
else if (shift && code === 'KeyM') overviewMilitary();
|
||||
else if (shift && code === 'KeyK') overviewMarkers();
|
||||
else if (shift && code === 'KeyE') viewCellDetails();
|
||||
else if (key === '!') toggleAddBurg();
|
||||
else if (key === '@') toggleAddLabel();
|
||||
else if (key === '#') toggleAddRiver();
|
||||
else if (key === '$') toggleAddRoute();
|
||||
else if (key === '%') toggleAddMarker();
|
||||
else if (alt && code === 'KeyB') console.table(pack.burgs);
|
||||
else if (alt && code === 'KeyS') console.table(pack.states);
|
||||
else if (alt && code === 'KeyC') console.table(pack.cultures);
|
||||
else if (alt && code === 'KeyR') console.table(pack.religions);
|
||||
else if (alt && code === 'KeyF') console.table(pack.features);
|
||||
else if (code === 'KeyX') toggleTexture();
|
||||
else if (code === 'KeyH') toggleHeight();
|
||||
else if (code === 'KeyB') toggleBiomes();
|
||||
else if (code === 'KeyE') toggleCells();
|
||||
else if (code === 'KeyG') toggleGrid();
|
||||
else if (code === 'KeyO') toggleCoordinates();
|
||||
else if (code === 'KeyW') toggleCompass();
|
||||
else if (code === 'KeyV') toggleRivers();
|
||||
else if (code === 'KeyF') toggleRelief();
|
||||
else if (code === 'KeyC') toggleCultures();
|
||||
else if (code === 'KeyS') toggleStates();
|
||||
else if (code === 'KeyP') toggleProvinces();
|
||||
else if (code === 'KeyZ') toggleZones();
|
||||
else if (code === 'KeyD') toggleBorders();
|
||||
else if (code === 'KeyR') toggleReligions();
|
||||
else if (code === 'KeyU') toggleRoutes();
|
||||
else if (code === 'KeyT') toggleTemp();
|
||||
else if (code === 'KeyN') togglePopulation();
|
||||
else if (code === 'KeyJ') toggleIce();
|
||||
else if (code === 'KeyA') togglePrec();
|
||||
else if (code === 'KeyY') toggleEmblems();
|
||||
else if (code === 'KeyL') toggleLabels();
|
||||
else if (code === 'KeyI') toggleIcons();
|
||||
else if (code === 'KeyM') toggleMilitary();
|
||||
else if (code === 'KeyK') toggleMarkers();
|
||||
else if (code === 'Equal') toggleRulers();
|
||||
else if (code === 'Slash') toggleScaleBar();
|
||||
else if (code === 'ArrowLeft') zoom.translateBy(svg, 10, 0);
|
||||
else if (code === 'ArrowRight') zoom.translateBy(svg, -10, 0);
|
||||
else if (code === 'ArrowUp') zoom.translateBy(svg, 0, 10);
|
||||
else if (code === 'ArrowDown') zoom.translateBy(svg, 0, -10);
|
||||
else if (key === '+' || key === '-') pressNumpadSign(key);
|
||||
else if (key === '0') resetZoom(1000);
|
||||
else if (key === '1') zoom.scaleTo(svg, 1);
|
||||
else if (key === '2') zoom.scaleTo(svg, 2);
|
||||
else if (key === '3') zoom.scaleTo(svg, 3);
|
||||
else if (key === '4') zoom.scaleTo(svg, 4);
|
||||
else if (key === '5') zoom.scaleTo(svg, 5);
|
||||
else if (key === '6') zoom.scaleTo(svg, 6);
|
||||
else if (key === '7') zoom.scaleTo(svg, 7);
|
||||
else if (key === '8') zoom.scaleTo(svg, 8);
|
||||
else if (key === '9') zoom.scaleTo(svg, 9);
|
||||
else if (ctrl) toggleMode();
|
||||
}
|
||||
|
||||
function pressNumpadSign(key) {
|
||||
const change = key === '+' ? 1 : -1;
|
||||
let brush = null;
|
||||
|
||||
if (brushRadius.offsetParent) brush = document.getElementById('brushRadius');
|
||||
else if (biomesManuallyBrush.offsetParent) brush = document.getElementById('biomesManuallyBrush');
|
||||
else if (statesManuallyBrush.offsetParent) brush = document.getElementById('statesManuallyBrush');
|
||||
else if (provincesManuallyBrush.offsetParent) brush = document.getElementById('provincesManuallyBrush');
|
||||
else if (culturesManuallyBrush.offsetParent) brush = document.getElementById('culturesManuallyBrush');
|
||||
else if (zonesBrush.offsetParent) brush = document.getElementById('zonesBrush');
|
||||
else if (religionsManuallyBrush.offsetParent) brush = document.getElementById('religionsManuallyBrush');
|
||||
|
||||
if (brush) {
|
||||
const value = minmax(+brush.value + change, +brush.min, +brush.max);
|
||||
brush.value = document.getElementById(brush.id + 'Number').value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleBy = key === '+' ? 1.2 : 0.8;
|
||||
zoom.scaleBy(svg, scaleBy); // if no brush elements displayed, zoom map
|
||||
}
|
||||
|
||||
function toggleMode() {
|
||||
if (zonesRemove.offsetParent) {
|
||||
zonesRemove.classList.contains('pressed') ? zonesRemove.classList.remove('pressed') : zonesRemove.classList.add('pressed');
|
||||
}
|
||||
}
|
||||
|
||||
function removeElementOnKey() {
|
||||
const fastDelete = Array.from(document.querySelectorAll("[role='dialog'] .fastDelete")).find((dialog) => dialog.style.display !== 'none');
|
||||
if (fastDelete) fastDelete.click();
|
||||
|
||||
const visibleDialogs = Array.from(document.querySelectorAll("[role='dialog']")).filter((dialog) => dialog.style.display !== 'none');
|
||||
if (!visibleDialogs.length) return;
|
||||
|
||||
visibleDialogs.forEach((dialog) => dialog.querySelectorAll('button').forEach((button) => button.textContent === 'Remove' && button.click()));
|
||||
}
|
||||
|
||||
function closeAllDialogs() {
|
||||
closeDialogs();
|
||||
hideOptions();
|
||||
}
|
||||
|
|
@ -51,8 +51,8 @@ function editLabel() {
|
|||
|
||||
function showEditorTips() {
|
||||
showMainTip();
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr('id')) tip('Drag to shift the label');
|
||||
else if (d3.event.target.parentNode.id === 'controlPoints') {
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr("id")) tip("Drag to shift the label");
|
||||
else if (d3.event.target.parentNode.id === "controlPoints") {
|
||||
if (d3.event.target.tagName === 'circle') tip('Drag to move, click to delete the control point');
|
||||
if (d3.event.target.tagName === 'path') tip('Click to add a control point');
|
||||
}
|
||||
|
|
@ -253,7 +253,13 @@ function editLabel() {
|
|||
const message = `Are you sure you want to remove ${basic ? 'all elements in the group' : 'the entire label group'}?<br><br>Labels to be removed: ${count}`;
|
||||
const onConfirm = () => {
|
||||
$('#labelEditor').dialog('close');
|
||||
hideGroupSection();
|
||||
resizable: false,
|
||||
title: "Remove route group",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
$("#labelEditor").dialog("close");
|
||||
hideGroupSection();
|
||||
labels
|
||||
.select('#' + group)
|
||||
.selectAll('text')
|
||||
|
|
@ -357,10 +363,13 @@ function editLabel() {
|
|||
const message = 'Are you sure you want to remove the label? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
defs.select('#textPath_' + elSelected.attr('id')).remove();
|
||||
title: "Remove label",
|
||||
elSelected.remove();
|
||||
$('#labelEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove label', message, confirm: 'Remove', onConfirm});
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
|
||||
function closeLabelEditor() {
|
||||
|
|
|
|||
549
modules/ui/labels-editor.js.orig
Normal file
549
modules/ui/labels-editor.js.orig
Normal file
|
|
@ -0,0 +1,549 @@
|
|||
'use strict';
|
||||
function editLabel() {
|
||||
if (customization) return;
|
||||
closeDialogs();
|
||||
if (!layerIsOn('toggleLabels')) toggleLabels();
|
||||
|
||||
const tspan = d3.event.target;
|
||||
const textPath = tspan.parentNode;
|
||||
const text = textPath.parentNode;
|
||||
<<<<<<< HEAD
|
||||
elSelected = d3.select(text).call(d3.drag().on('start', dragLabel)).classed('draggable', true);
|
||||
viewbox.on('touchmove mousemove', showEditorTips);
|
||||
|
||||
$('#labelEditor').dialog({
|
||||
title: 'Edit Label',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'center top+10', at: 'bottom', of: text, collision: 'fit'},
|
||||
=======
|
||||
elSelected = d3.select(text).call(d3.drag().on("start", dragLabel)).classed("draggable", true);
|
||||
viewbox.on("touchmove mousemove", showEditorTips);
|
||||
|
||||
$("#labelEditor").dialog({
|
||||
title: "Edit Label",
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: "center top+10", at: "bottom", of: text, collision: "fit"},
|
||||
>>>>>>> master
|
||||
close: closeLabelEditor
|
||||
});
|
||||
|
||||
drawControlPointsAndLine();
|
||||
selectLabelGroup(text);
|
||||
updateValues(textPath);
|
||||
|
||||
if (modules.editLabel) return;
|
||||
modules.editLabel = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById('labelGroupShow').addEventListener('click', showGroupSection);
|
||||
document.getElementById('labelGroupHide').addEventListener('click', hideGroupSection);
|
||||
document.getElementById('labelGroupSelect').addEventListener('click', changeGroup);
|
||||
document.getElementById('labelGroupInput').addEventListener('change', createNewGroup);
|
||||
document.getElementById('labelGroupNew').addEventListener('click', toggleNewGroupInput);
|
||||
document.getElementById('labelGroupRemove').addEventListener('click', removeLabelsGroup);
|
||||
|
||||
document.getElementById('labelTextShow').addEventListener('click', showTextSection);
|
||||
document.getElementById('labelTextHide').addEventListener('click', hideTextSection);
|
||||
document.getElementById('labelText').addEventListener('input', changeText);
|
||||
document.getElementById('labelTextRandom').addEventListener('click', generateRandomName);
|
||||
|
||||
document.getElementById('labelEditStyle').addEventListener('click', editGroupStyle);
|
||||
|
||||
document.getElementById('labelSizeShow').addEventListener('click', showSizeSection);
|
||||
document.getElementById('labelSizeHide').addEventListener('click', hideSizeSection);
|
||||
document.getElementById('labelStartOffset').addEventListener('input', changeStartOffset);
|
||||
document.getElementById('labelRelativeSize').addEventListener('input', changeRelativeSize);
|
||||
|
||||
document.getElementById('labelAlign').addEventListener('click', editLabelAlign);
|
||||
document.getElementById('labelLegend').addEventListener('click', editLabelLegend);
|
||||
document.getElementById('labelRemoveSingle').addEventListener('click', removeLabel);
|
||||
|
||||
function showEditorTips() {
|
||||
showMainTip();
|
||||
<<<<<<< HEAD
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr('id')) tip('Drag to shift the label');
|
||||
else if (d3.event.target.parentNode.id === 'controlPoints') {
|
||||
if (d3.event.target.tagName === 'circle') tip('Drag to move, click to delete the control point');
|
||||
if (d3.event.target.tagName === 'path') tip('Click to add a control point');
|
||||
=======
|
||||
if (d3.event.target.parentNode.parentNode.id === elSelected.attr("id")) tip("Drag to shift the label");
|
||||
else if (d3.event.target.parentNode.id === "controlPoints") {
|
||||
if (d3.event.target.tagName === "circle") tip("Drag to move, click to delete the control point");
|
||||
if (d3.event.target.tagName === "path") tip("Click to add a control point");
|
||||
>>>>>>> master
|
||||
}
|
||||
}
|
||||
|
||||
function selectLabelGroup(text) {
|
||||
const group = text.parentNode.id;
|
||||
const select = document.getElementById('labelGroupSelect');
|
||||
select.options.length = 0; // remove all options
|
||||
|
||||
<<<<<<< HEAD
|
||||
labels.selectAll(':scope > g').each(function () {
|
||||
if (this.id === 'burgLabels') return;
|
||||
=======
|
||||
labels.selectAll(":scope > g").each(function () {
|
||||
if (this.id === "burgLabels") return;
|
||||
>>>>>>> master
|
||||
select.options.add(new Option(this.id, this.id, false, this.id === group));
|
||||
});
|
||||
}
|
||||
|
||||
function updateValues(textPath) {
|
||||
document.getElementById('labelText').value = [...textPath.querySelectorAll('tspan')].map((tspan) => tspan.textContent).join('|');
|
||||
document.getElementById('labelStartOffset').value = parseFloat(textPath.getAttribute('startOffset'));
|
||||
document.getElementById('labelRelativeSize').value = parseFloat(textPath.getAttribute('font-size'));
|
||||
}
|
||||
|
||||
function drawControlPointsAndLine() {
|
||||
debug.select('#controlPoints').remove();
|
||||
debug.append('g').attr('id', 'controlPoints').attr('transform', elSelected.attr('transform'));
|
||||
const path = document.getElementById('textPath_' + elSelected.attr('id'));
|
||||
debug.select('#controlPoints').append('path').attr('d', path.getAttribute('d')).on('click', addInterimControlPoint);
|
||||
const l = path.getTotalLength();
|
||||
if (!l) return;
|
||||
const increment = l / Math.max(Math.ceil(l / 200), 2);
|
||||
for (let i = 0; i <= l; i += increment) {
|
||||
addControlPoint(path.getPointAtLength(i));
|
||||
}
|
||||
}
|
||||
|
||||
function addControlPoint(point) {
|
||||
<<<<<<< HEAD
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.append('circle')
|
||||
.attr('cx', point.x)
|
||||
.attr('cy', point.y)
|
||||
.attr('r', 2.5)
|
||||
.attr('stroke-width', 0.8)
|
||||
.call(d3.drag().on('drag', dragControlPoint))
|
||||
.on('click', clickControlPoint);
|
||||
=======
|
||||
debug.select("#controlPoints").append("circle").attr("cx", point.x).attr("cy", point.y).attr("r", 2.5).attr("stroke-width", 0.8).call(d3.drag().on("drag", dragControlPoint)).on("click", clickControlPoint);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function dragControlPoint() {
|
||||
this.setAttribute('cx', d3.event.x);
|
||||
this.setAttribute('cy', d3.event.y);
|
||||
redrawLabelPath();
|
||||
}
|
||||
|
||||
function redrawLabelPath() {
|
||||
const path = document.getElementById('textPath_' + elSelected.attr('id'));
|
||||
lineGen.curve(d3.curveBundle.beta(1));
|
||||
const points = [];
|
||||
debug
|
||||
<<<<<<< HEAD
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
points.push([this.getAttribute('cx'), this.getAttribute('cy')]);
|
||||
=======
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.each(function () {
|
||||
points.push([this.getAttribute("cx"), this.getAttribute("cy")]);
|
||||
>>>>>>> master
|
||||
});
|
||||
const d = round(lineGen(points));
|
||||
path.setAttribute('d', d);
|
||||
debug.select('#controlPoints > path').attr('d', d);
|
||||
}
|
||||
|
||||
function clickControlPoint() {
|
||||
this.remove();
|
||||
redrawLabelPath();
|
||||
}
|
||||
|
||||
function addInterimControlPoint() {
|
||||
const point = d3.mouse(this);
|
||||
|
||||
const dists = [];
|
||||
debug
|
||||
<<<<<<< HEAD
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
const x = +this.getAttribute('cx');
|
||||
const y = +this.getAttribute('cy');
|
||||
=======
|
||||
.select("#controlPoints")
|
||||
.selectAll("circle")
|
||||
.each(function () {
|
||||
const x = +this.getAttribute("cx");
|
||||
const y = +this.getAttribute("cy");
|
||||
>>>>>>> master
|
||||
dists.push((point[0] - x) ** 2 + (point[1] - y) ** 2);
|
||||
});
|
||||
|
||||
let index = dists.length;
|
||||
if (dists.length > 1) {
|
||||
const sorted = dists.slice(0).sort((a, b) => a - b);
|
||||
const closest = dists.indexOf(sorted[0]);
|
||||
const next = dists.indexOf(sorted[1]);
|
||||
if (closest <= next) index = closest + 1;
|
||||
else index = next + 1;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
const before = ':nth-child(' + (index + 2) + ')';
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.insert('circle', before)
|
||||
.attr('cx', point[0])
|
||||
.attr('cy', point[1])
|
||||
.attr('r', 2.5)
|
||||
.attr('stroke-width', 0.8)
|
||||
.call(d3.drag().on('drag', dragControlPoint))
|
||||
.on('click', clickControlPoint);
|
||||
=======
|
||||
const before = ":nth-child(" + (index + 2) + ")";
|
||||
debug.select("#controlPoints").insert("circle", before).attr("cx", point[0]).attr("cy", point[1]).attr("r", 2.5).attr("stroke-width", 0.8).call(d3.drag().on("drag", dragControlPoint)).on("click", clickControlPoint);
|
||||
>>>>>>> master
|
||||
|
||||
redrawLabelPath();
|
||||
}
|
||||
|
||||
function dragLabel() {
|
||||
<<<<<<< HEAD
|
||||
const tr = parseTransform(elSelected.attr('transform'));
|
||||
const dx = +tr[0] - d3.event.x,
|
||||
dy = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const x = d3.event.x,
|
||||
y = d3.event.y;
|
||||
const transform = `translate(${dx + x},${dy + y})`;
|
||||
elSelected.attr('transform', transform);
|
||||
debug.select('#controlPoints').attr('transform', transform);
|
||||
=======
|
||||
const tr = parseTransform(elSelected.attr("transform"));
|
||||
const dx = +tr[0] - d3.event.x,
|
||||
dy = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
const x = d3.event.x,
|
||||
y = d3.event.y;
|
||||
const transform = `translate(${dx + x},${dy + y})`;
|
||||
elSelected.attr("transform", transform);
|
||||
debug.select("#controlPoints").attr("transform", transform);
|
||||
>>>>>>> master
|
||||
});
|
||||
}
|
||||
|
||||
function showGroupSection() {
|
||||
<<<<<<< HEAD
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('labelGroupSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideGroupSection() {
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('labelGroupSection').style.display = 'none';
|
||||
document.getElementById('labelGroupInput').style.display = 'none';
|
||||
document.getElementById('labelGroupInput').value = '';
|
||||
document.getElementById('labelGroupSelect').style.display = 'inline-block';
|
||||
=======
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("labelGroupSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideGroupSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("labelGroupSection").style.display = "none";
|
||||
document.getElementById("labelGroupInput").style.display = "none";
|
||||
document.getElementById("labelGroupInput").value = "";
|
||||
document.getElementById("labelGroupSelect").style.display = "inline-block";
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
document.getElementById(this.value).appendChild(elSelected.node());
|
||||
}
|
||||
|
||||
function toggleNewGroupInput() {
|
||||
if (labelGroupInput.style.display === 'none') {
|
||||
labelGroupInput.style.display = 'inline-block';
|
||||
labelGroupInput.focus();
|
||||
labelGroupSelect.style.display = 'none';
|
||||
} else {
|
||||
<<<<<<< HEAD
|
||||
labelGroupInput.style.display = 'none';
|
||||
labelGroupSelect.style.display = 'inline-block';
|
||||
=======
|
||||
labelGroupInput.style.display = "none";
|
||||
labelGroupSelect.style.display = "inline-block";
|
||||
>>>>>>> master
|
||||
}
|
||||
}
|
||||
|
||||
function createNewGroup() {
|
||||
if (!this.value) {
|
||||
<<<<<<< HEAD
|
||||
tip('Please provide a valid group name');
|
||||
=======
|
||||
tip("Please provide a valid group name");
|
||||
>>>>>>> master
|
||||
return;
|
||||
}
|
||||
const group = this.value
|
||||
.toLowerCase()
|
||||
<<<<<<< HEAD
|
||||
.replace(/ /g, '_')
|
||||
.replace(/[^\w\s]/gi, '');
|
||||
=======
|
||||
.replace(/ /g, "_")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
>>>>>>> master
|
||||
|
||||
if (document.getElementById(group)) {
|
||||
tip('Element with this id already exists. Please provide a unique name', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number.isFinite(+group.charAt(0))) {
|
||||
tip('Group name should start with a letter', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// just rename if only 1 element left
|
||||
const oldGroup = elSelected.node().parentNode;
|
||||
if (oldGroup !== 'states' && oldGroup !== 'addedLabels' && oldGroup.childElementCount === 1) {
|
||||
document.getElementById('labelGroupSelect').selectedOptions[0].remove();
|
||||
document.getElementById('labelGroupSelect').options.add(new Option(group, group, false, true));
|
||||
oldGroup.id = group;
|
||||
toggleNewGroupInput();
|
||||
document.getElementById('labelGroupInput').value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const newGroup = elSelected.node().parentNode.cloneNode(false);
|
||||
document.getElementById('labels').appendChild(newGroup);
|
||||
newGroup.id = group;
|
||||
document.getElementById('labelGroupSelect').options.add(new Option(group, group, false, true));
|
||||
document.getElementById(group).appendChild(elSelected.node());
|
||||
|
||||
toggleNewGroupInput();
|
||||
document.getElementById('labelGroupInput').value = '';
|
||||
}
|
||||
|
||||
function removeLabelsGroup() {
|
||||
const group = elSelected.node().parentNode.id;
|
||||
const basic = group === 'states' || group === 'addedLabels';
|
||||
const count = elSelected.node().parentNode.childElementCount;
|
||||
<<<<<<< HEAD
|
||||
|
||||
const message = `Are you sure you want to remove ${basic ? 'all elements in the group' : 'the entire label group'}?<br><br>Labels to be removed: ${count}`;
|
||||
const onConfirm = () => {
|
||||
$('#labelEditor').dialog('close');
|
||||
hideGroupSection();
|
||||
labels
|
||||
.select('#' + group)
|
||||
.selectAll('text')
|
||||
.each(function () {
|
||||
document.getElementById('textPath_' + this.id).remove();
|
||||
this.remove();
|
||||
});
|
||||
if (!basic) labels.select('#' + group).remove();
|
||||
};
|
||||
confirmationDialog({title: 'Remove label group', message, confirm: 'Remove', onConfirm});
|
||||
}
|
||||
|
||||
function showTextSection() {
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('labelTextSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideTextSection() {
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('labelTextSection').style.display = 'none';
|
||||
}
|
||||
|
||||
function changeText() {
|
||||
const input = document.getElementById('labelText').value;
|
||||
const el = elSelected.select('textPath').node();
|
||||
const example = d3.select(elSelected.node().parentNode).append('text').attr('x', 0).attr('x', 0).attr('font-size', el.getAttribute('font-size')).node();
|
||||
=======
|
||||
alertMessage.innerHTML = `Are you sure you want to remove
|
||||
${basic ? "all elements in the group" : "the entire label group"}?
|
||||
<br><br>Labels to be removed: ${count}`;
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove route group",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
$("#labelEditor").dialog("close");
|
||||
hideGroupSection();
|
||||
labels
|
||||
.select("#" + group)
|
||||
.selectAll("text")
|
||||
.each(function () {
|
||||
document.getElementById("textPath_" + this.id).remove();
|
||||
this.remove();
|
||||
});
|
||||
if (!basic) labels.select("#" + group).remove();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showTextSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("labelTextSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideTextSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("labelTextSection").style.display = "none";
|
||||
}
|
||||
|
||||
function changeText() {
|
||||
const input = document.getElementById("labelText").value;
|
||||
const el = elSelected.select("textPath").node();
|
||||
const example = d3.select(elSelected.node().parentNode).append("text").attr("x", 0).attr("x", 0).attr("font-size", el.getAttribute("font-size")).node();
|
||||
>>>>>>> master
|
||||
|
||||
const lines = input.split('|');
|
||||
const top = (lines.length - 1) / -2; // y offset
|
||||
const inner = lines
|
||||
.map((l, d) => {
|
||||
example.innerHTML = l;
|
||||
const left = example.getBBox().width / -2; // x offset
|
||||
return `<tspan x="${left}px" dy="${d ? 1 : top}em">${l}</tspan>`;
|
||||
})
|
||||
<<<<<<< HEAD
|
||||
.join('');
|
||||
=======
|
||||
.join("");
|
||||
>>>>>>> master
|
||||
|
||||
el.innerHTML = inner;
|
||||
example.remove();
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (elSelected.attr('id').slice(0, 10) === 'stateLabel') tip('Use States Editor to change an actual state name, not just a label', false, 'warning');
|
||||
}
|
||||
|
||||
function generateRandomName() {
|
||||
let name = '';
|
||||
if (elSelected.attr('id').slice(0, 10) === 'stateLabel') {
|
||||
const id = +elSelected.attr('id').slice(10);
|
||||
=======
|
||||
if (elSelected.attr("id").slice(0, 10) === "stateLabel") tip("Use States Editor to change an actual state name, not just a label", false, "warning");
|
||||
}
|
||||
|
||||
function generateRandomName() {
|
||||
let name = "";
|
||||
if (elSelected.attr("id").slice(0, 10) === "stateLabel") {
|
||||
const id = +elSelected.attr("id").slice(10);
|
||||
>>>>>>> master
|
||||
const culture = pack.states[id].culture;
|
||||
name = Names.getState(Names.getCulture(culture, 4, 7, ''), culture);
|
||||
} else {
|
||||
const box = elSelected.node().getBBox();
|
||||
const cell = findCell((box.x + box.width) / 2, (box.y + box.height) / 2);
|
||||
const culture = pack.cells.culture[cell];
|
||||
name = Names.getCulture(culture);
|
||||
}
|
||||
document.getElementById('labelText').value = name;
|
||||
changeText();
|
||||
}
|
||||
|
||||
function editGroupStyle() {
|
||||
const g = elSelected.node().parentNode.id;
|
||||
editStyle('labels', g);
|
||||
}
|
||||
|
||||
function showSizeSection() {
|
||||
<<<<<<< HEAD
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('labelSizeSection').style.display = 'inline-block';
|
||||
}
|
||||
|
||||
function hideSizeSection() {
|
||||
document.querySelectorAll('#labelEditor > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('labelSizeSection').style.display = 'none';
|
||||
=======
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("labelSizeSection").style.display = "inline-block";
|
||||
}
|
||||
|
||||
function hideSizeSection() {
|
||||
document.querySelectorAll("#labelEditor > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("labelSizeSection").style.display = "none";
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function changeStartOffset() {
|
||||
elSelected.select('textPath').attr('startOffset', this.value + '%');
|
||||
tip('Label offset: ' + this.value + '%');
|
||||
}
|
||||
|
||||
function changeRelativeSize() {
|
||||
elSelected.select('textPath').attr('font-size', this.value + '%');
|
||||
tip('Label relative size: ' + this.value + '%');
|
||||
changeText();
|
||||
}
|
||||
|
||||
function editLabelAlign() {
|
||||
const bbox = elSelected.node().getBBox();
|
||||
const c = [bbox.x + bbox.width / 2, bbox.y + bbox.height / 2];
|
||||
<<<<<<< HEAD
|
||||
const path = defs.select('#textPath_' + elSelected.attr('id'));
|
||||
path.attr('d', `M${c[0] - bbox.width},${c[1]}h${bbox.width * 2}`);
|
||||
=======
|
||||
const path = defs.select("#textPath_" + elSelected.attr("id"));
|
||||
path.attr("d", `M${c[0] - bbox.width},${c[1]}h${bbox.width * 2}`);
|
||||
>>>>>>> master
|
||||
drawControlPointsAndLine();
|
||||
}
|
||||
|
||||
function editLabelLegend() {
|
||||
const id = elSelected.attr('id');
|
||||
const name = elSelected.text();
|
||||
editNotes(id, name);
|
||||
}
|
||||
|
||||
function removeLabel() {
|
||||
<<<<<<< HEAD
|
||||
const message = 'Are you sure you want to remove the label? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
defs.select('#textPath_' + elSelected.attr('id')).remove();
|
||||
elSelected.remove();
|
||||
$('#labelEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove label', message, confirm: 'Remove', onConfirm});
|
||||
=======
|
||||
alertMessage.innerHTML = "Are you sure you want to remove the label?";
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
title: "Remove label",
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog("close");
|
||||
defs.select("#textPath_" + elSelected.attr("id")).remove();
|
||||
elSelected.remove();
|
||||
$("#labelEditor").dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function closeLabelEditor() {
|
||||
debug.select('#controlPoints').remove();
|
||||
unselect();
|
||||
}
|
||||
}
|
||||
|
|
@ -48,8 +48,7 @@ function changePreset(preset) {
|
|||
.querySelectorAll('li')
|
||||
.forEach(function (e) {
|
||||
if (layers.includes(e.id) && !layerIsOn(e.id)) e.click();
|
||||
// turn on
|
||||
else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click(); // turn off
|
||||
else if (!layers.includes(e.id) && layerIsOn(e.id)) e.click();
|
||||
});
|
||||
layersPreset.value = preset;
|
||||
localStorage.setItem('preset', preset);
|
||||
|
|
@ -122,6 +121,7 @@ function restoreLayers() {
|
|||
if (layerIsOn('toggleReligions')) drawReligions();
|
||||
if (layerIsOn('toggleIce')) drawIce();
|
||||
if (layerIsOn('toggleEmblems')) drawEmblems();
|
||||
if (layerIsOn('toggleMarkers')) drawMarkers();
|
||||
|
||||
// some layers are rendered each time, remove them if they are not on
|
||||
if (!layerIsOn('toggleBorders')) borders.selectAll('path').remove();
|
||||
|
|
@ -1419,8 +1419,8 @@ function toggleTexture(event) {
|
|||
turnButtonOn('toggleTexture');
|
||||
// append default texture image selected by default. Don't append on load to not harm performance
|
||||
if (!texture.selectAll('*').size()) {
|
||||
const x = +styleTextureShiftX.value,
|
||||
y = +styleTextureShiftY.value;
|
||||
const x = +styleTextureShiftX.value;
|
||||
const y = +styleTextureShiftY.value;
|
||||
const image = texture
|
||||
.append('image')
|
||||
.attr('id', 'textureImage')
|
||||
|
|
@ -1430,16 +1430,13 @@ function toggleTexture(event) {
|
|||
.attr('height', graphHeight - y)
|
||||
.attr('xlink:href', getDefaultTexture())
|
||||
.attr('preserveAspectRatio', 'xMidYMid slice');
|
||||
if (styleTextureInput.value !== 'default') getBase64(styleTextureInput.value, (base64) => image.attr('xlink:href', base64));
|
||||
getBase64(styleTextureInput.value, (base64) => image.attr('xlink:href', base64));
|
||||
}
|
||||
$('#texture').fadeIn();
|
||||
zoom.scaleBy(svg, 1.00001); // enforce browser re-draw
|
||||
if (event && isCtrlClick(event)) editStyle('texture');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('texture');
|
||||
return;
|
||||
}
|
||||
if (event && isCtrlClick(event)) return editStyle('texture');
|
||||
$('#texture').fadeOut();
|
||||
turnButtonOff('toggleTexture');
|
||||
}
|
||||
|
|
@ -1459,14 +1456,16 @@ function toggleRivers(event) {
|
|||
|
||||
function drawRivers() {
|
||||
TIME && console.time('drawRivers');
|
||||
rivers.selectAll('*').remove();
|
||||
|
||||
const {addMeandering, getRiverPath} = Rivers;
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const riverPaths = pack.rivers.map((river) => {
|
||||
const meanderedPoints = addMeandering(river.cells, river.points);
|
||||
const widthFactor = river.widthFactor || 1;
|
||||
const startingWidth = river.sourceWidth || 0;
|
||||
const path = getRiverPath(meanderedPoints, widthFactor, startingWidth);
|
||||
return `<path id="river${river.i}" d="${path}"/>`;
|
||||
|
||||
const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => {
|
||||
if (!cells || cells.length < 2) return;
|
||||
const meanderedPoints = addMeandering(cells, points);
|
||||
const path = getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||
return `<path id="river${i}" d="${path}"/>`;
|
||||
});
|
||||
rivers.html(riverPaths.join(''));
|
||||
TIME && console.timeEnd('drawRivers');
|
||||
|
|
@ -1505,18 +1504,51 @@ function toggleMilitary() {
|
|||
function toggleMarkers(event) {
|
||||
if (!layerIsOn('toggleMarkers')) {
|
||||
turnButtonOn('toggleMarkers');
|
||||
$('#markers').fadeIn();
|
||||
drawMarkers();
|
||||
if (event && isCtrlClick(event)) editStyle('markers');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('markers');
|
||||
return;
|
||||
}
|
||||
$('#markers').fadeOut();
|
||||
if (event && isCtrlClick(event)) return editStyle('markers');
|
||||
markers.selectAll('*').remove();
|
||||
turnButtonOff('toggleMarkers');
|
||||
}
|
||||
}
|
||||
|
||||
function drawMarkers() {
|
||||
const rescale = +markers.attr('rescale');
|
||||
const pinned = +markers.attr('pinned');
|
||||
|
||||
const markersData = pinned ? pack.markers.filter(({pinned}) => pinned) : pack.markers;
|
||||
const html = markersData.map((marker) => drawMarker(marker, rescale));
|
||||
markers.html(html.join(''));
|
||||
}
|
||||
|
||||
const getPin = (shape = 'bubble', fill = '#fff', stroke = '#000') => {
|
||||
if (shape === 'bubble') return `<path d="M6,19 l9,10 L24,19" fill="${stroke}" stroke="none" /><circle cx="15" cy="15" r="10" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === 'pin') return `<path d="m 15,3 c -5.5,0 -9.7,4.09 -9.7,9.3 0,6.8 9.7,17 9.7,17 0,0 9.7,-10.2 9.7,-17 C 24.7,7.09 20.5,3 15,3 Z" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === 'square') return `<path d="m 20,25 -5,4 -5,-4 z" fill="${stroke}"/><path d="M 5,5 H 25 V 25 H 5 Z" fill="${fill}" stroke="${stroke}"/>`;
|
||||
if (shape === 'squarish') return `<path d="m 5,5 h 20 v 20 h -6 l -4,4 -4,-4 H 5 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'diamond') return `<path d="M 2,15 15,1 28,15 15,29 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'hex') return `<path d="M 15,29 4.61,21 V 9 L 15,3 25.4,9 v 12 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'hexy') return `<path d="M 15,29 6,21 5,8 15,4 25,8 24,21 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'shieldy') return `<path d="M 15,29 6,21 5,7 c 0,0 5,-3 10,-3 5,0 10,3 10,3 l -1,14 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'shield') return `<path d="M 4.6,5.2 H 25 v 6.7 A 20.3,20.4 0 0 1 15,29 20.3,20.4 0 0 1 4.6,11.9 Z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'pentagon') return `<path d="M 4,16 9,4 h 12 l 5,12 -11,13 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'heptagon') return `<path d="M 15,29 6,22 4,12 10,4 h 10 l 6,8 -2,10 z" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'circle') return `<circle cx="15" cy="15" r="11" fill="${fill}" stroke="${stroke}" />`;
|
||||
if (shape === 'no') return '';
|
||||
};
|
||||
|
||||
function drawMarker(marker, rescale = 1) {
|
||||
const {i, icon, x, y, dx = 50, dy = 50, px = 12, size = 30, pin, fill, stroke} = marker;
|
||||
const id = `marker${i}`;
|
||||
const zoomSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
const viewX = rn(x - zoomSize / 2, 1);
|
||||
const viewY = rn(y - zoomSize, 1);
|
||||
const pinHTML = getPin(pin, fill, stroke);
|
||||
|
||||
return `<svg id="${id}" viewbox="0 0 30 30" width="${zoomSize}" height="${zoomSize}" x="${viewX}" y="${viewY}"><g>${pinHTML}</g><text x="${dx}%" y="${dy}%" font-size="${px}px" >${icon}</text></svg>`;
|
||||
}
|
||||
|
||||
function toggleLabels(event) {
|
||||
if (!layerIsOn('toggleLabels')) {
|
||||
turnButtonOn('toggleLabels');
|
||||
|
|
@ -1620,21 +1652,21 @@ function drawEmblems() {
|
|||
const validBurgs = burgs.filter((b) => b.i && !b.removed && b.coa && b.coaSize != 0);
|
||||
|
||||
const getStateEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 40, 10), 100);
|
||||
const startSize = minmax((graphHeight + graphWidth) / 40, 10, 100);
|
||||
const statesMod = 1 + validStates.length / 100 - (15 - validStates.length) / 200; // states number modifier
|
||||
const sizeMod = +document.getElementById('emblemsStateSizeInput').value || 1;
|
||||
return rn((startSize / statesMod) * sizeMod); // target size ~50px on 1536x754 map with 15 states
|
||||
};
|
||||
|
||||
const getProvinceEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 100, 5), 70);
|
||||
const startSize = minmax((graphHeight + graphWidth) / 100, 5, 70);
|
||||
const provincesMod = 1 + validProvinces.length / 1000 - (115 - validProvinces.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById('emblemsProvinceSizeInput').value || 1;
|
||||
return rn((startSize / provincesMod) * sizeMod); // target size ~20px on 1536x754 map with 115 provinces
|
||||
};
|
||||
|
||||
const getBurgEmblemSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 185, 2), 50);
|
||||
const startSize = minmax((graphHeight + graphWidth) / 185, 2, 50);
|
||||
const burgsMod = 1 + validBurgs.length / 1000 - (450 - validBurgs.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById('emblemsBurgSizeInput').value || 1;
|
||||
return rn((startSize / burgsMod) * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs
|
||||
|
|
@ -1685,11 +1717,9 @@ function drawEmblems() {
|
|||
|
||||
const burgNodes = nodes.filter((node) => node.type === 'burg');
|
||||
const burgString = burgNodes.map((d) => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join('');
|
||||
emblems.select('#burgEmblems').attr('font-size', sizeBurgs).html(burgString);
|
||||
|
||||
const provinceNodes = nodes.filter((node) => node.type === 'province');
|
||||
const provinceString = provinceNodes.map((d) => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join('');
|
||||
emblems.select('#provinceEmblems').attr('font-size', sizeProvinces).html(provinceString);
|
||||
|
||||
const stateNodes = nodes.filter((node) => node.type === 'state');
|
||||
const stateString = stateNodes.map((d) => `<use data-i="${d.i}" x="${rn(d.x - d.shift)}" y="${rn(d.y - d.shift)}" width="${d.size}em" height="${d.size}em"/>`).join('');
|
||||
|
|
|
|||
1970
modules/ui/layers.js.orig
Normal file
1970
modules/ui/layers.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,291 +1,261 @@
|
|||
'use strict';
|
||||
function editMarker() {
|
||||
function editMarker(markerI) {
|
||||
if (customization) return;
|
||||
closeDialogs('#markerEditor, .stable');
|
||||
$('#markerEditor').dialog();
|
||||
closeDialogs(".stable");
|
||||
|
||||
const [element, marker] = getElement(markerI, d3.event);
|
||||
if (!marker || !element) return;
|
||||
|
||||
elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true);
|
||||
|
||||
if (document.getElementById("notesEditor").offsetParent) editNotes(element.id, element.id);
|
||||
|
||||
// dom elements
|
||||
const markerType = document.getElementById("markerType");
|
||||
const markerIcon = document.getElementById("markerIcon");
|
||||
const markerIconSelect = document.getElementById("markerIconSelect");
|
||||
const markerIconSize = document.getElementById("markerIconSize");
|
||||
const markerIconShiftX = document.getElementById("markerIconShiftX");
|
||||
const markerIconShiftY = document.getElementById("markerIconShiftY");
|
||||
const markerSize = document.getElementById("markerSize");
|
||||
const markerPin = document.getElementById("markerPin");
|
||||
const markerFill = document.getElementById("markerFill");
|
||||
const markerStroke = document.getElementById("markerStroke");
|
||||
|
||||
const markerNotes = document.getElementById("markerNotes");
|
||||
const markerLock = document.getElementById("markerLock");
|
||||
const addMarker = document.getElementById("addMarker");
|
||||
const markerAdd = document.getElementById("markerAdd");
|
||||
const markerRemove = document.getElementById("markerRemove");
|
||||
|
||||
elSelected = d3.select(d3.event.target).call(d3.drag().on('start', dragMarker)).classed('draggable', true);
|
||||
updateInputs();
|
||||
|
||||
if (modules.editMarker) return;
|
||||
modules.editMarker = true;
|
||||
|
||||
$('#markerEditor').dialog({
|
||||
title: 'Edit Marker',
|
||||
title: "Edit Marker",
|
||||
resizable: false,
|
||||
position: {my: 'center top+30', at: 'bottom', of: d3.event, collision: 'fit'},
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
|
||||
close: closeMarkerEditor
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById('markerGroup').addEventListener('click', toggleGroupSection);
|
||||
document.getElementById('markerAddGroup').addEventListener('click', toggleGroupInput);
|
||||
document.getElementById('markerSelectGroup').addEventListener('change', changeGroup);
|
||||
document.getElementById('markerInputGroup').addEventListener('change', createGroup);
|
||||
document.getElementById('markerRemoveGroup').addEventListener('click', removeGroup);
|
||||
const listeners = [
|
||||
listen(markerType, "change", changeMarkerType),
|
||||
listen(markerIcon, "input", changeMarkerIcon),
|
||||
listen(markerIconSelect, "click", selectMarkerIcon),
|
||||
listen(markerIconSize, "input", changeIconSize),
|
||||
listen(markerIconShiftX, "input", changeIconShiftX),
|
||||
listen(markerIconShiftY, "input", changeIconShiftY),
|
||||
listen(markerSize, "input", changeMarkerSize),
|
||||
listen(markerPin, "change", changeMarkerPin),
|
||||
listen(markerFill, "input", changePinFill),
|
||||
listen(markerStroke, "input", changePinStroke),
|
||||
listen(markerNotes, "click", editMarkerLegend),
|
||||
listen(markerLock, "click", toggleMarkerLock),
|
||||
listen(markerAdd, "click", toggleAddMarker),
|
||||
listen(markerRemove, "click", confirmMarkerDeletion)
|
||||
];
|
||||
|
||||
document.getElementById('markerIcon').addEventListener('click', toggleIconSection);
|
||||
document.getElementById('markerIconSize').addEventListener('input', changeIconSize);
|
||||
document.getElementById('markerIconShiftX').addEventListener('input', changeIconShiftX);
|
||||
document.getElementById('markerIconShiftY').addEventListener('input', changeIconShiftY);
|
||||
document.getElementById('markerIconSelect').addEventListener('click', selectMarkerIcon);
|
||||
function getElement(markerI, event) {
|
||||
if (event) {
|
||||
const element = event.target?.closest("svg");
|
||||
const marker = pack.markers.find(({i}) => Number(element.id.slice(6)) === i);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
document.getElementById('markerStyle').addEventListener('click', toggleStyleSection);
|
||||
document.getElementById('markerSize').addEventListener('input', changeMarkerSize);
|
||||
document.getElementById('markerBaseStroke').addEventListener('input', changePinStroke);
|
||||
document.getElementById('markerBaseFill').addEventListener('input', changePinFill);
|
||||
document.getElementById('markerIconStrokeWidth').addEventListener('input', changeIconStrokeWidth);
|
||||
document.getElementById('markerIconStroke').addEventListener('input', changeIconStroke);
|
||||
document.getElementById('markerIconFill').addEventListener('input', changeIconFill);
|
||||
const element = document.getElementById(`marker${markerI}`);
|
||||
const marker = pack.markers.find(({i}) => i === markerI);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
document.getElementById('markerToggleBubble').addEventListener('click', togglePinVisibility);
|
||||
document.getElementById('markerLegendButton').addEventListener('click', editMarkerLegend);
|
||||
document.getElementById('markerAdd').addEventListener('click', toggleAddMarker);
|
||||
document.getElementById('markerRemove').addEventListener('click', removeMarker);
|
||||
|
||||
updateGroupOptions();
|
||||
function getSameTypeMarkers() {
|
||||
const currentType = marker.type;
|
||||
if (!currentType) return [marker];
|
||||
return pack.markers.filter(({type}) => type === currentType);
|
||||
}
|
||||
|
||||
function dragMarker() {
|
||||
const tr = parseTransform(this.getAttribute('transform'));
|
||||
const x = +tr[0] - d3.event.x,
|
||||
y = +tr[1] - d3.event.y;
|
||||
const dx = +this.getAttribute("x") - d3.event.x;
|
||||
const dy = +this.getAttribute("y") - d3.event.y;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const transform = `translate(${x + d3.event.x},${y + d3.event.y})`;
|
||||
this.setAttribute('transform', transform);
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", dx + x);
|
||||
this.setAttribute("y", dy + y);
|
||||
});
|
||||
|
||||
d3.event.on("end", function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", rn(dx + x, 2));
|
||||
this.setAttribute("y", rn(dy + y, 2));
|
||||
|
||||
const size = marker.size || 30;
|
||||
const zoomSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
|
||||
|
||||
marker.x = rn(x + dx + zoomSize / 2, 1);
|
||||
marker.y = rn(y + dy + zoomSize, 1);
|
||||
marker.cell = findCell(marker.x, marker.y);
|
||||
.selectAll('symbol')
|
||||
.each(function () {
|
||||
});
|
||||
}
|
||||
|
||||
function updateInputs() {
|
||||
const id = elSelected.attr('data-id');
|
||||
const symbol = d3.select('#defs-markers').select(id);
|
||||
const icon = symbol.select('text');
|
||||
const {icon, type = "", size = 30, dx = 50, dy = 50, px = 12, stroke = "#000000", fill = "#ffffff", pin = "bubble", lock} = marker;
|
||||
|
||||
markerSelectGroup.value = id.slice(1);
|
||||
markerIconSize.value = parseFloat(icon.attr('font-size'));
|
||||
markerIconShiftX.value = parseFloat(icon.attr('x'));
|
||||
markerIconShiftY.value = parseFloat(icon.attr('y'));
|
||||
markerType.value = type;
|
||||
markerIcon.value = icon;
|
||||
markerIconSize.value = px;
|
||||
markerIconShiftX.value = dx;
|
||||
markerIconShiftY.value = dy;
|
||||
markerSize.value = size;
|
||||
markerPin.value = pin;
|
||||
markerFill.value = fill;
|
||||
markerStroke.value = stroke;
|
||||
|
||||
markerSize.value = elSelected.attr('data-size');
|
||||
markerBaseStroke.value = symbol.select('path').attr('fill');
|
||||
markerBaseFill.value = symbol.select('circle').attr('fill');
|
||||
|
||||
markerIconStrokeWidth.value = icon.attr('stroke-width');
|
||||
markerIconStroke.value = icon.attr('stroke');
|
||||
markerIconFill.value = icon.attr('fill');
|
||||
|
||||
markerToggleBubble.className = symbol.select('circle').attr('opacity') === '0' ? 'icon-info' : 'icon-info-circled';
|
||||
markerIconSelect.innerHTML = icon.text();
|
||||
}
|
||||
|
||||
function toggleGroupSection() {
|
||||
if (markerGroupSection.style.display === 'inline-block') {
|
||||
markerEditor.querySelectorAll('button:not(#markerGroup)').forEach((b) => (b.style.display = 'inline-block'));
|
||||
markerGroupSection.style.display = 'none';
|
||||
} else {
|
||||
markerEditor.querySelectorAll('button:not(#markerGroup)').forEach((b) => (b.style.display = 'none'));
|
||||
markerGroupSection.style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
|
||||
function updateGroupOptions() {
|
||||
markerSelectGroup.innerHTML = '';
|
||||
d3.select('#defs-markers')
|
||||
.selectAll('symbol')
|
||||
.each(function () {
|
||||
markerSelectGroup.options.add(new Option(this.id, this.id));
|
||||
});
|
||||
markerSelectGroup.value = elSelected.attr('data-id').slice(1);
|
||||
}
|
||||
|
||||
function toggleGroupInput() {
|
||||
if (markerInputGroup.style.display === 'inline-block') {
|
||||
markerSelectGroup.style.display = 'inline-block';
|
||||
markerInputGroup.style.display = 'none';
|
||||
} else {
|
||||
markerSelectGroup.style.display = 'none';
|
||||
markerInputGroup.style.display = 'inline-block';
|
||||
markerInputGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function changeGroup() {
|
||||
elSelected.attr('xlink:href', '#' + this.value);
|
||||
elSelected.attr('data-id', '#' + this.value);
|
||||
}
|
||||
|
||||
function createGroup() {
|
||||
let newGroup = this.value
|
||||
.toLowerCase()
|
||||
markerLock.className = lock ? "icon-lock" : "icon-lock-open";
|
||||
.replace(/ /g, '_')
|
||||
.replace(/[^\w\s]/gi, '');
|
||||
if (Number.isFinite(+newGroup.charAt(0))) newGroup = 'm' + newGroup;
|
||||
if (document.getElementById(newGroup)) {
|
||||
tip('Element with this id already exists. Please provide a unique name', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
markerInputGroup.value = '';
|
||||
// clone old group assigning new id
|
||||
const id = elSelected.attr('data-id');
|
||||
const clone = d3.select('#defs-markers').select(id).node().cloneNode(true);
|
||||
clone.id = newGroup;
|
||||
document.getElementById('defs-markers').insertBefore(clone, null);
|
||||
elSelected.attr('xlink:href', '#' + newGroup).attr('data-id', '#' + newGroup);
|
||||
|
||||
// select new group
|
||||
markerSelectGroup.options.add(new Option(newGroup, newGroup, false, true));
|
||||
toggleGroupInput();
|
||||
}
|
||||
|
||||
function removeGroup() {
|
||||
const id = elSelected.attr('data-id');
|
||||
const used = document.querySelectorAll("use[data-id='" + id + "']");
|
||||
|
||||
const count = used.length === 1 ? '1 element' : used.length + ' elements';
|
||||
const message = `Are you sure you want to remove all markers of that type (${count})? <br>This action cannot be reverted`;
|
||||
const onConfirm = () => {
|
||||
if (id !== '#marker0') d3.select('#defs-markers').select(id).remove();
|
||||
used.forEach((e) => {
|
||||
const index = notes.findIndex((n) => n.id === e.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
e.remove();
|
||||
});
|
||||
updateGroupOptions();
|
||||
updateGroupOptions();
|
||||
$('#markerEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove marker type', message, confirm: 'Remove', onConfirm});
|
||||
function changeMarkerType() {
|
||||
marker.type = this.value;
|
||||
}
|
||||
|
||||
function toggleIconSection() {
|
||||
if (markerIconSection.style.display === 'inline-block') {
|
||||
markerEditor.querySelectorAll('button:not(#markerIcon)').forEach((b) => (b.style.display = 'inline-block'));
|
||||
markerIconSection.style.display = 'none';
|
||||
markerIconSelect.style.display = 'none';
|
||||
} else {
|
||||
markerEditor.querySelectorAll('button:not(#markerIcon)').forEach((b) => (b.style.display = 'none'));
|
||||
markerIconSection.style.display = 'inline-block';
|
||||
markerIconSelect.style.display = 'inline-block';
|
||||
}
|
||||
function changeMarkerIcon() {
|
||||
const icon = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
}
|
||||
|
||||
function selectMarkerIcon() {
|
||||
selectIcon(this.innerHTML, (v) => {
|
||||
this.innerHTML = v;
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers').select(id).select('text').text(v);
|
||||
selectIcon(marker.icon, icon => {
|
||||
markerIcon.value = icon;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconSize() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers')
|
||||
.select(id)
|
||||
.select('text')
|
||||
.attr('font-size', this.value + 'px');
|
||||
const px = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.px = px;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftX() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers')
|
||||
.select(id)
|
||||
.select('text')
|
||||
.attr('x', this.value + '%');
|
||||
const dx = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dx = dx;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftY() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers')
|
||||
.select(id)
|
||||
.select('text')
|
||||
.attr('y', this.value + '%');
|
||||
}
|
||||
|
||||
function toggleStyleSection() {
|
||||
if (markerStyleSection.style.display === 'inline-block') {
|
||||
markerEditor.querySelectorAll('button:not(#markerStyle)').forEach((b) => (b.style.display = 'inline-block'));
|
||||
markerStyleSection.style.display = 'none';
|
||||
} else {
|
||||
markerEditor.querySelectorAll('button:not(#markerStyle)').forEach((b) => (b.style.display = 'none'));
|
||||
markerStyleSection.style.display = 'inline-block';
|
||||
}
|
||||
const dy = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dy = dy;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeMarkerSize() {
|
||||
const id = elSelected.attr('data-id');
|
||||
document.querySelectorAll("use[data-id='" + id + "']").forEach((e) => {
|
||||
const x = +e.dataset.x,
|
||||
y = +e.dataset.y;
|
||||
const size = +this.value;
|
||||
const rescale = +markers.attr("rescale");
|
||||
const desired = (e.dataset.size = +markerSize.value);
|
||||
const size = Math.max(desired * 5 + 25 / scale, 1);
|
||||
|
||||
e.setAttribute('x', x - size / 2);
|
||||
e.setAttribute('y', y - size / 2);
|
||||
e.setAttribute('width', size);
|
||||
e.setAttribute('height', size);
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.size = size;
|
||||
const {i, x, y, hidden} = marker;
|
||||
const el = !hidden && document.getElementById(`marker${i}`);
|
||||
if (!el) return;
|
||||
|
||||
const zoomedSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
el.setAttribute("width", zoomedSize);
|
||||
el.setAttribute("height", zoomedSize);
|
||||
el.setAttribute("x", rn(x - zoomedSize / 2, 1));
|
||||
el.setAttribute("y", rn(y - zoomedSize, 1));
|
||||
});
|
||||
invokeActiveZooming();
|
||||
}
|
||||
|
||||
function changePinStroke() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select(id).select('path').attr('fill', this.value);
|
||||
d3.select(id).select('circle').attr('stroke', this.value);
|
||||
function changeMarkerPin() {
|
||||
const pin = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.pin = pin;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinFill() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select(id).select('circle').attr('fill', this.value);
|
||||
const fill = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.fill = fill;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconStrokeWidth() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers').select(id).select('text').attr('stroke-width', this.value);
|
||||
function changePinStroke() {
|
||||
const stroke = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.stroke = stroke;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconStroke() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers').select(id).select('text').attr('stroke', this.value);
|
||||
function redrawIcon({i, hidden, icon, dx = 50, dy = 50, px = 12}) {
|
||||
const iconElement = !hidden && document.querySelector(`#marker${i} > text`);
|
||||
if (iconElement) {
|
||||
iconElement.innerHTML = icon;
|
||||
iconElement.setAttribute("x", dx + "%");
|
||||
iconElement.setAttribute("y", dy + "%");
|
||||
iconElement.setAttribute("font-size", px + "px");
|
||||
}
|
||||
}
|
||||
|
||||
function changeIconFill() {
|
||||
const id = elSelected.attr('data-id');
|
||||
d3.select('#defs-markers').select(id).select('text').attr('fill', this.value);
|
||||
}
|
||||
|
||||
function togglePinVisibility() {
|
||||
const id = elSelected.attr('data-id');
|
||||
let show = 1;
|
||||
if (this.className === 'icon-info-circled') {
|
||||
this.className = 'icon-info';
|
||||
show = 0;
|
||||
} else this.className = 'icon-info-circled';
|
||||
function redrawPin({i, hidden, pin = "bubble", fill = "#fff", stroke = "#000"}) {
|
||||
const pinGroup = !hidden && document.querySelector(`#marker${i} > g`);
|
||||
if (pinGroup) pinGroup.innerHTML = getPin(pin, fill, stroke);
|
||||
d3.select(id).select('circle').attr('opacity', show);
|
||||
d3.select(id).select('path').attr('opacity', show);
|
||||
}
|
||||
|
||||
function editMarkerLegend() {
|
||||
const id = elSelected.attr('id');
|
||||
const id = element.id;
|
||||
editNotes(id, id);
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
document.getElementById('addMarker').click();
|
||||
function toggleMarkerLock() {
|
||||
marker.lock = !marker.lock;
|
||||
markerLock.classList.toggle("icon-lock-open");
|
||||
markerLock.classList.toggle("icon-lock");
|
||||
}
|
||||
|
||||
function removeMarker() {
|
||||
const message = 'Are you sure you want to remove the marker? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
const index = notes.findIndex((n) => n.id === elSelected.attr('id'));
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
elSelected.remove();
|
||||
$('#markerEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove marker', message, confirm: 'Remove', onConfirm});
|
||||
function toggleAddMarker() {
|
||||
markerAdd.classList.toggle("pressed");
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function confirmMarkerDeletion() {
|
||||
confirmationDialog({
|
||||
title: "Remove marker",
|
||||
message: "Are you sure you want to remove this marker? The action cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: deleteMarker
|
||||
|
||||
function deleteMarker() {
|
||||
notes = notes.filter(note => note.id !== element.id);
|
||||
pack.markers = pack.markers.filter(m => m.i !== marker.i);
|
||||
element.remove();
|
||||
$("#markerEditor").dialog("close");
|
||||
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function closeMarkerEditor() {
|
||||
listeners.forEach(removeListener => removeListener());
|
||||
|
||||
unselect();
|
||||
if (addMarker.classList.contains('pressed')) addMarker.classList.remove('pressed');
|
||||
if (markerAdd.classList.contains('pressed')) markerAdd.classList.remove('pressed');
|
||||
|
|
|
|||
265
modules/ui/markers-editor.js.orig
Normal file
265
modules/ui/markers-editor.js.orig
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
'use strict';
|
||||
function editMarker(markerI) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
|
||||
const [element, marker] = getElement(markerI, d3.event);
|
||||
if (!marker || !element) return;
|
||||
|
||||
elSelected = d3.select(element).raise().call(d3.drag().on("start", dragMarker)).classed("draggable", true);
|
||||
|
||||
if (document.getElementById("notesEditor").offsetParent) editNotes(element.id, element.id);
|
||||
|
||||
// dom elements
|
||||
const markerType = document.getElementById("markerType");
|
||||
const markerIcon = document.getElementById("markerIcon");
|
||||
const markerIconSelect = document.getElementById("markerIconSelect");
|
||||
const markerIconSize = document.getElementById("markerIconSize");
|
||||
const markerIconShiftX = document.getElementById("markerIconShiftX");
|
||||
const markerIconShiftY = document.getElementById("markerIconShiftY");
|
||||
const markerSize = document.getElementById("markerSize");
|
||||
const markerPin = document.getElementById("markerPin");
|
||||
const markerFill = document.getElementById("markerFill");
|
||||
const markerStroke = document.getElementById("markerStroke");
|
||||
|
||||
const markerNotes = document.getElementById("markerNotes");
|
||||
const markerLock = document.getElementById("markerLock");
|
||||
const addMarker = document.getElementById("addMarker");
|
||||
const markerAdd = document.getElementById("markerAdd");
|
||||
const markerRemove = document.getElementById("markerRemove");
|
||||
|
||||
updateInputs();
|
||||
|
||||
$('#markerEditor').dialog({
|
||||
title: "Edit Marker",
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "svg", collision: "fit"},
|
||||
close: closeMarkerEditor
|
||||
});
|
||||
|
||||
const listeners = [
|
||||
listen(markerType, "change", changeMarkerType),
|
||||
listen(markerIcon, "input", changeMarkerIcon),
|
||||
listen(markerIconSelect, "click", selectMarkerIcon),
|
||||
listen(markerIconSize, "input", changeIconSize),
|
||||
listen(markerIconShiftX, "input", changeIconShiftX),
|
||||
listen(markerIconShiftY, "input", changeIconShiftY),
|
||||
listen(markerSize, "input", changeMarkerSize),
|
||||
listen(markerPin, "change", changeMarkerPin),
|
||||
listen(markerFill, "input", changePinFill),
|
||||
listen(markerStroke, "input", changePinStroke),
|
||||
listen(markerNotes, "click", editMarkerLegend),
|
||||
listen(markerLock, "click", toggleMarkerLock),
|
||||
listen(markerAdd, "click", toggleAddMarker),
|
||||
listen(markerRemove, "click", confirmMarkerDeletion)
|
||||
];
|
||||
|
||||
function getElement(markerI, event) {
|
||||
if (event) {
|
||||
const element = event.target?.closest("svg");
|
||||
const marker = pack.markers.find(({i}) => Number(element.id.slice(6)) === i);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
const element = document.getElementById(`marker${markerI}`);
|
||||
const marker = pack.markers.find(({i}) => i === markerI);
|
||||
return [element, marker];
|
||||
}
|
||||
|
||||
function getSameTypeMarkers() {
|
||||
const currentType = marker.type;
|
||||
if (!currentType) return [marker];
|
||||
return pack.markers.filter(({type}) => type === currentType);
|
||||
}
|
||||
|
||||
function dragMarker() {
|
||||
const dx = +this.getAttribute("x") - d3.event.x;
|
||||
const dy = +this.getAttribute("y") - d3.event.y;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", dx + x);
|
||||
this.setAttribute("y", dy + y);
|
||||
});
|
||||
|
||||
d3.event.on("end", function () {
|
||||
const {x, y} = d3.event;
|
||||
this.setAttribute("x", rn(dx + x, 2));
|
||||
this.setAttribute("y", rn(dy + y, 2));
|
||||
|
||||
const size = marker.size || 30;
|
||||
const zoomSize = Math.max(rn(size / 5 + 24 / scale, 2), 1);
|
||||
|
||||
marker.x = rn(x + dx + zoomSize / 2, 1);
|
||||
marker.y = rn(y + dy + zoomSize, 1);
|
||||
marker.cell = findCell(marker.x, marker.y);
|
||||
.selectAll('symbol')
|
||||
.each(function () {
|
||||
});
|
||||
}
|
||||
|
||||
function updateInputs() {
|
||||
const {icon, type = "", size = 30, dx = 50, dy = 50, px = 12, stroke = "#000000", fill = "#ffffff", pin = "bubble", lock} = marker;
|
||||
|
||||
markerType.value = type;
|
||||
markerIcon.value = icon;
|
||||
markerIconSize.value = px;
|
||||
markerIconShiftX.value = dx;
|
||||
markerIconShiftY.value = dy;
|
||||
markerSize.value = size;
|
||||
markerPin.value = pin;
|
||||
markerFill.value = fill;
|
||||
markerStroke.value = stroke;
|
||||
|
||||
markerLock.className = lock ? "icon-lock" : "icon-lock-open";
|
||||
.replace(/ /g, '_')
|
||||
.replace(/[^\w\s]/gi, '');
|
||||
if (Number.isFinite(+newGroup.charAt(0))) newGroup = 'm' + newGroup;
|
||||
}
|
||||
|
||||
function changeMarkerType() {
|
||||
marker.type = this.value;
|
||||
}
|
||||
|
||||
function changeMarkerIcon() {
|
||||
const icon = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
}
|
||||
|
||||
function selectMarkerIcon() {
|
||||
selectIcon(marker.icon, icon => {
|
||||
markerIcon.value = icon;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.icon = icon;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconSize() {
|
||||
const px = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.px = px;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftX() {
|
||||
const dx = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dx = dx;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeIconShiftY() {
|
||||
const dy = +this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.dy = dy;
|
||||
redrawIcon(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changeMarkerSize() {
|
||||
const size = +this.value;
|
||||
const rescale = +markers.attr("rescale");
|
||||
const desired = (e.dataset.size = +markerSize.value);
|
||||
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.size = size;
|
||||
const {i, x, y, hidden} = marker;
|
||||
const el = !hidden && document.getElementById(`marker${i}`);
|
||||
if (!el) return;
|
||||
|
||||
const zoomedSize = rescale ? Math.max(rn(size / 5 + 24 / scale, 2), 1) : size;
|
||||
el.setAttribute("width", zoomedSize);
|
||||
el.setAttribute("height", zoomedSize);
|
||||
el.setAttribute("x", rn(x - zoomedSize / 2, 1));
|
||||
el.setAttribute("y", rn(y - zoomedSize, 1));
|
||||
});
|
||||
}
|
||||
|
||||
function changeMarkerPin() {
|
||||
const pin = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.pin = pin;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinFill() {
|
||||
const fill = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.fill = fill;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function changePinStroke() {
|
||||
const stroke = this.value;
|
||||
getSameTypeMarkers().forEach(marker => {
|
||||
marker.stroke = stroke;
|
||||
redrawPin(marker);
|
||||
});
|
||||
}
|
||||
|
||||
function redrawIcon({i, hidden, icon, dx = 50, dy = 50, px = 12}) {
|
||||
const iconElement = !hidden && document.querySelector(`#marker${i} > text`);
|
||||
if (iconElement) {
|
||||
iconElement.innerHTML = icon;
|
||||
iconElement.setAttribute("x", dx + "%");
|
||||
iconElement.setAttribute("y", dy + "%");
|
||||
iconElement.setAttribute("font-size", px + "px");
|
||||
}
|
||||
}
|
||||
|
||||
function redrawPin({i, hidden, pin = "bubble", fill = "#fff", stroke = "#000"}) {
|
||||
const pinGroup = !hidden && document.querySelector(`#marker${i} > g`);
|
||||
if (pinGroup) pinGroup.innerHTML = getPin(pin, fill, stroke);
|
||||
d3.select(id).select('circle').attr('opacity', show);
|
||||
d3.select(id).select('path').attr('opacity', show);
|
||||
}
|
||||
|
||||
function editMarkerLegend() {
|
||||
const id = element.id;
|
||||
editNotes(id, id);
|
||||
}
|
||||
|
||||
function toggleMarkerLock() {
|
||||
marker.lock = !marker.lock;
|
||||
markerLock.classList.toggle("icon-lock-open");
|
||||
markerLock.classList.toggle("icon-lock");
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
markerAdd.classList.toggle("pressed");
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function confirmMarkerDeletion() {
|
||||
confirmationDialog({
|
||||
title: "Remove marker",
|
||||
message: "Are you sure you want to remove this marker? The action cannot be reverted",
|
||||
confirm: "Remove",
|
||||
onConfirm: deleteMarker
|
||||
|
||||
function deleteMarker() {
|
||||
notes = notes.filter(note => note.id !== element.id);
|
||||
pack.markers = pack.markers.filter(m => m.i !== marker.i);
|
||||
element.remove();
|
||||
$("#markerEditor").dialog("close");
|
||||
if (document.getElementById("markersOverviewRefresh").offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function closeMarkerEditor() {
|
||||
listeners.forEach(removeListener => removeListener());
|
||||
|
||||
unselect();
|
||||
addMarker.classList.remove("pressed");
|
||||
markerAdd.classList.remove("pressed");
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
}
|
||||
}
|
||||
196
modules/ui/markers-overview.js
Normal file
196
modules/ui/markers-overview.js
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
'use strict';
|
||||
function overviewMarkers() {
|
||||
if (customization) return;
|
||||
closeDialogs('#markersOverview, .stable');
|
||||
if (!layerIsOn('toggleMarkers')) toggleMarkers();
|
||||
|
||||
const markerGroup = document.getElementById('markers');
|
||||
const body = document.getElementById('markersBody');
|
||||
const markersInverPin = document.getElementById('markersInverPin');
|
||||
const markersInverLock = document.getElementById('markersInverLock');
|
||||
const markersFooterNumber = document.getElementById('markersFooterNumber');
|
||||
const markersOverviewRefresh = document.getElementById('markersOverviewRefresh');
|
||||
const markersAddFromOverview = document.getElementById('markersAddFromOverview');
|
||||
const markersGenerationConfig = document.getElementById('markersGenerationConfig');
|
||||
const markersRemoveAll = document.getElementById('markersRemoveAll');
|
||||
const markersExport = document.getElementById('markersExport');
|
||||
|
||||
addLines();
|
||||
|
||||
$('#markersOverview').dialog({
|
||||
title: 'Markers Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: close,
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
const listeners = [
|
||||
listen(body, 'click', handleLineClick),
|
||||
listen(markersInverPin, 'click', invertPin),
|
||||
listen(markersInverLock, 'click', invertLock),
|
||||
listen(markersOverviewRefresh, 'click', addLines),
|
||||
listen(markersAddFromOverview, 'click', toggleAddMarker),
|
||||
listen(markersGenerationConfig, 'click', configMarkersGeneration),
|
||||
listen(markersRemoveAll, 'click', triggerRemoveAll),
|
||||
listen(markersExport, 'click', exportMarkers)
|
||||
];
|
||||
|
||||
function handleLineClick(ev) {
|
||||
const el = ev.target;
|
||||
const i = +el.parentNode.dataset.i;
|
||||
|
||||
if (el.classList.contains('icon-pencil')) return openEditor(i);
|
||||
if (el.classList.contains('icon-dot-circled')) return focusOnMarker(i);
|
||||
if (el.classList.contains('icon-pin')) return pinMarker(el, i);
|
||||
if (el.classList.contains('locks')) return toggleLockStatus(el, i);
|
||||
if (el.classList.contains('icon-trash-empty')) return triggerRemove(i);
|
||||
}
|
||||
|
||||
function addLines() {
|
||||
const lines = pack.markers
|
||||
.map(({i, type, icon, pinned, lock}) => {
|
||||
return `<div class="states" data-i=${i} data-type="${type}">
|
||||
<div data-tip="Marker icon and type" style="width:12em">${icon} ${type}</div>
|
||||
<span style="padding-right:.1em" data-tip="Edit marker" class="icon-pencil"></span>
|
||||
<span style="padding-right:.1em" data-tip="Focus on marker position" class="icon-dot-circled pointer"></span>
|
||||
<span style="padding-right:.1em" data-tip="Pin marker (display only pinned markers)" class="icon-pin ${pinned ? '' : 'inactive'}" pointer"></span>
|
||||
<span style="padding-right:.1em" class="locks pointer ${lock ? 'icon-lock' : 'icon-lock-open inactive'}" onmouseover="showElementLockTip(event)"></span>
|
||||
<span data-tip="Remove marker" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
body.innerHTML = lines;
|
||||
markersFooterNumber.innerText = pack.markers.length;
|
||||
|
||||
applySorting(markersHeader);
|
||||
}
|
||||
|
||||
function invertPin() {
|
||||
let anyPinned = false;
|
||||
|
||||
pack.markers.forEach((marker) => {
|
||||
const pinned = !marker.pinned;
|
||||
if (pinned) {
|
||||
marker.pinned = true;
|
||||
anyPinned = true;
|
||||
} else delete marker.pinned;
|
||||
});
|
||||
|
||||
markerGroup.setAttribute('pinned', anyPinned ? 1 : null);
|
||||
drawMarkers();
|
||||
addLines();
|
||||
}
|
||||
|
||||
function invertLock() {
|
||||
pack.markers = pack.markers.map((marker) => ({...marker, lock: !marker.lock}));
|
||||
addLines();
|
||||
}
|
||||
|
||||
function openEditor(i) {
|
||||
const marker = pack.markers.find((marker) => marker.i === i);
|
||||
if (!marker) return;
|
||||
|
||||
const {x, y} = marker;
|
||||
zoomTo(x, y, 8, 2000);
|
||||
editMarker(i);
|
||||
}
|
||||
|
||||
function focusOnMarker(i) {
|
||||
highlightElement(document.getElementById(`marker${i}`), 2);
|
||||
}
|
||||
|
||||
function pinMarker(el, i) {
|
||||
const marker = pack.markers.find((marker) => marker.i === i);
|
||||
if (marker.pinned) {
|
||||
delete marker.pinned;
|
||||
const anyPinned = pack.markers.some((marker) => marker.pinned);
|
||||
if (!anyPinned) markerGroup.removeAttribute('pinned');
|
||||
} else {
|
||||
marker.pinned = true;
|
||||
markerGroup.setAttribute('pinned', 1);
|
||||
}
|
||||
el.classList.toggle('inactive');
|
||||
drawMarkers();
|
||||
}
|
||||
|
||||
function toggleLockStatus(el, i) {
|
||||
const marker = pack.markers.find((marker) => marker.i === i);
|
||||
if (marker.lock) {
|
||||
delete marker.lock;
|
||||
el.className = 'locks pointer icon-lock-open inactive';
|
||||
} else {
|
||||
marker.lock = true;
|
||||
el.className = 'locks pointer icon-lock';
|
||||
}
|
||||
}
|
||||
|
||||
function triggerRemove(i) {
|
||||
confirmationDialog({
|
||||
title: 'Remove marker',
|
||||
message: 'Are you sure you want to remove this marker? The action cannot be reverted',
|
||||
confirm: 'Remove',
|
||||
onConfirm: () => removeMarker(i)
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
markersAddFromOverview.classList.toggle('pressed');
|
||||
addMarker.click();
|
||||
}
|
||||
|
||||
function removeMarker(i) {
|
||||
notes = notes.filter((note) => note.id !== `marker${i}`);
|
||||
pack.markers = pack.markers.filter((marker) => marker.i !== i);
|
||||
document.getElementById(`marker${i}`)?.remove();
|
||||
addLines();
|
||||
}
|
||||
|
||||
function triggerRemoveAll() {
|
||||
confirmationDialog({
|
||||
title: 'Remove all markers',
|
||||
message: 'Are you sure you want to remove all non-locked markers? The action cannot be reverted',
|
||||
confirm: 'Remove all',
|
||||
onConfirm: removeAllMarkers
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllMarkers() {
|
||||
pack.markers = pack.markers.filter(({i, lock}) => {
|
||||
if (lock) return true;
|
||||
|
||||
const id = `marker${i}`;
|
||||
document.getElementById(id)?.remove();
|
||||
notes = notes.filter((note) => note.id !== id);
|
||||
return false;
|
||||
});
|
||||
|
||||
addLines();
|
||||
}
|
||||
|
||||
function exportMarkers() {
|
||||
const headers = 'Id,Type,Icon,Name,Note,X,Y\n';
|
||||
|
||||
const body = pack.markers.map((marker) => {
|
||||
const {i, type, icon, x, y} = marker;
|
||||
const id = `marker${i}`;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
const legend = escape(note.legend);
|
||||
return [id, type, icon, note.name, legend, x, y].join(',');
|
||||
});
|
||||
|
||||
const data = headers + body.join('\n');
|
||||
const fileName = getFileName('Markers') + '.csv';
|
||||
downloadFile(data, fileName);
|
||||
}
|
||||
|
||||
function close() {
|
||||
listeners.forEach((removeListener) => removeListener());
|
||||
|
||||
addMarker.classList.remove('pressed');
|
||||
markerAdd.classList.remove('pressed');
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,33 +10,33 @@ class Rulers {
|
|||
}
|
||||
|
||||
toString() {
|
||||
return this.data.map(ruler => ruler.toString()).join("; ");
|
||||
return this.data.map((ruler) => ruler.toString()).join('; ');
|
||||
}
|
||||
|
||||
fromString(string) {
|
||||
this.data = [];
|
||||
|
||||
const rulers = string.split("; ");
|
||||
const rulers = string.split('; ');
|
||||
for (const rulerString of rulers) {
|
||||
const [type, pointsString] = rulerString.split(": ");
|
||||
const points = pointsString.split(" ").map(el => el.split(",").map(n => +n));
|
||||
const Type = type === "Ruler" ? Ruler : type === "Opisometer" ? Opisometer : type === "RouteOpisometer" ? RouteOpisometer : type === "Planimeter" ? Planimeter : null;
|
||||
const [type, pointsString] = rulerString.split(': ');
|
||||
const points = pointsString.split(' ').map((el) => el.split(',').map((n) => +n));
|
||||
const Type = type === 'Ruler' ? Ruler : type === 'Opisometer' ? Opisometer : type === 'RouteOpisometer' ? RouteOpisometer : type === 'Planimeter' ? Planimeter : null;
|
||||
this.create(Type, points);
|
||||
}
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.data.forEach(ruler => ruler.draw());
|
||||
this.data.forEach((ruler) => ruler.draw());
|
||||
}
|
||||
|
||||
undraw() {
|
||||
this.data.forEach(ruler => ruler.undraw());
|
||||
this.data.forEach((ruler) => ruler.undraw());
|
||||
}
|
||||
|
||||
remove(id) {
|
||||
if (id === undefined) return;
|
||||
|
||||
const ruler = this.data.find(ruler => ruler.id === id);
|
||||
const ruler = this.data.find((ruler) => ruler.id === id);
|
||||
ruler.undraw();
|
||||
const rulerIndex = this.data.indexOf(ruler);
|
||||
rulers.data.splice(rulerIndex, 1);
|
||||
|
|
@ -50,7 +50,7 @@ class Measurer {
|
|||
}
|
||||
|
||||
toString() {
|
||||
return this.constructor.name + ": " + this.points.join(" ");
|
||||
return this.constructor.name + ': ' + this.points.join(' ');
|
||||
}
|
||||
|
||||
getSize() {
|
||||
|
|
@ -62,13 +62,13 @@ class Measurer {
|
|||
}
|
||||
|
||||
drag() {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const tr = parseTransform(this.getAttribute('transform'));
|
||||
const x = +tr[0] - d3.event.x,
|
||||
y = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
d3.event.on('drag', function () {
|
||||
const transform = `translate(${x + d3.event.x},${y + d3.event.y})`;
|
||||
this.setAttribute("transform", transform);
|
||||
this.setAttribute('transform', transform);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
getPointsString() {
|
||||
return this.points.join(" ");
|
||||
return this.points.join(' ');
|
||||
}
|
||||
|
||||
updatePoint(index, x, y) {
|
||||
|
|
@ -119,7 +119,7 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
getPointId(x, y) {
|
||||
return this.points.findIndex(el => el[0] == x && el[1] == y);
|
||||
return this.points.findIndex((el) => el[0] == x && el[1] == y);
|
||||
}
|
||||
|
||||
pushPoint(i) {
|
||||
|
|
@ -128,42 +128,42 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
if (this.el) this.el.selectAll('*').remove();
|
||||
const points = this.getPointsString();
|
||||
const size = this.getSize();
|
||||
const dash = this.getDash();
|
||||
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "ruler")
|
||||
.call(d3.drag().on("start", this.drag))
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("polyline")
|
||||
.attr("points", points)
|
||||
.attr("class", "white")
|
||||
.attr("stroke-width", size)
|
||||
.call(d3.drag().on("start", () => this.addControl(this)));
|
||||
el.append("polyline")
|
||||
.attr("points", points)
|
||||
.attr("class", "gray")
|
||||
.attr("stroke-width", rn(size * 1.2, 2))
|
||||
.attr("stroke-dasharray", dash);
|
||||
el.append("g")
|
||||
.attr("class", "rulerPoints")
|
||||
.attr("stroke-width", 0.5 * size)
|
||||
.attr("font-size", 2 * size);
|
||||
el.append("text")
|
||||
.attr("dx", ".35em")
|
||||
.attr("dy", "-.45em")
|
||||
.on("click", () => rulers.remove(this.id));
|
||||
.append('g')
|
||||
.attr('class', 'ruler')
|
||||
.call(d3.drag().on('start', this.drag))
|
||||
.attr('font-size', 10 * size));
|
||||
el.append('polyline')
|
||||
.attr('points', points)
|
||||
.attr('class', 'white')
|
||||
.attr('stroke-width', size)
|
||||
.call(d3.drag().on('start', () => this.addControl(this)));
|
||||
el.append('polyline')
|
||||
.attr('points', points)
|
||||
.attr('class', 'gray')
|
||||
.attr('stroke-width', rn(size * 1.2, 2))
|
||||
.attr('stroke-dasharray', dash);
|
||||
el.append('g')
|
||||
.attr('class', 'rulerPoints')
|
||||
.attr('stroke-width', 0.5 * size)
|
||||
.attr('font-size', 2 * size);
|
||||
el.append('text')
|
||||
.attr('dx', '.35em')
|
||||
.attr('dy', '-.45em')
|
||||
.on('click', () => rulers.remove(this.id));
|
||||
this.drawPoints(el);
|
||||
this.updateLabel();
|
||||
return this;
|
||||
}
|
||||
|
||||
drawPoints(el) {
|
||||
const g = el.select(".rulerPoints");
|
||||
g.selectAll("circle").remove();
|
||||
const g = el.select('.rulerPoints');
|
||||
g.selectAll('circle').remove();
|
||||
|
||||
for (let i = 0; i < this.points.length; i++) {
|
||||
const [x, y] = this.points[i];
|
||||
|
|
@ -173,19 +173,19 @@ class Ruler extends Measurer {
|
|||
|
||||
drawPoint(el, x, y, i) {
|
||||
const context = this;
|
||||
el.append("circle")
|
||||
.attr("r", "1em")
|
||||
.attr("cx", x)
|
||||
.attr("cy", y)
|
||||
.attr("class", this.isEdge(i) ? "edge" : "control")
|
||||
.on("click", function () {
|
||||
el.append('circle')
|
||||
.attr('r', '1em')
|
||||
.attr('cx', x)
|
||||
.attr('cy', y)
|
||||
.attr('class', this.isEdge(i) ? 'edge' : 'control')
|
||||
.on('click', function () {
|
||||
context.removePoint(context, i);
|
||||
})
|
||||
.call(
|
||||
d3
|
||||
.drag()
|
||||
.clickDistance(3)
|
||||
.on("start", function () {
|
||||
.on('start', function () {
|
||||
context.dragControl(context, i);
|
||||
})
|
||||
);
|
||||
|
|
@ -197,9 +197,9 @@ class Ruler extends Measurer {
|
|||
|
||||
updateLabel() {
|
||||
const length = this.getLength();
|
||||
const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const text = rn(length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
const [x, y] = last(this.points);
|
||||
this.el.select("text").attr("x", x).attr("y", y).text(text);
|
||||
this.el.select('text').attr('x', x).attr('y', y).text(text);
|
||||
}
|
||||
|
||||
getLength() {
|
||||
|
|
@ -215,13 +215,13 @@ class Ruler extends Measurer {
|
|||
dragControl(context, pointId) {
|
||||
let addPoint = context.isEdge(pointId) && d3.event.sourceEvent.ctrlKey;
|
||||
let circle = context.el.select(`circle:nth-child(${pointId + 1})`);
|
||||
const line = context.el.selectAll("polyline");
|
||||
const line = context.el.selectAll('polyline');
|
||||
|
||||
let x0 = rn(d3.event.x, 1);
|
||||
let y0 = rn(d3.event.y, 1);
|
||||
let axis;
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
d3.event.on('drag', function () {
|
||||
if (addPoint) {
|
||||
if (d3.event.dx < 0.1 && d3.event.dy < 0.1) return;
|
||||
context.pushPoint(pointId);
|
||||
|
|
@ -232,10 +232,10 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
const shiftPressed = d3.event.sourceEvent.shiftKey;
|
||||
if (shiftPressed && !axis) axis = Math.abs(d3.event.dx) > Math.abs(d3.event.dy) ? "x" : "y";
|
||||
if (shiftPressed && !axis) axis = Math.abs(d3.event.dx) > Math.abs(d3.event.dy) ? 'x' : 'y';
|
||||
|
||||
const x = axis === "y" ? x0 : rn(d3.event.x, 1);
|
||||
const y = axis === "x" ? y0 : rn(d3.event.y, 1);
|
||||
const x = axis === 'y' ? x0 : rn(d3.event.x, 1);
|
||||
const y = axis === 'x' ? y0 : rn(d3.event.y, 1);
|
||||
|
||||
if (!shiftPressed) {
|
||||
axis = null;
|
||||
|
|
@ -244,8 +244,8 @@ class Ruler extends Measurer {
|
|||
}
|
||||
|
||||
context.updatePoint(pointId, x, y);
|
||||
line.attr("points", context.getPointsString());
|
||||
circle.attr("cx", x).attr("cy", y);
|
||||
line.attr('points', context.getPointsString());
|
||||
circle.attr('cx', x).attr('cy', y);
|
||||
context.updateLabel();
|
||||
});
|
||||
}
|
||||
|
|
@ -273,43 +273,43 @@ class Opisometer extends Measurer {
|
|||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
if (this.el) this.el.selectAll('*').remove();
|
||||
const size = this.getSize();
|
||||
const dash = this.getDash();
|
||||
const context = this;
|
||||
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "opisometer")
|
||||
.call(d3.drag().on("start", this.drag))
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("path").attr("class", "white").attr("stroke-width", size);
|
||||
el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
||||
.append('g')
|
||||
.attr('class', 'opisometer')
|
||||
.call(d3.drag().on('start', this.drag))
|
||||
.attr('font-size', 10 * size));
|
||||
el.append('path').attr('class', 'white').attr('stroke-width', size);
|
||||
el.append('path').attr('class', 'gray').attr('stroke-width', size).attr('stroke-dasharray', dash);
|
||||
const rulerPoints = el
|
||||
.append("g")
|
||||
.attr("class", "rulerPoints")
|
||||
.attr("stroke-width", 0.5 * size)
|
||||
.attr("font-size", 2 * size);
|
||||
.append('g')
|
||||
.attr('class', 'rulerPoints')
|
||||
.attr('stroke-width', 0.5 * size)
|
||||
.attr('font-size', 2 * size);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.append('circle')
|
||||
.attr('r', '1em')
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
d3.drag().on('start', function () {
|
||||
context.dragControl(context, 0);
|
||||
})
|
||||
);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.append('circle')
|
||||
.attr('r', '1em')
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
d3.drag().on('start', function () {
|
||||
context.dragControl(context, 1);
|
||||
})
|
||||
);
|
||||
el.append("text")
|
||||
.attr("dx", ".35em")
|
||||
.attr("dy", "-.45em")
|
||||
.on("click", () => rulers.remove(this.id));
|
||||
el.append('text')
|
||||
.attr('dx', '.35em')
|
||||
.attr('dy', '-.45em')
|
||||
.on('click', () => rulers.remove(this.id));
|
||||
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
|
|
@ -319,26 +319,26 @@ class Opisometer extends Measurer {
|
|||
updateCurve() {
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.5));
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
this.el.selectAll('path').attr('d', path);
|
||||
|
||||
const left = this.points[0];
|
||||
const right = last(this.points);
|
||||
this.el.select(".rulerPoints > circle:first-child").attr("cx", left[0]).attr("cy", left[1]);
|
||||
this.el.select(".rulerPoints > circle:last-child").attr("cx", right[0]).attr("cy", right[1]);
|
||||
this.el.select('.rulerPoints > circle:first-child').attr('cx', left[0]).attr('cy', left[1]);
|
||||
this.el.select('.rulerPoints > circle:last-child').attr('cx', right[0]).attr('cy', right[1]);
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
const length = this.el.select("path").node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const length = this.el.select('path').node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
const [x, y] = last(this.points);
|
||||
this.el.select("text").attr("x", x).attr("y", y).text(text);
|
||||
this.el.select('text').attr('x', x).attr('y', y).text(text);
|
||||
}
|
||||
|
||||
dragControl(context, rigth) {
|
||||
const MIN_DIST = d3.event.sourceEvent.shiftKey ? 9 : 100;
|
||||
let prev = rigth ? last(context.points) : context.points[0];
|
||||
|
||||
d3.event.on("drag", function () {
|
||||
d3.event.on('drag', function () {
|
||||
const point = [d3.event.x | 0, d3.event.y | 0];
|
||||
|
||||
const dist2 = (prev[0] - point[0]) ** 2 + (prev[1] - point[1]) ** 2;
|
||||
|
|
@ -351,7 +351,7 @@ class Opisometer extends Measurer {
|
|||
context.updateLabel();
|
||||
});
|
||||
|
||||
d3.event.on("end", function () {
|
||||
d3.event.on('end', function () {
|
||||
if (!d3.event.sourceEvent.shiftKey) context.optimize();
|
||||
});
|
||||
}
|
||||
|
|
@ -361,7 +361,7 @@ class RouteOpisometer extends Measurer {
|
|||
constructor(points) {
|
||||
super(points);
|
||||
if (pack.cells) {
|
||||
this.cellStops = points.map(p => findCell(p[0], p[1]));
|
||||
this.cellStops = points.map((p) => findCell(p[0], p[1]));
|
||||
} else {
|
||||
this.cellStops = null;
|
||||
}
|
||||
|
|
@ -369,7 +369,7 @@ class RouteOpisometer extends Measurer {
|
|||
|
||||
checkCellStops() {
|
||||
if (!this.cellStops) {
|
||||
this.cellStops = this.points.map(p => findCell(p[0], p[1]));
|
||||
this.cellStops = this.points.map((p) => findCell(p[0], p[1]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -412,42 +412,42 @@ class RouteOpisometer extends Measurer {
|
|||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
if (this.el) this.el.selectAll('*').remove();
|
||||
const size = this.getSize();
|
||||
const dash = this.getDash();
|
||||
const context = this;
|
||||
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "opisometer")
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("path").attr("class", "white").attr("stroke-width", size);
|
||||
el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
||||
.append('g')
|
||||
.attr('class', 'opisometer')
|
||||
.attr('font-size', 10 * size));
|
||||
el.append('path').attr('class', 'white').attr('stroke-width', size);
|
||||
el.append('path').attr('class', 'gray').attr('stroke-width', size).attr('stroke-dasharray', dash);
|
||||
const rulerPoints = el
|
||||
.append("g")
|
||||
.attr("class", "rulerPoints")
|
||||
.attr("stroke-width", 0.5 * size)
|
||||
.attr("font-size", 2 * size);
|
||||
.append('g')
|
||||
.attr('class', 'rulerPoints')
|
||||
.attr('stroke-width', 0.5 * size)
|
||||
.attr('font-size', 2 * size);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.append('circle')
|
||||
.attr('r', '1em')
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
d3.drag().on('start', function () {
|
||||
context.dragControl(context, 0);
|
||||
})
|
||||
);
|
||||
rulerPoints
|
||||
.append("circle")
|
||||
.attr("r", "1em")
|
||||
.append('circle')
|
||||
.attr('r', '1em')
|
||||
.call(
|
||||
d3.drag().on("start", function () {
|
||||
d3.drag().on('start', function () {
|
||||
context.dragControl(context, 1);
|
||||
})
|
||||
);
|
||||
el.append("text")
|
||||
.attr("dx", ".35em")
|
||||
.attr("dy", "-.45em")
|
||||
.on("click", () => rulers.remove(this.id));
|
||||
el.append('text')
|
||||
.attr('dx', '.35em')
|
||||
.attr('dy', '-.45em')
|
||||
.on('click', () => rulers.remove(this.id));
|
||||
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
|
|
@ -457,23 +457,23 @@ class RouteOpisometer extends Measurer {
|
|||
updateCurve() {
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.5));
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
this.el.selectAll('path').attr('d', path);
|
||||
|
||||
const left = this.points[0];
|
||||
const right = last(this.points);
|
||||
this.el.select(".rulerPoints > circle:first-child").attr("cx", left[0]).attr("cy", left[1]);
|
||||
this.el.select(".rulerPoints > circle:last-child").attr("cx", right[0]).attr("cy", right[1]);
|
||||
this.el.select('.rulerPoints > circle:first-child').attr('cx', left[0]).attr('cy', left[1]);
|
||||
this.el.select('.rulerPoints > circle:last-child').attr('cx', right[0]).attr('cy', right[1]);
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
const length = this.el.select("path").node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const length = this.el.select('path').node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
const [x, y] = last(this.points);
|
||||
this.el.select("text").attr("x", x).attr("y", y).text(text);
|
||||
this.el.select('text').attr('x', x).attr('y', y).text(text);
|
||||
}
|
||||
|
||||
dragControl(context, rigth) {
|
||||
d3.event.on("drag", function () {
|
||||
d3.event.on('drag', function () {
|
||||
const mousePoint = [d3.event.x | 0, d3.event.y | 0];
|
||||
const cells = pack.cells;
|
||||
|
||||
|
|
@ -493,16 +493,16 @@ class Planimeter extends Measurer {
|
|||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
if (this.el) this.el.selectAll('*').remove();
|
||||
const size = this.getSize();
|
||||
|
||||
const el = (this.el = ruler
|
||||
.append("g")
|
||||
.attr("class", "planimeter")
|
||||
.call(d3.drag().on("start", this.drag))
|
||||
.attr("font-size", 10 * size));
|
||||
el.append("path").attr("class", "planimeter").attr("stroke-width", size);
|
||||
el.append("text").on("click", () => rulers.remove(this.id));
|
||||
.append('g')
|
||||
.attr('class', 'planimeter')
|
||||
.call(d3.drag().on('start', this.drag))
|
||||
.attr('font-size', 10 * size));
|
||||
el.append('path').attr('class', 'planimeter').attr('stroke-width', size);
|
||||
el.append('text').on('click', () => rulers.remove(this.id));
|
||||
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
|
|
@ -512,32 +512,33 @@ class Planimeter extends Measurer {
|
|||
updateCurve() {
|
||||
lineGen.curve(d3.curveCatmullRomClosed.alpha(0.5));
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
this.el.selectAll('path').attr('d', path);
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
if (this.points.length < 3) return;
|
||||
|
||||
const polygonArea = rn(Math.abs(d3.polygonArea(this.points)));
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
const area = si(polygonArea * distanceScaleInput.value ** 2) + " " + unit;
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
const area = si(polygonArea * distanceScaleInput.value ** 2) + ' ' + unit;
|
||||
const c = polylabel([this.points], 1.0);
|
||||
this.el.select("text").attr("x", c[0]).attr("y", c[1]).text(area);
|
||||
this.el.select('text').attr('x', c[0]).attr('y', c[1]).text(area);
|
||||
}
|
||||
}
|
||||
|
||||
// Scale bar
|
||||
function drawScaleBar() {
|
||||
if (scaleBar.style("display") === "none") return; // no need to re-draw hidden element
|
||||
scaleBar.selectAll("*").remove(); // fully redraw every time
|
||||
function drawScaleBar(requestedScale) {
|
||||
if (scaleBar.style('display') === 'none') return; // no need to re-draw hidden element
|
||||
scaleBar.selectAll('*').remove(); // fully redraw every time
|
||||
const scaleLevel = requestedScale || scale;
|
||||
|
||||
const dScale = distanceScaleInput.value;
|
||||
const distanceScale = distanceScaleInput.value;
|
||||
const unit = distanceUnitInput.value;
|
||||
const size = +barSizeInput.value;
|
||||
|
||||
// calculate size
|
||||
const init = 100; // actual length in pixels if scale, dScale and size = 1;
|
||||
const size = +barSizeInput.value;
|
||||
let val = (init * size * dScale) / scale; // bar length in distance unit
|
||||
const init = 100;
|
||||
let val = (init * size * distanceScale) / scaleLevel; // bar length in distance unit
|
||||
if (val > 900) val = rn(val, -3);
|
||||
// round to 1000
|
||||
else if (val > 90) val = rn(val, -2);
|
||||
|
|
@ -545,81 +546,81 @@ function drawScaleBar() {
|
|||
else if (val > 9) val = rn(val, -1);
|
||||
// round to 10
|
||||
else val = rn(val); // round to 1
|
||||
const l = (val * scale) / dScale; // actual length in pixels on this scale
|
||||
const length = (val * scaleLevel) / distanceScale; // actual length in pixels on this scale
|
||||
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0.5)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", l + size - 0.5)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "white");
|
||||
.append('line')
|
||||
.attr('x1', 0.5)
|
||||
.attr('y1', 0)
|
||||
.attr('x2', length + size - 0.5)
|
||||
.attr('y2', 0)
|
||||
.attr('stroke-width', size)
|
||||
.attr('stroke', 'white');
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", size)
|
||||
.attr("x2", l + size)
|
||||
.attr("y2", size)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
const dash = size + " " + rn(l / 5 - size, 2);
|
||||
.append('line')
|
||||
.attr('x1', 0)
|
||||
.attr('y1', size)
|
||||
.attr('x2', length + size)
|
||||
.attr('y2', size)
|
||||
.attr('stroke-width', size)
|
||||
.attr('stroke', '#3d3d3d');
|
||||
const dash = size + ' ' + rn(length / 5 - size, 2);
|
||||
scaleBar
|
||||
.append("line")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", 0)
|
||||
.attr("x2", l + size)
|
||||
.attr("y2", 0)
|
||||
.attr("stroke-width", rn(size * 3, 2))
|
||||
.attr("stroke-dasharray", dash)
|
||||
.attr("stroke", "#3d3d3d");
|
||||
.append('line')
|
||||
.attr('x1', 0)
|
||||
.attr('y1', 0)
|
||||
.attr('x2', length + size)
|
||||
.attr('y2', 0)
|
||||
.attr('stroke-width', rn(size * 3, 2))
|
||||
.attr('stroke-dasharray', dash)
|
||||
.attr('stroke', '#3d3d3d');
|
||||
|
||||
const fontSize = rn(5 * size, 1);
|
||||
scaleBar
|
||||
.selectAll("text")
|
||||
.selectAll('text')
|
||||
.data(d3.range(0, 6))
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("x", d => rn((d * l) / 5, 2))
|
||||
.attr("y", 0)
|
||||
.attr("dy", "-.5em")
|
||||
.attr("font-size", fontSize)
|
||||
.text(d => rn((((d * l) / 5) * dScale) / scale) + (d < 5 ? "" : " " + unit));
|
||||
.append('text')
|
||||
.attr('x', (d) => rn((d * length) / 5, 2))
|
||||
.attr('y', 0)
|
||||
.attr('dy', '-.5em')
|
||||
.attr('font-size', fontSize)
|
||||
.text((d) => rn((((d * length) / 5) * distanceScale) / scaleLevel) + (d < 5 ? '' : ' ' + unit));
|
||||
|
||||
if (barLabel.value !== "") {
|
||||
if (barLabel.value !== '') {
|
||||
scaleBar
|
||||
.append("text")
|
||||
.attr("x", (l + 1) / 2)
|
||||
.attr("y", 2 * size)
|
||||
.attr("dominant-baseline", "text-before-edge")
|
||||
.attr("font-size", fontSize)
|
||||
.append('text')
|
||||
.attr('x', (length + 1) / 2)
|
||||
.attr('y', 2 * size)
|
||||
.attr('dominant-baseline', 'text-before-edge')
|
||||
.attr('font-size', fontSize)
|
||||
.text(barLabel.value);
|
||||
}
|
||||
|
||||
const bbox = scaleBar.node().getBBox();
|
||||
// append backbround rectangle
|
||||
scaleBar
|
||||
.insert("rect", ":first-child")
|
||||
.attr("x", -10)
|
||||
.attr("y", -20)
|
||||
.attr("width", bbox.width + 10)
|
||||
.attr("height", bbox.height + 15)
|
||||
.attr("stroke-width", size)
|
||||
.attr("stroke", "none")
|
||||
.attr("filter", "url(#blur5)")
|
||||
.attr("fill", barBackColor.value)
|
||||
.attr("opacity", +barBackOpacity.value);
|
||||
.insert('rect', ':first-child')
|
||||
.attr('x', -10)
|
||||
.attr('y', -20)
|
||||
.attr('width', bbox.width + 10)
|
||||
.attr('height', bbox.height + 15)
|
||||
.attr('stroke-width', size)
|
||||
.attr('stroke', 'none')
|
||||
.attr('filter', 'url(#blur5)')
|
||||
.attr('fill', barBackColor.value)
|
||||
.attr('opacity', +barBackOpacity.value);
|
||||
|
||||
fitScaleBar();
|
||||
}
|
||||
|
||||
// fit ScaleBar to canvas size
|
||||
function fitScaleBar() {
|
||||
if (!scaleBar.select("rect").size() || scaleBar.style("display") === "none") return;
|
||||
if (!scaleBar.select('rect').size() || scaleBar.style('display') === 'none') return;
|
||||
const px = isNaN(+barPosX.value) ? 0.99 : barPosX.value / 100;
|
||||
const py = isNaN(+barPosY.value) ? 0.99 : barPosY.value / 100;
|
||||
const bbox = scaleBar.select("rect").node().getBBox();
|
||||
const bbox = scaleBar.select('rect').node().getBBox();
|
||||
const x = rn(svgWidth * px - bbox.width + 10),
|
||||
y = rn(svgHeight * py - bbox.height + 20);
|
||||
scaleBar.attr("transform", `translate(${x},${y})`);
|
||||
scaleBar.attr('transform', `translate(${x},${y})`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,9 +199,9 @@ function overviewMilitary() {
|
|||
|
||||
function militaryCustomize() {
|
||||
const types = ['melee', 'ranged', 'mounted', 'machinery', 'naval', 'armored', 'aviation', 'magical'];
|
||||
const table = document.getElementById('militaryOptions').querySelector('tbody');
|
||||
const tableBody = document.getElementById('militaryOptions').querySelector('tbody');
|
||||
removeUnitLines();
|
||||
options.military.map((u) => addUnitLine(u));
|
||||
options.military.map((unit) => addUnitLine(unit));
|
||||
|
||||
$('#militaryOptions').dialog({
|
||||
title: 'Edit Military Units',
|
||||
|
|
@ -219,44 +219,127 @@ function overviewMilitary() {
|
|||
open: function () {
|
||||
const buttons = $(this).dialog('widget').find('.ui-dialog-buttonset > button');
|
||||
buttons[0].addEventListener('mousemove', () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>"));
|
||||
buttons[1].addEventListener('mousemove', () => tip('Add new military unit to the table'));
|
||||
buttons[2].addEventListener('mousemove', () => tip('Restore default military units and settings'));
|
||||
buttons[3].addEventListener('mousemove', () => tip('Close the window without saving the changes'));
|
||||
}
|
||||
});
|
||||
|
||||
if (modules.overviewMilitaryCustomize) return;
|
||||
modules.overviewMilitaryCustomize = true;
|
||||
|
||||
tableBody.addEventListener('click', (event) => {
|
||||
const el = event.target;
|
||||
if (el.tagName !== 'BUTTON') return;
|
||||
const type = el.dataset.type;
|
||||
|
||||
if (type === 'icon') return selectIcon(el.innerHTML, (v) => (el.innerHTML = v));
|
||||
if (type === 'biomes') {
|
||||
const {i, name, color} = biomesData;
|
||||
const biomesArray = Array(i.length).fill(null);
|
||||
const biomes = biomesArray.map((_, i) => ({i, name: name[i], color: color[i]}));
|
||||
return selectLimitation(el, biomes);
|
||||
}
|
||||
if (type === 'states') return selectLimitation(el, pack.states);
|
||||
if (type === 'cultures') return selectLimitation(el, pack.cultures);
|
||||
if (type === 'religions') return selectLimitation(el, pack.religions);
|
||||
});
|
||||
|
||||
function removeUnitLines() {
|
||||
table.querySelectorAll('tr').forEach((el) => el.remove());
|
||||
tableBody.querySelectorAll('tr').forEach((el) => el.remove());
|
||||
}
|
||||
|
||||
function addUnitLine(u) {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon || ' '}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${types
|
||||
.map((t) => `<option ${u.type === t ? 'selected' : ''} value="${t}">${t}</option>`)
|
||||
.join(' ')}</select></td>
|
||||
function getLimitValue(attr) {
|
||||
return attr?.join(',') || '';
|
||||
}
|
||||
|
||||
function getLimitText(attr) {
|
||||
return attr?.length ? 'some' : 'all';
|
||||
}
|
||||
|
||||
function getLimitTip(attr, data) {
|
||||
if (!attr || !attr.length) return '';
|
||||
return attr.map((i) => data?.[i]?.name || '').join(', ');
|
||||
}
|
||||
|
||||
function addUnitLine(unit) {
|
||||
const typeOptions = types.map((t) => `<option ${unit.type === t ? 'selected' : ''} value="${t}">${t}</option>`).join(' ');
|
||||
const getLimitButton = (attr) =>
|
||||
`<button
|
||||
data-tip="Select allowed ${attr}"
|
||||
data-type="${attr}"
|
||||
title="${getLimitTip(unit[attr], pack[attr])}"
|
||||
data-value="${getLimitValue(unit[attr])}">
|
||||
${getLimitText(unit[attr])}
|
||||
</button>`;
|
||||
|
||||
row.innerHTML = `<td><button data-type="icon" data-tip="Click to select unit icon">${unit.icon || ' '}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${unit.name}"></td>
|
||||
<td>${getLimitButton('biomes')}</td>
|
||||
<td>${getLimitButton('states')}</td>
|
||||
<td>${getLimitButton('cultures')}</td>
|
||||
<td>${getLimitButton('religions')}</td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${unit.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${unit.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (for total personnel calculation)" type="number" min=1 step=1 value="${unit.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${unit.power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${typeOptions}</select></td>
|
||||
<td data-tip="Check if unit is separate and can be stacked only with units of the same type">
|
||||
<input id="${u.name}Separate" type="checkbox" class="checkbox" ${u.separate ? 'checked' : ''}>
|
||||
<label for="${u.name}Separate" class="checkbox-label"></label></td>
|
||||
<input id="${unit.name}Separate" type="checkbox" class="checkbox" ${unit.separate ? 'checked' : ''}>
|
||||
<label for="${unit.name}Separate" class="checkbox-label"></label></td>
|
||||
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
|
||||
row.querySelector('button').addEventListener('click', function (e) {
|
||||
selectIcon(this.innerHTML, (v) => (this.innerHTML = v));
|
||||
});
|
||||
table.appendChild(row);
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
removeUnitLines();
|
||||
Military.getDefaultOptions().map((u) => addUnitLine(u));
|
||||
Military.getDefaultOptions().map((unit) => addUnitLine(unit));
|
||||
}
|
||||
|
||||
function selectLimitation(el, data) {
|
||||
const type = el.dataset.type;
|
||||
const value = el.dataset.value;
|
||||
const initial = value ? value.split(',').map((v) => +v) : [];
|
||||
|
||||
const filtered = data.filter((datum) => datum.i && !datum.removed);
|
||||
const lines = filtered.map(
|
||||
({i, name, fullName, color}) =>
|
||||
`<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td>
|
||||
<td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${!initial.length || initial.includes(i) ? 'checked' : ''} >
|
||||
<label for="el${i}" class="checkbox-label">${fullName || name}</label>
|
||||
</td></tr>`
|
||||
);
|
||||
alertMessage.innerHTML = `<b>Limit unit by ${type}:</b><table style="margin-top:.3em"><tbody>${lines.join('')}</tbody></table>`;
|
||||
|
||||
$('#alert').dialog({
|
||||
width: fitContent(),
|
||||
title: `Limit unit`,
|
||||
buttons: {
|
||||
Invert: function () {
|
||||
alertMessage.querySelectorAll('input').forEach((el) => (el.checked = !el.checked));
|
||||
},
|
||||
Apply: function () {
|
||||
const inputs = Array.from(alertMessage.querySelectorAll('input'));
|
||||
const selected = inputs.reduce((acc, input) => {
|
||||
if (input.checked) acc.push(input.dataset.i);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (!selected.length) return tip('Select at least one element', false, 'error');
|
||||
|
||||
const allAreSelected = selected.length === inputs.length;
|
||||
el.dataset.value = allAreSelected ? '' : selected.join(',');
|
||||
el.innerHTML = allAreSelected ? 'all' : 'some';
|
||||
el.setAttribute('title', getLimitTip(selected, data));
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyMilitaryOptions() {
|
||||
const unitLines = Array.from(table.querySelectorAll('tr'));
|
||||
const unitLines = Array.from(tableBody.querySelectorAll('tr'));
|
||||
const names = unitLines.map((r) => r.querySelector('input').value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_'));
|
||||
if (new Set(names).size !== names.length) {
|
||||
tip('All units should have unique names', false, 'error');
|
||||
|
|
@ -265,14 +348,22 @@ function overviewMilitary() {
|
|||
|
||||
$('#militaryOptions').dialog('close');
|
||||
options.military = unitLines.map((r, i) => {
|
||||
const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll('input, select, button')).map((d) => {
|
||||
let value = d.value;
|
||||
if (d.type === 'number') value = +d.value || 0;
|
||||
if (d.type === 'checkbox') value = +d.checked || 0;
|
||||
if (d.type === 'button') value = d.innerHTML || '⠀';
|
||||
return value;
|
||||
const elements = Array.from(r.querySelectorAll('input, button, select'));
|
||||
const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] = elements.map((el) => {
|
||||
const {type, value} = el.dataset || {};
|
||||
if (type === 'icon') return el.innerHTML || '⠀';
|
||||
if (type) return value ? value.split(',').map((v) => parseInt(v)) : null;
|
||||
if (el.type === 'number') return +el.value || 0;
|
||||
if (el.type === 'checkbox') return +el.checked || 0;
|
||||
return el.value;
|
||||
});
|
||||
return {icon, name: names[i], rural, urban, crew, power, type, separate};
|
||||
|
||||
const unit = {icon, name: names[i], rural, urban, crew, power, type, separate};
|
||||
if (biomes) unit.biomes = biomes;
|
||||
if (states) unit.states = states;
|
||||
if (cultures) unit.cultures = cultures;
|
||||
if (religions) unit.religions = religions;
|
||||
return unit;
|
||||
});
|
||||
localStorage.setItem('military', JSON.stringify(options.military));
|
||||
Military.generate();
|
||||
|
|
|
|||
492
modules/ui/military-overview.js.orig
Normal file
492
modules/ui/military-overview.js.orig
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
'use strict';
|
||||
function overviewMilitary() {
|
||||
if (customization) return;
|
||||
closeDialogs('#militaryOverview, .stable');
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
if (!layerIsOn('toggleMilitary')) toggleMilitary();
|
||||
|
||||
const body = document.getElementById('militaryBody');
|
||||
addLines();
|
||||
$('#militaryOverview').dialog();
|
||||
|
||||
if (modules.overviewMilitary) return;
|
||||
modules.overviewMilitary = true;
|
||||
updateHeaders();
|
||||
|
||||
$('#militaryOverview').dialog({
|
||||
title: 'Military Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById('militaryOverviewRefresh').addEventListener('click', addLines);
|
||||
document.getElementById('militaryPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('militaryOptionsButton').addEventListener('click', militaryCustomize);
|
||||
document.getElementById('militaryRegimentsList').addEventListener('click', () => overviewRegiments(-1));
|
||||
document.getElementById('militaryOverviewRecalculate').addEventListener('click', militaryRecalculate);
|
||||
document.getElementById('militaryExport').addEventListener('click', downloadMilitaryData);
|
||||
document.getElementById('militaryWiki').addEventListener('click', () => wiki('Military-Forces'));
|
||||
|
||||
body.addEventListener('change', function (ev) {
|
||||
const el = ev.target,
|
||||
line = el.parentNode,
|
||||
state = +line.dataset.id;
|
||||
changeAlert(state, line, +el.value);
|
||||
});
|
||||
|
||||
body.addEventListener('click', function (ev) {
|
||||
const el = ev.target,
|
||||
line = el.parentNode,
|
||||
state = +line.dataset.id;
|
||||
if (el.tagName === 'SPAN') overviewRegiments(state);
|
||||
});
|
||||
|
||||
// update military types in header and tooltips
|
||||
function updateHeaders() {
|
||||
const header = document.getElementById('militaryHeader');
|
||||
header.querySelectorAll('.removable').forEach((el) => el.remove());
|
||||
const insert = (html) => document.getElementById('militaryTotal').insertAdjacentHTML('beforebegin', html);
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
insert(`<div data-tip="State ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`);
|
||||
}
|
||||
header.querySelectorAll('.removable').forEach(function (e) {
|
||||
e.addEventListener('click', function () {
|
||||
sortLines(this);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add line for each state
|
||||
function addLines() {
|
||||
body.innerHTML = '';
|
||||
let lines = '';
|
||||
const states = pack.states.filter((s) => s.i && !s.removed);
|
||||
|
||||
for (const s of states) {
|
||||
const population = rn((s.rural + s.urban * urbanization) * populationRate);
|
||||
const getForces = (u) => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0);
|
||||
const total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0);
|
||||
const rate = (total / population) * 100;
|
||||
|
||||
const sortData = options.military.map((u) => `data-${u.name}="${getForces(u)}"`).join(' ');
|
||||
const lineData = options.military.map((u) => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(' ');
|
||||
|
||||
lines += `<div class="states" data-id=${s.i} data-state="${
|
||||
s.name
|
||||
}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
|
||||
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
|
||||
s.color
|
||||
}" class="fillRect"></svg>
|
||||
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
|
||||
${lineData}
|
||||
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>
|
||||
<div data-type="population" data-tip="State population">${si(population)}</div>
|
||||
<div data-type="rate" data-tip="Military personnel rate (% of state population). Depends on war alert">${rn(rate, 2)}%</div>
|
||||
<input data-tip="War Alert. Editable modifier to military forces number, depends of political situation" style="width:4.1em" type="number" min=0 step=.01 value="${rn(
|
||||
s.alert,
|
||||
2
|
||||
)}">
|
||||
<span data-tip="Show regiments list" class="icon-list-bullet pointer"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.insertAdjacentHTML('beforeend', lines);
|
||||
updateFooter();
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => stateHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => stateHighlightOff(ev)));
|
||||
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(militaryHeader);
|
||||
}
|
||||
|
||||
function changeAlert(state, line, alert) {
|
||||
const s = pack.states[state];
|
||||
const dif = s.alert || alert ? alert / s.alert : 0; // modifier
|
||||
s.alert = line.dataset.alert = alert;
|
||||
|
||||
s.military.forEach((r) => {
|
||||
Object.keys(r.u).forEach((u) => (r.u[u] = rn(r.u[u] * dif))); // change units value
|
||||
r.a = d3.sum(Object.values(r.u)); // change total
|
||||
armies.select(`g>g#regiment${s.i}-${r.i}>text`).text(Military.getTotal(r)); // change icon text
|
||||
});
|
||||
|
||||
const getForces = (u) => s.military.reduce((s, r) => s + (r.u[u.name] || 0), 0);
|
||||
options.military.forEach((u) => (line.dataset[u.name] = line.querySelector(`div[data-type='${u.name}']`).innerHTML = getForces(u)));
|
||||
|
||||
const population = rn((s.rural + s.urban * urbanization) * populationRate);
|
||||
const total = (line.dataset.total = options.military.reduce((s, u) => s + getForces(u) * u.crew, 0));
|
||||
const rate = (line.dataset.rate = (total / population) * 100);
|
||||
line.querySelector("div[data-type='total']").innerHTML = si(total);
|
||||
line.querySelector("div[data-type='rate']").innerHTML = rn(rate, 2) + '%';
|
||||
|
||||
updateFooter();
|
||||
}
|
||||
|
||||
function updateFooter() {
|
||||
const lines = Array.from(body.querySelectorAll(':scope > div'));
|
||||
const statesNumber = (militaryFooterStates.innerHTML = pack.states.filter((s) => s.i && !s.removed).length);
|
||||
const total = d3.sum(lines.map((el) => el.dataset.total));
|
||||
militaryFooterForcesTotal.innerHTML = si(total);
|
||||
militaryFooterForces.innerHTML = si(total / statesNumber);
|
||||
militaryFooterRate.innerHTML = rn(d3.sum(lines.map((el) => el.dataset.rate)) / statesNumber, 2) + '%';
|
||||
militaryFooterAlert.innerHTML = rn(d3.sum(lines.map((el) => el.dataset.alert)) / statesNumber, 2);
|
||||
}
|
||||
|
||||
function stateHighlightOn(event) {
|
||||
const state = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
armies
|
||||
.select('#army' + state)
|
||||
.transition()
|
||||
.duration(2000)
|
||||
.style('fill', '#ff0000');
|
||||
|
||||
if (!layerIsOn('toggleStates')) return;
|
||||
const d = regions.select('#state' + state).attr('d');
|
||||
|
||||
<<<<<<< HEAD
|
||||
const path = debug.append('path').attr('class', 'highlight').attr('d', d).attr('fill', 'none').attr('stroke', 'red').attr('stroke-width', 1).attr('opacity', 1).attr('filter', 'url(#blur1)');
|
||||
=======
|
||||
const path = debug
|
||||
.append("path")
|
||||
.attr("class", "highlight")
|
||||
.attr("d", d)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "red")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("opacity", 1)
|
||||
.attr("filter", "url(#blur1)");
|
||||
>>>>>>> master
|
||||
|
||||
const l = path.node().getTotalLength(),
|
||||
dur = (l + 5000) / 2;
|
||||
const i = d3.interpolateString('0,' + l, l + ',' + l);
|
||||
path
|
||||
.transition()
|
||||
.duration(dur)
|
||||
.attrTween('stroke-dasharray', function () {
|
||||
return (t) => i(t);
|
||||
});
|
||||
}
|
||||
|
||||
function stateHighlightOff(event) {
|
||||
debug.selectAll('.highlight').each(function () {
|
||||
d3.select(this).transition().duration(1000).attr('opacity', 0).remove();
|
||||
});
|
||||
|
||||
const state = +event.target.dataset.id;
|
||||
armies
|
||||
.select('#army' + state)
|
||||
.transition()
|
||||
.duration(1000)
|
||||
.style('fill', null);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const lines = body.querySelectorAll(':scope > div');
|
||||
const array = Array.from(lines),
|
||||
cache = [];
|
||||
|
||||
const total = function (type) {
|
||||
if (cache[type]) cache[type];
|
||||
cache[type] = d3.sum(array.map((el) => +el.dataset[type]));
|
||||
return cache[type];
|
||||
};
|
||||
|
||||
lines.forEach(function (el) {
|
||||
el.querySelectorAll('div').forEach(function (div) {
|
||||
const type = div.dataset.type;
|
||||
if (type === 'rate') return;
|
||||
div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + '%' : '0%';
|
||||
});
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = 'absolute';
|
||||
addLines();
|
||||
}
|
||||
}
|
||||
|
||||
function militaryCustomize() {
|
||||
<<<<<<< HEAD
|
||||
const types = ['melee', 'ranged', 'mounted', 'machinery', 'naval', 'armored', 'aviation', 'magical'];
|
||||
const table = document.getElementById('militaryOptions').querySelector('tbody');
|
||||
removeUnitLines();
|
||||
options.military.map((u) => addUnitLine(u));
|
||||
=======
|
||||
const types = ["melee", "ranged", "mounted", "machinery", "naval", "armored", "aviation", "magical"];
|
||||
const tableBody = document.getElementById("militaryOptions").querySelector("tbody");
|
||||
removeUnitLines();
|
||||
options.military.map(unit => addUnitLine(unit));
|
||||
>>>>>>> master
|
||||
|
||||
$('#militaryOptions').dialog({
|
||||
title: 'Edit Military Units',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Apply: applyMilitaryOptions,
|
||||
Add: () => addUnitLine({icon: '🛡️', name: 'custom' + militaryOptionsTable.rows.length, rural: 0.2, urban: 0.5, crew: 1, power: 1, type: 'melee'}),
|
||||
Restore: restoreDefaultUnits,
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
open: function () {
|
||||
<<<<<<< HEAD
|
||||
const buttons = $(this).dialog('widget').find('.ui-dialog-buttonset > button');
|
||||
buttons[0].addEventListener('mousemove', () => tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>"));
|
||||
buttons[1].addEventListener('mousemove', () => tip('Add new military unit to the table'));
|
||||
buttons[2].addEventListener('mousemove', () => tip('Restore default military units and settings'));
|
||||
buttons[3].addEventListener('mousemove', () => tip('Close the window without saving the changes'));
|
||||
=======
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
||||
buttons[0].addEventListener("mousemove", () =>
|
||||
tip("Apply military units settings. <span style='color:#cb5858'>All forces will be recalculated!</span>")
|
||||
);
|
||||
buttons[1].addEventListener("mousemove", () => tip("Add new military unit to the table"));
|
||||
buttons[2].addEventListener("mousemove", () => tip("Restore default military units and settings"));
|
||||
buttons[3].addEventListener("mousemove", () => tip("Close the window without saving the changes"));
|
||||
>>>>>>> master
|
||||
}
|
||||
});
|
||||
|
||||
if (modules.overviewMilitaryCustomize) return;
|
||||
modules.overviewMilitaryCustomize = true;
|
||||
|
||||
tableBody.addEventListener("click", event => {
|
||||
const el = event.target;
|
||||
if (el.tagName !== "BUTTON") return;
|
||||
const type = el.dataset.type;
|
||||
|
||||
if (type === "icon") return selectIcon(el.innerHTML, v => (el.innerHTML = v));
|
||||
if (type === "biomes") {
|
||||
const {i, name, color} = biomesData;
|
||||
const biomesArray = Array(i.length).fill(null);
|
||||
const biomes = biomesArray.map((_, i) => ({i, name: name[i], color: color[i]}));
|
||||
return selectLimitation(el, biomes);
|
||||
}
|
||||
if (type === "states") return selectLimitation(el, pack.states);
|
||||
if (type === "cultures") return selectLimitation(el, pack.cultures);
|
||||
if (type === "religions") return selectLimitation(el, pack.religions);
|
||||
});
|
||||
|
||||
function removeUnitLines() {
|
||||
<<<<<<< HEAD
|
||||
table.querySelectorAll('tr').forEach((el) => el.remove());
|
||||
}
|
||||
|
||||
function addUnitLine(u) {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `<td><button type="button" data-tip="Click to select unit icon">${u.icon || ' '}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${u.name}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${u.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${u.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (used for total personnel calculation)" type="number" min=1 step=1 value="${u.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${u.power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${types
|
||||
.map((t) => `<option ${u.type === t ? 'selected' : ''} value="${t}">${t}</option>`)
|
||||
.join(' ')}</select></td>
|
||||
<td data-tip="Check if unit is separate and can be stacked only with units of the same type">
|
||||
<input id="${u.name}Separate" type="checkbox" class="checkbox" ${u.separate ? 'checked' : ''}>
|
||||
<label for="${u.name}Separate" class="checkbox-label"></label></td>
|
||||
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
|
||||
row.querySelector('button').addEventListener('click', function (e) {
|
||||
selectIcon(this.innerHTML, (v) => (this.innerHTML = v));
|
||||
});
|
||||
table.appendChild(row);
|
||||
=======
|
||||
tableBody.querySelectorAll("tr").forEach(el => el.remove());
|
||||
}
|
||||
|
||||
function getLimitValue(attr) {
|
||||
return attr?.join(",") || "";
|
||||
}
|
||||
|
||||
function getLimitText(attr) {
|
||||
return attr?.length ? "some" : "all";
|
||||
}
|
||||
|
||||
function getLimitTip(attr, data) {
|
||||
if (!attr || !attr.length) return "";
|
||||
return attr.map(i => data?.[i]?.name || "").join(", ");
|
||||
}
|
||||
|
||||
function addUnitLine(unit) {
|
||||
const row = document.createElement("tr");
|
||||
const typeOptions = types.map(t => `<option ${unit.type === t ? "selected" : ""} value="${t}">${t}</option>`).join(" ");
|
||||
const getLimitButton = attr =>
|
||||
`<button
|
||||
data-tip="Select allowed ${attr}"
|
||||
data-type="${attr}"
|
||||
title="${getLimitTip(unit[attr], pack[attr])}"
|
||||
data-value="${getLimitValue(unit[attr])}">
|
||||
${getLimitText(unit[attr])}
|
||||
</button>`;
|
||||
|
||||
row.innerHTML = `<td><button data-type="icon" data-tip="Click to select unit icon">${unit.icon || " "}</button></td>
|
||||
<td><input data-tip="Type unit name. If name is changed for existing unit, old unit will be replaced" value="${unit.name}"></td>
|
||||
<td>${getLimitButton("biomes")}</td>
|
||||
<td>${getLimitButton("states")}</td>
|
||||
<td>${getLimitButton("cultures")}</td>
|
||||
<td>${getLimitButton("religions")}</td>
|
||||
<td><input data-tip="Enter conscription percentage for rural population" type="number" min=0 max=100 step=.01 value="${unit.rural}"></td>
|
||||
<td><input data-tip="Enter conscription percentage for urban population" type="number" min=0 max=100 step=.01 value="${unit.urban}"></td>
|
||||
<td><input data-tip="Enter average number of people in crew (for total personnel calculation)" type="number" min=1 step=1 value="${unit.crew}"></td>
|
||||
<td><input data-tip="Enter military power (used for battle simulation)" type="number" min=0 step=.1 value="${unit.power}"></td>
|
||||
<td><select data-tip="Select unit type to apply special rules on forces recalculation">${typeOptions}</select></td>
|
||||
<td data-tip="Check if unit is separate and can be stacked only with units of the same type">
|
||||
<input id="${unit.name}Separate" type="checkbox" class="checkbox" ${unit.separate ? "checked" : ""}>
|
||||
<label for="${unit.name}Separate" class="checkbox-label"></label></td>
|
||||
<td data-tip="Remove the unit"><span data-tip="Remove unit type" class="icon-trash-empty pointer" onclick="this.parentElement.parentElement.remove();"></span></td>`;
|
||||
tableBody.appendChild(row);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
removeUnitLines();
|
||||
<<<<<<< HEAD
|
||||
Military.getDefaultOptions().map((u) => addUnitLine(u));
|
||||
}
|
||||
|
||||
function applyMilitaryOptions() {
|
||||
const unitLines = Array.from(table.querySelectorAll('tr'));
|
||||
const names = unitLines.map((r) => r.querySelector('input').value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, '_'));
|
||||
=======
|
||||
Military.getDefaultOptions().map(unit => addUnitLine(unit));
|
||||
}
|
||||
|
||||
function selectLimitation(el, data) {
|
||||
const type = el.dataset.type;
|
||||
const value = el.dataset.value;
|
||||
const initial = value ? value.split(",").map(v => +v) : [];
|
||||
|
||||
const filtered = data.filter(datum => datum.i && !datum.removed);
|
||||
const lines = filtered.map(
|
||||
({i, name, fullName, color}) =>
|
||||
`<tr data-tip="${name}"><td><span style="color:${color}">⬤</span></td>
|
||||
<td><input data-i="${i}" id="el${i}" type="checkbox" class="checkbox" ${!initial.length || initial.includes(i) ? "checked" : ""} >
|
||||
<label for="el${i}" class="checkbox-label">${fullName || name}</label>
|
||||
</td></tr>`
|
||||
);
|
||||
alertMessage.innerHTML = `<b>Limit unit by ${type}:</b><table style="margin-top:.3em"><tbody>${lines.join("")}</tbody></table>`;
|
||||
|
||||
$("#alert").dialog({
|
||||
width: fitContent(),
|
||||
title: `Limit unit`,
|
||||
buttons: {
|
||||
Invert: function () {
|
||||
alertMessage.querySelectorAll("input").forEach(el => (el.checked = !el.checked));
|
||||
},
|
||||
Apply: function () {
|
||||
const inputs = Array.from(alertMessage.querySelectorAll("input"));
|
||||
const selected = inputs.reduce((acc, input) => {
|
||||
if (input.checked) acc.push(input.dataset.i);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (!selected.length) return tip("Select at least one element", false, "error");
|
||||
|
||||
const allAreSelected = selected.length === inputs.length;
|
||||
el.dataset.value = allAreSelected ? "" : selected.join(",");
|
||||
el.innerHTML = allAreSelected ? "all" : "some";
|
||||
el.setAttribute("title", getLimitTip(selected, data));
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applyMilitaryOptions() {
|
||||
const unitLines = Array.from(tableBody.querySelectorAll("tr"));
|
||||
const names = unitLines.map(r => r.querySelector("input").value.replace(/[&\/\\#, +()$~%.'":*?<>{}]/g, "_"));
|
||||
>>>>>>> master
|
||||
if (new Set(names).size !== names.length) {
|
||||
tip('All units should have unique names', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$('#militaryOptions').dialog('close');
|
||||
options.military = unitLines.map((r, i) => {
|
||||
<<<<<<< HEAD
|
||||
const [icon, name, rural, urban, crew, power, type, separate] = Array.from(r.querySelectorAll('input, select, button')).map((d) => {
|
||||
let value = d.value;
|
||||
if (d.type === 'number') value = +d.value || 0;
|
||||
if (d.type === 'checkbox') value = +d.checked || 0;
|
||||
if (d.type === 'button') value = d.innerHTML || '⠀';
|
||||
return value;
|
||||
=======
|
||||
const elements = Array.from(r.querySelectorAll("input, button, select"));
|
||||
const [icon, name, biomes, states, cultures, religions, rural, urban, crew, power, type, separate] = elements.map(el => {
|
||||
const {type, value} = el.dataset || {};
|
||||
if (type === "icon") return el.innerHTML || "⠀";
|
||||
if (type) return value ? value.split(",").map(v => parseInt(v)) : null;
|
||||
if (el.type === "number") return +el.value || 0;
|
||||
if (el.type === "checkbox") return +el.checked || 0;
|
||||
return el.value;
|
||||
>>>>>>> master
|
||||
});
|
||||
|
||||
const unit = {icon, name: names[i], rural, urban, crew, power, type, separate};
|
||||
if (biomes) unit.biomes = biomes;
|
||||
if (states) unit.states = states;
|
||||
if (cultures) unit.cultures = cultures;
|
||||
if (religions) unit.religions = religions;
|
||||
return unit;
|
||||
});
|
||||
localStorage.setItem('military', JSON.stringify(options.military));
|
||||
Military.generate();
|
||||
updateHeaders();
|
||||
addLines();
|
||||
}
|
||||
}
|
||||
|
||||
function militaryRecalculate() {
|
||||
alertMessage.innerHTML = 'Are you sure you want to recalculate military forces for all states?<br>Regiments for all states will be regenerated';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove regiment',
|
||||
buttons: {
|
||||
Recalculate: function () {
|
||||
$(this).dialog('close');
|
||||
Military.generate();
|
||||
addLines();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function downloadMilitaryData() {
|
||||
const units = options.military.map((u) => u.name);
|
||||
let data = 'Id,State,' + units.map((u) => capitalize(u)).join(',') + ',Total,Population,Rate,War Alert\n'; // headers
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.state + ',';
|
||||
data += units.map((u) => el.dataset[u]).join(',') + ',';
|
||||
data += el.dataset.total + ',';
|
||||
data += el.dataset.population + ',';
|
||||
data += rn(el.dataset.rate, 2) + '%,';
|
||||
data += el.dataset.alert + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName('Military') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
function editNotes(id, name) {
|
||||
// update list of objects
|
||||
const select = document.getElementById('notesSelect');
|
||||
|
|
@ -8,11 +9,12 @@ function editNotes(id, name) {
|
|||
}
|
||||
|
||||
// initiate pell (html editor)
|
||||
const notesText = document.getElementById('notesText');
|
||||
notesText.innerHTML = '';
|
||||
const editor = Pell.init({
|
||||
element: document.getElementById('notesText'),
|
||||
element: notesText,
|
||||
onChange: (html) => {
|
||||
const id = document.getElementById('notesSelect').value;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
const note = notes.find((note) => note.id === select.value);
|
||||
if (!note) return;
|
||||
note.legend = html;
|
||||
showNote(note);
|
||||
|
|
@ -43,8 +45,7 @@ function editNotes(id, name) {
|
|||
title: 'Notes Editor',
|
||||
minWidth: '40em',
|
||||
width: '50vw',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
close: () => (notesText.innerHTML = '')
|
||||
position: {my: 'center', at: 'center', of: 'svg'}
|
||||
});
|
||||
|
||||
if (modules.editNotes) return;
|
||||
|
|
@ -107,7 +108,7 @@ function editNotes(id, name) {
|
|||
return;
|
||||
}
|
||||
|
||||
highlightElement(element); // if element is found
|
||||
highlightElement(element, 3); // if element is found
|
||||
}
|
||||
|
||||
function downloadLegends() {
|
||||
|
|
|
|||
183
modules/ui/notes-editor.js.orig
Normal file
183
modules/ui/notes-editor.js.orig
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<<<<<<< HEAD
|
||||
'use strict';
|
||||
=======
|
||||
"use strict";
|
||||
|
||||
>>>>>>> master
|
||||
function editNotes(id, name) {
|
||||
// update list of objects
|
||||
const select = document.getElementById('notesSelect');
|
||||
select.options.length = 0;
|
||||
for (const note of notes) {
|
||||
select.options.add(new Option(note.id, note.id));
|
||||
}
|
||||
|
||||
// initiate pell (html editor)
|
||||
const notesText = document.getElementById("notesText");
|
||||
notesText.innerHTML = "";
|
||||
const editor = Pell.init({
|
||||
<<<<<<< HEAD
|
||||
element: document.getElementById('notesText'),
|
||||
onChange: (html) => {
|
||||
const id = document.getElementById('notesSelect').value;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
=======
|
||||
element: notesText,
|
||||
onChange: html => {
|
||||
const note = notes.find(note => note.id === select.value);
|
||||
>>>>>>> master
|
||||
if (!note) return;
|
||||
note.legend = html;
|
||||
showNote(note);
|
||||
}
|
||||
});
|
||||
|
||||
// select an object
|
||||
if (notes.length || id) {
|
||||
if (!id) id = notes[0].id;
|
||||
let note = notes.find((note) => note.id === id);
|
||||
if (note === undefined) {
|
||||
if (!name) name = id;
|
||||
note = {id, name, legend: ''};
|
||||
notes.push(note);
|
||||
select.options.add(new Option(id, id));
|
||||
}
|
||||
select.value = id;
|
||||
notesName.value = note.name;
|
||||
editor.content.innerHTML = note.legend;
|
||||
showNote(note);
|
||||
} else {
|
||||
editor.content.innerHTML = 'There are no added notes. Click on element (e.g. label) and add a free text note';
|
||||
document.getElementById('notesName').value = '';
|
||||
}
|
||||
|
||||
// open a dialog
|
||||
<<<<<<< HEAD
|
||||
$('#notesEditor').dialog({
|
||||
title: 'Notes Editor',
|
||||
minWidth: '40em',
|
||||
width: '50vw',
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
close: () => (notesText.innerHTML = '')
|
||||
=======
|
||||
$("#notesEditor").dialog({
|
||||
title: "Notes Editor",
|
||||
minWidth: "40em",
|
||||
width: "50vw",
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
>>>>>>> master
|
||||
});
|
||||
|
||||
if (modules.editNotes) return;
|
||||
modules.editNotes = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById('notesSelect').addEventListener('change', changeObject);
|
||||
document.getElementById('notesName').addEventListener('input', changeName);
|
||||
document.getElementById('notesPin').addEventListener('click', () => (options.pinNotes = !options.pinNotes));
|
||||
document.getElementById('notesSpeak').addEventListener('click', () => speak(editor.content.innerHTML));
|
||||
document.getElementById('notesFocus').addEventListener('click', validateHighlightElement);
|
||||
document.getElementById('notesDownload').addEventListener('click', downloadLegends);
|
||||
document.getElementById('notesUpload').addEventListener('click', () => legendsToLoad.click());
|
||||
document.getElementById('legendsToLoad').addEventListener('change', function () {
|
||||
uploadFile(this, uploadLegends);
|
||||
});
|
||||
document.getElementById('notesClearStyle').addEventListener('click', clearStyle);
|
||||
document.getElementById('notesRemove').addEventListener('click', triggerNotesRemove);
|
||||
|
||||
function showNote(note) {
|
||||
document.getElementById('notes').style.display = 'block';
|
||||
document.getElementById('notesHeader').innerHTML = note.name;
|
||||
document.getElementById('notesBody').innerHTML = note.legend;
|
||||
}
|
||||
|
||||
function changeObject() {
|
||||
const note = notes.find((note) => note.id === this.value);
|
||||
if (!note) return;
|
||||
notesName.value = note.name;
|
||||
editor.content.innerHTML = note.legend;
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
const id = document.getElementById('notesSelect').value;
|
||||
const note = notes.find((note) => note.id === id);
|
||||
if (!note) return;
|
||||
note.name = this.value;
|
||||
showNote(note);
|
||||
}
|
||||
|
||||
function validateHighlightElement() {
|
||||
const select = document.getElementById('notesSelect');
|
||||
const element = document.getElementById(select.value);
|
||||
|
||||
if (element === null) {
|
||||
alertMessage.innerHTML = 'Related element is not found. Would you like to remove the note?';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Element not found',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
removeLegend();
|
||||
},
|
||||
Keep: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
highlightElement(element, 3); // if element is found
|
||||
}
|
||||
|
||||
function downloadLegends() {
|
||||
const data = JSON.stringify(notes);
|
||||
const name = getFileName('Notes') + '.txt';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function uploadLegends(dataLoaded) {
|
||||
if (!dataLoaded) {
|
||||
tip('Cannot load the file. Please check the data format', false, 'error');
|
||||
return;
|
||||
}
|
||||
notes = JSON.parse(dataLoaded);
|
||||
document.getElementById('notesSelect').options.length = 0;
|
||||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
|
||||
function clearStyle() {
|
||||
editor.content.innerHTML = editor.content.textContent;
|
||||
}
|
||||
|
||||
function triggerNotesRemove() {
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the selected note?';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove note',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
removeLegend();
|
||||
},
|
||||
Keep: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeLegend() {
|
||||
const select = document.getElementById('notesSelect');
|
||||
const index = notes.findIndex((n) => n.id === select.value);
|
||||
notes.splice(index, 1);
|
||||
select.options.length = 0;
|
||||
if (!notes.length) {
|
||||
$('#notesEditor').dialog('close');
|
||||
return;
|
||||
}
|
||||
notesText.innerHTML = '';
|
||||
editNotes(notes[0].id, notes[0].name);
|
||||
}
|
||||
}
|
||||
|
|
@ -89,17 +89,19 @@ function showSupporters() {
|
|||
Maxwell Hill,Drunken_Legends,rob bee,Jesse Holmes,YYako,Detocroix,Anoplexian,Hannah,Paul,Sandra Krohn,Lucid,Richard Keating,Allen Varney,Rick Falkvinge,
|
||||
Seth Fusion,Adam Butler,Gus,StroboWolf,Sadie Blackthorne,Zewen Senpai,Dell McKnight,Oneiris,Darinius Dragonclaw Studios,Christopher Whitney,Rhodes HvZ,
|
||||
Jeppe Skov Jensen,María Martín López,Martin Seeger,Annie Rishor,Aram Sabatés,MadNomadMedia,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,
|
||||
Thirty-OneR ,ThatGuyGW ,Dee Chiu,MontyBoosh ,Achillain ,Jaden ,SashaTK,Steve Johnson,Eric Foley,Vito Martono,James H. Anthony,Kevin Cossutta,Thirty-OneR,
|
||||
ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,Andrew Rostaing,Daniel Gill,
|
||||
Char,Jack,Barna Csíkos,Ian Rousseau,Nicholas Grabstas,Tom Van Orden jr,Bryan Brake,Akylos,Riley Seaman,MaxOliver,Evan-DiLeo,Alex Debus,Joshua Vaught,
|
||||
Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,Radovan Zapletal,Jmmat6,
|
||||
Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,Guilherme Aguiar,Jarno Hallikainen,
|
||||
Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,Cooper Counts,Patrick Jones,Clonetone,
|
||||
PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram,
|
||||
Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,
|
||||
Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,
|
||||
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,
|
||||
"Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas"`;
|
||||
Thirty-OneR,ThatGuyGW,Dee Chiu,MontyBoosh,Achillain,Jaden,SashaTK,Steve Johnson,Pierrick Bertrand,Jared Kennedy,Dylan Devenny,Kyle Robertson,
|
||||
Andrew Rostaing,Daniel Gill,Char,Jack,Barna Csíkos,Ian Rousseau,Nicholas Grabstas,Tom Van Orden jr,Bryan Brake,Akylos,Riley Seaman,MaxOliver,Evan-DiLeo,
|
||||
Alex Debus,Joshua Vaught,Kyle S,Eric Moore,Dean Dunakin,Uniquenameosaurus,WarWizardGames,Chance Mena,Jan Ka,Miguel Alejandro,Dalton Clark,Simon Drapeau,
|
||||
Radovan Zapletal,Jmmat6,Justa Badge,Blargh Blarghmoomoo,Vanessa Anjos,Grant A. Murray,Akirsop,Rikard Wolff,Jake Fish,teco 47,Antiroo,Jakob Siegel,
|
||||
Guilherme Aguiar,Jarno Hallikainen,Justin Mcclain,Kristin Chernoff,Rowland Kingman,Esther Busch,Grayson McClead,Austin,Hakon the Viking,Chad Riley,
|
||||
Cooper Counts,Patrick Jones,Clonetone,PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,
|
||||
Page One Project,Spencer Morris,Paul Ingram,Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,
|
||||
Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,
|
||||
PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,
|
||||
Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,
|
||||
Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,
|
||||
Mike Conley,Xavier privé,Hope You're Well,Mark Sprietsma,Robert Landry,Nick Mowry,steve hall,Markell,Josh Wren,Neutrix,BLRageQuit,Rocky,
|
||||
Dario Spadavecchia,Bas Kroot,John Patrick Callahan Jr,Alexandra Vesey,D`;
|
||||
|
||||
const array = supporters
|
||||
.replace(/(?:\r\n|\r|\n)/g, '')
|
||||
|
|
@ -148,7 +150,9 @@ optionsContent.addEventListener('input', function (event) {
|
|||
else if (id === 'regionsInput' || id === 'regionsOutput') changeStatesNumber(value);
|
||||
else if (id === 'emblemShape') changeEmblemShape(value);
|
||||
else if (id === 'tooltipSizeInput' || id === 'tooltipSizeOutput') changeTooltipSize(value);
|
||||
else if (id === 'transparencyInput') changeDialogsTransparency(value);
|
||||
else if (id === "themeHueInput") changeThemeHue(value);
|
||||
else if (id === "themeColorInput") changeDialogsTheme(themeColorInput.value, transparencyInput.value);
|
||||
else if (id === "transparencyInput") changeDialogsTheme(themeColorInput.value, value);
|
||||
});
|
||||
|
||||
optionsContent.addEventListener('change', function (event) {
|
||||
|
|
@ -156,23 +160,24 @@ optionsContent.addEventListener('change', function (event) {
|
|||
const value = event.target.value;
|
||||
|
||||
if (id === 'zoomExtentMin' || id === 'zoomExtentMax') changeZoomExtent(value);
|
||||
else if (id === 'optionsSeed') generateMapWithSeed();
|
||||
else if (id === "optionsSeed") generateMapWithSeed("seed change");
|
||||
else if (id === 'uiSizeInput' || id === 'uiSizeOutput') changeUIsize(value);
|
||||
if (id === 'shapeRendering') viewbox.attr('shape-rendering', value);
|
||||
else if (id === 'yearInput') changeYear();
|
||||
else if (id === 'eraInput') changeEra();
|
||||
else if (id === "stateLabelsModeInput") options.stateLabelsMode = value;
|
||||
});
|
||||
|
||||
optionsContent.addEventListener('click', function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === 'toggleFullscreen') toggleFullscreen();
|
||||
else if (id === 'optionsSeedGenerate') generateMapWithSeed();
|
||||
else if (id === 'optionsMapHistory') showSeedHistoryDialog();
|
||||
else if (id === 'optionsCopySeed') copyMapURL();
|
||||
else if (id === 'optionsEraRegenerate') regenerateEra();
|
||||
else if (id === 'zoomExtentDefault') restoreDefaultZoomExtent();
|
||||
else if (id === 'translateExtent') toggleTranslateExtent(event.target);
|
||||
else if (id === 'speakerTest') testSpeaker();
|
||||
else if (id === "themeColorRestore") restoreDefaultThemeColor();
|
||||
});
|
||||
|
||||
function mapSizeInputChange() {
|
||||
|
|
@ -206,8 +211,8 @@ function changeMapSize() {
|
|||
|
||||
// just apply canvas size that was already set
|
||||
function applyMapSize() {
|
||||
const zoomMin = +zoomExtentMin.value,
|
||||
zoomMax = +zoomExtentMax.value;
|
||||
const zoomMin = +zoomExtentMin.value;
|
||||
const zoomMax = +zoomExtentMax.value;
|
||||
graphWidth = +mapWidthInput.value;
|
||||
graphHeight = +mapHeightInput.value;
|
||||
svgWidth = Math.min(graphWidth, window.innerWidth);
|
||||
|
|
@ -275,12 +280,9 @@ function testSpeaker() {
|
|||
speechSynthesis.speak(speaker);
|
||||
}
|
||||
|
||||
function generateMapWithSeed() {
|
||||
if (optionsSeed.value == seed) {
|
||||
tip('The current map already has this seed', false, 'error');
|
||||
return;
|
||||
}
|
||||
regeneratePrompt();
|
||||
function generateMapWithSeed(source) {
|
||||
if (optionsSeed.value == seed) return tip("The current map already has this seed", false, "error");
|
||||
regeneratePrompt(source);
|
||||
}
|
||||
|
||||
function showSeedHistoryDialog() {
|
||||
|
|
@ -311,7 +313,7 @@ function restoreSeed(id) {
|
|||
mapHeightInput.value = mapHistory[id].height;
|
||||
templateInput.value = mapHistory[id].template;
|
||||
if (locked('template')) unlock('template');
|
||||
regeneratePrompt();
|
||||
regeneratePrompt("seed history");
|
||||
}
|
||||
|
||||
function restoreDefaultZoomExtent() {
|
||||
|
|
@ -415,7 +417,7 @@ function changeUIsize(value) {
|
|||
if (+value > max) value = max;
|
||||
|
||||
uiSizeInput.value = uiSizeOutput.value = value;
|
||||
document.getElementsByTagName('body')[0].style.fontSize = value * 11 + 'px';
|
||||
document.getElementsByTagName("body")[0].style.fontSize = rn(value * 10, 2) + "px";
|
||||
document.getElementById('options').style.width = value * 300 + 'px';
|
||||
}
|
||||
|
||||
|
|
@ -427,26 +429,56 @@ function changeTooltipSize(value) {
|
|||
tooltip.style.fontSize = `calc(${value}px + 0.5vw)`;
|
||||
}
|
||||
|
||||
// change transparency for modal windows
|
||||
function changeDialogsTransparency(value) {
|
||||
transparencyInput.value = transparencyOutput.value = value;
|
||||
const alpha = (100 - +value) / 100;
|
||||
const optionsColor = 'rgba(164, 139, 149, ' + alpha + ')';
|
||||
const dialogsColor = 'rgba(255, 255, 255, ' + alpha + ')';
|
||||
const optionButtonsColor = 'rgba(145, 110, 127, ' + Math.min(alpha + 0.3, 1) + ')';
|
||||
const optionLiColor = 'rgba(153, 123, 137, ' + Math.min(alpha + 0.3, 1) + ')';
|
||||
document.getElementById('options').style.backgroundColor = optionsColor;
|
||||
document.getElementById('dialogs').style.backgroundColor = dialogsColor;
|
||||
document.querySelectorAll('.tabcontent button').forEach((el) => (el.style.backgroundColor = optionButtonsColor));
|
||||
document.querySelectorAll('.tabcontent li').forEach((el) => (el.style.backgroundColor = optionLiColor));
|
||||
document.querySelectorAll('button.options').forEach((el) => (el.style.backgroundColor = optionLiColor));
|
||||
const THEME_COLOR = "#997787";
|
||||
function restoreDefaultThemeColor() {
|
||||
localStorage.removeItem("themeColor");
|
||||
changeDialogsTheme(THEME_COLOR, transparencyInput.value);
|
||||
}
|
||||
|
||||
function changeThemeHue(hue) {
|
||||
const {s, l} = d3.hsl(themeColorInput.value);
|
||||
const newColor = d3.hsl(+hue, s, l).hex();
|
||||
changeDialogsTheme(newColor, transparencyInput.value);
|
||||
}
|
||||
|
||||
// change color and transparency for modal windows
|
||||
function changeDialogsTheme(themeColor, transparency) {
|
||||
transparencyInput.value = transparencyOutput.value = transparency;
|
||||
const alpha = (100 - +transparency) / 100;
|
||||
const alphaReduced = Math.min(alpha + 0.3, 1);
|
||||
|
||||
const {h, s, l} = d3.hsl(themeColor || THEME_COLOR);
|
||||
themeColorInput.value = themeColor || THEME_COLOR;
|
||||
themeHueInput.value = h;
|
||||
|
||||
const getRGBA = (hue, saturation, lightness, alpha) => {
|
||||
const color = d3.hsl(hue, saturation, lightness, alpha);
|
||||
return color.toString();
|
||||
};
|
||||
|
||||
const theme = [
|
||||
{name: "--bg-main", h, s, l, alpha},
|
||||
{name: "--bg-lighter", h, s, l: l + 0.02, alpha},
|
||||
{name: "--bg-light", h, s: s - 0.02, l: l + 0.06, alpha},
|
||||
{name: "--light-solid", h, s: s + 0.01, l: l + 0.05, alpha: 1},
|
||||
{name: "--dark-solid", h, s, l: l - 0.2, alpha: 1},
|
||||
{name: "--header", h, s: s, l: l - 0.03, alpha: alphaReduced},
|
||||
{name: "--header-active", h, s: s, l: l - 0.09, alpha: alphaReduced},
|
||||
{name: "--bg-disabled", h, s: s - 0.04, l: l + 0.09, alphaReduced},
|
||||
{name: "--bg-dialogs", h: 0, s: 0, l: 0.98, alpha}
|
||||
];
|
||||
|
||||
const sx = document.documentElement.style;
|
||||
theme.forEach(({name, h, s, l, alpha}) => {
|
||||
sx.setProperty(name, getRGBA(h, s, l, alpha));
|
||||
});
|
||||
}
|
||||
|
||||
function changeZoomExtent(value) {
|
||||
const min = Math.max(+zoomExtentMin.value, 0.01),
|
||||
max = Math.min(+zoomExtentMax.value, 200);
|
||||
const min = Math.max(+zoomExtentMin.value, 0.01);
|
||||
const max = Math.min(+zoomExtentMax.value, 200);
|
||||
zoom.scaleExtent([min, max]);
|
||||
const scale = Math.max(Math.min(+value, 200), 0.01);
|
||||
const scale = minmax(+value, 0.01, 200);
|
||||
zoom.scaleTo(svg, scale);
|
||||
}
|
||||
|
||||
|
|
@ -482,13 +514,12 @@ function applyStoredOptions() {
|
|||
.map((w) => +w);
|
||||
if (localStorage.getItem('military')) options.military = JSON.parse(localStorage.getItem('military'));
|
||||
|
||||
changeDialogsTransparency(localStorage.getItem('transparency') || 5);
|
||||
if (localStorage.getItem('tooltipSize')) changeTooltipSize(localStorage.getItem('tooltipSize'));
|
||||
if (localStorage.getItem('regions')) changeStatesNumber(localStorage.getItem('regions'));
|
||||
|
||||
uiSizeInput.max = uiSizeOutput.max = getUImaxSize();
|
||||
if (localStorage.getItem('uiSize')) changeUIsize(localStorage.getItem('uiSize'));
|
||||
else changeUIsize(Math.max(Math.min(rn(mapWidthInput.value / 1280, 1), 2.5), 1));
|
||||
else changeUIsize(minmax(rn(mapWidthInput.value / 1280, 1), 1, 2.5));
|
||||
|
||||
// search params overwrite stored and default options
|
||||
const params = new URL(window.location.href).searchParams;
|
||||
|
|
@ -497,8 +528,14 @@ function applyStoredOptions() {
|
|||
if (width) mapWidthInput.value = width;
|
||||
if (height) mapHeightInput.value = height;
|
||||
|
||||
const transparency = localStorage.getItem("transparency") || 5;
|
||||
const themeColor = localStorage.getItem("themeColor");
|
||||
changeDialogsTheme(themeColor, transparency);
|
||||
|
||||
// set shape rendering
|
||||
viewbox.attr('shape-rendering', shapeRendering.value);
|
||||
|
||||
options.stateLabelsMode = stateLabelsModeInput.value;
|
||||
}
|
||||
|
||||
// randomize options if randomization is allowed (not locked or options='default')
|
||||
|
|
@ -529,10 +566,9 @@ function randomizeOptions() {
|
|||
|
||||
// 'Units Editor' settings
|
||||
const US = navigator.language === 'en-US';
|
||||
const UK = navigator.language === 'en-GB';
|
||||
if (randomize || !locked('distanceScale')) distanceScaleOutput.value = distanceScaleInput.value = gauss(3, 1, 1, 5);
|
||||
if (!stored('distanceUnit')) distanceUnitInput.value = US || UK ? 'mi' : 'km';
|
||||
if (!stored('heightUnit')) heightUnit.value = US || UK ? 'ft' : 'm';
|
||||
if (!stored("distanceUnit")) distanceUnitInput.value = US ? "mi" : "km";
|
||||
if (!stored("heightUnit")) heightUnit.value = US ? "ft" : "m";
|
||||
if (!stored('temperatureScale')) temperatureScale.value = US ? '°F' : '°C';
|
||||
|
||||
// World settings
|
||||
|
|
@ -619,22 +655,16 @@ function restoreDefaultOptions() {
|
|||
// Sticked menu Options listeners
|
||||
document.getElementById('sticked').addEventListener('click', function (event) {
|
||||
const id = event.target.id;
|
||||
if (id === 'newMapButton') regeneratePrompt();
|
||||
if (id === "newMapButton") regeneratePrompt("sticky button");
|
||||
else if (id === 'saveButton') showSavePane();
|
||||
else if (id === 'loadButton') showLoadPane();
|
||||
else if (id === "exportButton") showExportPane();
|
||||
else if (id === 'zoomReset') resetZoom(1000);
|
||||
});
|
||||
|
||||
function regeneratePrompt() {
|
||||
if (customization) {
|
||||
tip('New map cannot be generated when edit mode is active, please exit the mode and retry', false, 'error');
|
||||
return;
|
||||
}
|
||||
function regeneratePrompt(source) {
|
||||
if (customization) return tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error");
|
||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||
if (workingTime < 5) {
|
||||
regenerateMap();
|
||||
return;
|
||||
}
|
||||
if (workingTime < 5) return regenerateMap(source);
|
||||
|
||||
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br>
|
||||
All unsaved changes made to the current map will be lost`;
|
||||
|
|
@ -647,19 +677,20 @@ function regeneratePrompt() {
|
|||
},
|
||||
Generate: function () {
|
||||
closeDialogs();
|
||||
regenerateMap();
|
||||
regenerateMap(source);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showSavePane() {
|
||||
document.getElementById('showLabels').checked = !hideLabels.checked;
|
||||
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
|
||||
sharableLinkContainer.style.display = "none";
|
||||
|
||||
$('#saveMapData').dialog({
|
||||
title: 'Save map',
|
||||
resizable: false,
|
||||
width: '30em',
|
||||
width: "25em",
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Close: function () {
|
||||
|
|
@ -669,21 +700,21 @@ function showSavePane() {
|
|||
});
|
||||
}
|
||||
|
||||
// download map data as GeoJSON
|
||||
function saveGeoJSON() {
|
||||
alertMessage.innerHTML = `You can export map data in GeoJSON format used in GIS tools such as QGIS.
|
||||
Check out ${link('https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export', 'wiki-page')} for guidance`;
|
||||
function copyLinkToClickboard() {
|
||||
const shrableLink = document.getElementById("sharableLink");
|
||||
const link = shrableLink.getAttribute("href");
|
||||
navigator.clipboard.writeText(link).then(() => tip("Link is copied to the clipboard", true, "success", 8000));
|
||||
}
|
||||
|
||||
$('#alert').dialog({
|
||||
title: 'GIS data export',
|
||||
function showExportPane() {
|
||||
document.getElementById("showLabels").checked = !hideLabels.checked;
|
||||
|
||||
$("#exportMapData").dialog({
|
||||
title: "Export map data",
|
||||
resizable: false,
|
||||
width: '35em',
|
||||
width: "26em",
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Cells: saveGeoJSON_Cells,
|
||||
Routes: saveGeoJSON_Routes,
|
||||
Rivers: saveGeoJSON_Rivers,
|
||||
Markers: saveGeoJSON_Markers,
|
||||
Close: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
|
|
@ -691,11 +722,11 @@ function saveGeoJSON() {
|
|||
});
|
||||
}
|
||||
|
||||
function showLoadPane() {
|
||||
async function showLoadPane() {
|
||||
$('#loadMapData').dialog({
|
||||
title: 'Load map',
|
||||
resizable: false,
|
||||
width: '17em',
|
||||
width: "24em",
|
||||
position: {my: 'center', at: 'center', of: 'svg'},
|
||||
buttons: {
|
||||
Close: function () {
|
||||
|
|
@ -703,6 +734,25 @@ function showLoadPane() {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons");
|
||||
const fileSelect = document.getElementById("loadFromDropboxSelect");
|
||||
const files = await Cloud.providers.dropbox.list();
|
||||
|
||||
if (!files) {
|
||||
loadFromDropboxButtons.style.display = "none";
|
||||
fileSelect.innerHTML = `<option value="" disabled selected>Save files to Dropbox first</option>`;
|
||||
return;
|
||||
}
|
||||
|
||||
loadFromDropboxButtons.style.display = "block";
|
||||
fileSelect.innerHTML = "";
|
||||
files.forEach(file => {
|
||||
const opt = document.createElement("option");
|
||||
opt.innerText = file.name;
|
||||
opt.value = file.path;
|
||||
fileSelect.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
function loadURL() {
|
||||
|
|
@ -747,7 +797,9 @@ function openSaveTiles() {
|
|||
status.innerHTML = '';
|
||||
let loading = null;
|
||||
|
||||
$('#saveTilesScreen').dialog({
|
||||
const inputs = document.getElementById("saveTilesScreen").querySelectorAll("input");
|
||||
inputs.forEach(input => input.addEventListener("input", updateTilesOptions));
|
||||
|
||||
resizable: false,
|
||||
title: 'Download tiles',
|
||||
width: '23em',
|
||||
|
|
@ -767,17 +819,12 @@ function openSaveTiles() {
|
|||
}
|
||||
},
|
||||
close: () => {
|
||||
debug.selectAll('*').remove();
|
||||
inputs.forEach(input => input.removeEventListener("input", updateTilesOptions));
|
||||
clearInterval(loading);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById('saveTilesScreen')
|
||||
.querySelectorAll('input')
|
||||
.forEach((el) => el.addEventListener('input', updateTilesOptions));
|
||||
|
||||
function updateTilesOptions() {
|
||||
if (this?.tagName === 'INPUT') {
|
||||
const {nextElementSibling: next, previousElementSibling: prev} = this;
|
||||
|
|
|
|||
1187
modules/ui/options.js.orig
Normal file
1187
modules/ui/options.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -34,7 +34,7 @@ function editProvinces() {
|
|||
document.getElementById('provincesManually').addEventListener('click', enterProvincesManualAssignent);
|
||||
document.getElementById('provincesManuallyApply').addEventListener('click', applyProvincesManualAssignent);
|
||||
document.getElementById('provincesManuallyCancel').addEventListener('click', () => exitProvincesManualAssignment());
|
||||
document.getElementById('provincesAdd').addEventListener('click', enterAddProvinceMode);
|
||||
document.getElementById('provincesRelease').addEventListener('click', triggerProvincesRelease);
|
||||
document.getElementById('provincesRecolor').addEventListener('click', recolorProvinces);
|
||||
|
||||
body.addEventListener('click', function (ev) {
|
||||
|
|
@ -148,7 +148,6 @@ function editProvinces() {
|
|||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Declare province independence (turn non-capital province with burgs into a new state)" class="icon-flag-empty ${separable ? '' : 'placeholder'} hide"></span>
|
||||
<span data-tip="Toggle province focus" class="icon-pin ${focused ? '' : ' inactive'} hide"></span>
|
||||
<span data-tip="Remove the province" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
}
|
||||
|
|
@ -228,74 +227,63 @@ function editProvinces() {
|
|||
function capitalZoomIn(p) {
|
||||
const capital = pack.provinces[p].burg;
|
||||
const l = burgLabels.select("[data-id='" + capital + "']");
|
||||
const x = +l.attr('x'),
|
||||
y = +l.attr('y');
|
||||
const x = +l.attr('x');
|
||||
const y = +l.attr('y');
|
||||
zoomTo(x, y, 8, 2000);
|
||||
}
|
||||
|
||||
function triggerIndependencePromps(p) {
|
||||
alertMessage.innerHTML = 'Are you sure you want to declare province independence? <br>It will turn province into a new state';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
confirmationDialog({
|
||||
title: 'Declare independence',
|
||||
buttons: {
|
||||
Declare: function () {
|
||||
declareProvinceIndependence(p);
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
message: 'Are you sure you want to declare province independence? <br>It will turn province into a new state',
|
||||
confirm: 'Declare',
|
||||
onConfirm: () => {
|
||||
const [oldStateId, newStateId] = declareProvinceIndependence(p);
|
||||
updateStatesPostRelease([oldStateId], [newStateId]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function declareProvinceIndependence(p) {
|
||||
const states = pack.states,
|
||||
provinces = pack.provinces,
|
||||
cells = pack.cells;
|
||||
if (provinces[p].burgs.some((b) => pack.burgs[b].capital)) {
|
||||
tip('Cannot declare independence of a province having capital burg. Please change capital first', false, 'error');
|
||||
return;
|
||||
}
|
||||
function declareProvinceIndependence(provinceId) {
|
||||
const {states, provinces, cells, burgs} = pack;
|
||||
const province = provinces[provinceId];
|
||||
const {name, burg: burgId, burgs: provinceBurgs} = province;
|
||||
|
||||
const oldState = pack.provinces[p].state;
|
||||
const newState = pack.states.length;
|
||||
if (provinceBurgs.some((b) => burgs[b].capital)) return tip('Cannot declare independence of a province having capital burg. Please change capital first', false, 'error');
|
||||
if (!burgId) return tip('Cannot declare independence of a province without burg', false, 'error');
|
||||
|
||||
const oldStateId = province.state;
|
||||
const newStateId = states.length;
|
||||
|
||||
// turn province burg into a capital
|
||||
const burg = provinces[p].burg;
|
||||
if (!burg) return;
|
||||
pack.burgs[burg].capital = 1;
|
||||
moveBurgToGroup(burg, 'cities');
|
||||
burgs[burgId].capital = 1;
|
||||
moveBurgToGroup(burgId, 'cities');
|
||||
|
||||
// move all burgs to a new state
|
||||
provinces[p].burgs.forEach((b) => (pack.burgs[b].state = newState));
|
||||
province.burgs.forEach((b) => (burgs[b].state = newStateId));
|
||||
|
||||
// difine new state attributes
|
||||
const center = pack.burgs[burg].cell;
|
||||
const culture = pack.burgs[burg].culture;
|
||||
const name = provinces[p].name;
|
||||
const {cell: center, culture} = burgs[burgId];
|
||||
const color = getRandomColor();
|
||||
|
||||
const coa = provinces[p].coa;
|
||||
const coaEl = document.getElementById('provinceCOA' + p);
|
||||
if (coaEl) coaEl.id = 'stateCOA' + newState;
|
||||
emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove();
|
||||
const coa = province.coa;
|
||||
const coaEl = document.getElementById('provinceCOA' + provinceId);
|
||||
if (coaEl) coaEl.id = 'stateCOA' + newStateId;
|
||||
emblems.select(`#provinceEmblems > use[data-i='${provinceId}']`).remove();
|
||||
|
||||
// update cells
|
||||
cells.i
|
||||
.filter((i) => cells.province[i] === p)
|
||||
.filter((i) => cells.province[i] === provinceId)
|
||||
.forEach((i) => {
|
||||
cells.province[i] = 0;
|
||||
cells.state[i] = newState;
|
||||
cells.state[i] = newStateId;
|
||||
});
|
||||
|
||||
// update diplomacy and reverse relations
|
||||
const diplomacy = states.map((s) => {
|
||||
if (!s.i || s.removed) return 'x';
|
||||
let relations = states[oldState].diplomacy[s.i]; // relations between Nth state and old overlord
|
||||
if (s.i === oldState) relations = 'Enemy';
|
||||
// new state is Enemy to its old overlord
|
||||
let relations = states[oldStateId].diplomacy[s.i]; // relations between Nth state and old overlord
|
||||
// new state is Enemy to its old owner
|
||||
if (s.i === oldStateId) relations = 'Enemy';
|
||||
else if (relations === 'Ally') relations = 'Suspicion';
|
||||
else if (relations === 'Friendly') relations = 'Suspicion';
|
||||
else if (relations === 'Suspicion') relations = 'Neutral';
|
||||
|
|
@ -307,28 +295,51 @@ function editProvinces() {
|
|||
return relations;
|
||||
});
|
||||
diplomacy.push('x');
|
||||
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldState].name}`]);
|
||||
states[0].diplomacy.push([`Independance declaration`, `${name} declared its independance from ${states[oldStateId].name}`]);
|
||||
|
||||
// create new state
|
||||
states.push({i: newState, name, diplomacy, provinces: [], color, expansionism: 0.5, capital: burg, type: 'Generic', center, culture, military: [], alert: 1, coa});
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms([newState]);
|
||||
|
||||
if (layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
else drawStates();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
else drawBorders();
|
||||
BurgsAndStates.drawStateLabels([newState, oldState]);
|
||||
states.push({
|
||||
i: newStateId,
|
||||
name,
|
||||
diplomacy,
|
||||
provinces: [],
|
||||
color,
|
||||
expansionism: 0.5,
|
||||
capital: burgId,
|
||||
type: 'Generic',
|
||||
center,
|
||||
culture,
|
||||
military: [],
|
||||
alert: 1,
|
||||
coa
|
||||
});
|
||||
|
||||
// remove old province
|
||||
unfog('focusProvince' + p);
|
||||
if (states[oldState].provinces.includes(p)) states[oldState].provinces.splice(states[oldState].provinces.indexOf(p), 1);
|
||||
provinces[p] = {i: p, removed: true};
|
||||
states[oldStateId].provinces = states[oldStateId].provinces.filter((p) => p !== provinceId);
|
||||
provinces[provinceId] = {i: provinceId, removed: true};
|
||||
|
||||
// draw emblem
|
||||
COArenderer.add('state', newState, coa, pack.states[newState].pole[0], pack.states[newState].pole[1]);
|
||||
return [oldStateId, newStateId];
|
||||
}
|
||||
|
||||
function updateStatesPostRelease(oldStates, newStates) {
|
||||
const allStates = unique([...oldStates, ...newStates]);
|
||||
|
||||
layerIsOn('toggleProvinces') && toggleProvinces();
|
||||
layerIsOn('toggleStates') ? drawStates() : toggleStates();
|
||||
layerIsOn('toggleBorders') ? drawBorders() : toggleBorders();
|
||||
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms(newStates);
|
||||
BurgsAndStates.drawStateLabels(allStates);
|
||||
|
||||
// redraw emblems
|
||||
allStates.forEach((stateId) => {
|
||||
emblems.select(`#stateEmblems > use[data-i='${stateId}']`)?.remove();
|
||||
const {coa, pole} = pack.states[stateId];
|
||||
COArenderer.add('state', stateId, coa, ...pole);
|
||||
});
|
||||
|
||||
unfog();
|
||||
closeDialogs();
|
||||
editStates();
|
||||
}
|
||||
|
|
@ -547,7 +558,17 @@ function editProvinces() {
|
|||
const provinces = pack.provinces
|
||||
.filter((p) => p.i && !p.removed)
|
||||
.map((p) => {
|
||||
return {id: p.i + states.length - 1, i: p.i, state: p.state, color: p.color, name: p.name, fullName: p.fullName, area: p.area, urban: p.urban, rural: p.rural};
|
||||
return {
|
||||
id: p.i + states.length - 1,
|
||||
i: p.i,
|
||||
state: p.state,
|
||||
color: p.color,
|
||||
name: p.name,
|
||||
fullName: p.fullName,
|
||||
area: p.area,
|
||||
urban: p.urban,
|
||||
rural: p.rural
|
||||
};
|
||||
});
|
||||
const data = states.concat(provinces);
|
||||
const root = d3
|
||||
|
|
@ -571,8 +592,6 @@ function editProvinces() {
|
|||
</select>`;
|
||||
alertMessage.innerHTML += `<div id='provinceInfo' class='chartInfo'>‍</div>`;
|
||||
const svg = d3.select('#alertMessage').insert('svg', '#provinceInfo').attr('id', 'provincesTree').attr('width', width).attr('height', height).attr('font-size', '10px');
|
||||
const graph = svg.append('g').attr('transform', `translate(10, 0)`);
|
||||
document.getElementById('provincesTreeType').addEventListener('change', updateChart);
|
||||
|
||||
treeLayout(root);
|
||||
|
||||
|
|
@ -694,6 +713,34 @@ function editProvinces() {
|
|||
provs.selectAll('text').call(d3.drag().on('drag', dragLabel)).classed('draggable', true);
|
||||
}
|
||||
|
||||
function triggerProvincesRelease() {
|
||||
confirmationDialog({
|
||||
title: 'Release provinces',
|
||||
message: `Are you sure you want to release all provinces?
|
||||
</br>It will turn all separable provinces into independent states.
|
||||
</br>Capital province and provinces without any burgs will state as they are`,
|
||||
confirm: 'Release',
|
||||
onConfirm: () => {
|
||||
const oldStateIds = [];
|
||||
const newStateIds = [];
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach((el) => {
|
||||
const provinceId = +el.dataset.id;
|
||||
const province = pack.provinces[provinceId];
|
||||
if (!province.burg) return;
|
||||
if (province.burg === pack.states[province.state].capital) return;
|
||||
if (province.burgs.some((burgId) => pack.burgs[burgId].capital)) return;
|
||||
|
||||
const [oldStateId, newStateId] = declareProvinceIndependence(provinceId);
|
||||
oldStateIds.push(oldStateId);
|
||||
newStateIds.push(newStateId);
|
||||
});
|
||||
|
||||
updateStatesPostRelease(unique(oldStateIds), newStateIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function enterProvincesManualAssignent() {
|
||||
if (!layerIsOn('toggleProvinces')) toggleProvinces();
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
|
|
@ -852,10 +899,8 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function enterAddProvinceMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
exitAddProvinceMode();
|
||||
return;
|
||||
}
|
||||
if (this.classList.contains('pressed')) return exitAddProvinceMode();
|
||||
|
||||
customization = 12;
|
||||
this.classList.add('pressed');
|
||||
tip('Click on the map to place a new province center', true);
|
||||
|
|
@ -864,24 +909,16 @@ function editProvinces() {
|
|||
}
|
||||
|
||||
function addProvince() {
|
||||
const cells = pack.cells,
|
||||
provinces = pack.provinces;
|
||||
const {cells, provinces} = pack;
|
||||
const point = d3.mouse(this);
|
||||
const center = findCell(point[0], point[1]);
|
||||
if (cells.h[center] < 20) {
|
||||
tip('You cannot place province into the water. Please click on a land cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (cells.h[center] < 20) return tip('You cannot place province into the water. Please click on a land cell', false, 'error');
|
||||
|
||||
const oldProvince = cells.province[center];
|
||||
if (oldProvince && provinces[oldProvince].center === center) {
|
||||
tip('The cell is already a center of a different province. Select other cell', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (oldProvince && provinces[oldProvince].center === center) return tip('The cell is already a center of a different province. Select other cell', false, 'error');
|
||||
|
||||
const state = cells.state[center];
|
||||
if (!state) {
|
||||
tip('You cannot create a province in neutral lands. Please assign this land to a state first', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (!state) return tip('You cannot create a province in neutral lands. Please assign this land to a state first', false, 'error');
|
||||
|
||||
if (d3.event.shiftKey === false) exitAddProvinceMode();
|
||||
|
||||
|
|
@ -892,8 +929,8 @@ function editProvinces() {
|
|||
const name = burg ? pack.burgs[burg].name : Names.getState(Names.getCultureShort(c), c);
|
||||
const formName = oldProvince ? provinces[oldProvince].formName : 'Province';
|
||||
const fullName = name + ' ' + formName;
|
||||
const stateColor = pack.states[state].color,
|
||||
rndColor = getRandomColor();
|
||||
const stateColor = pack.states[state].color;
|
||||
const rndColor = getRandomColor();
|
||||
const color = stateColor[0] === '#' ? d3.color(d3.interpolate(stateColor, rndColor)(0.2)).hex() : rndColor;
|
||||
|
||||
// generate emblem
|
||||
|
|
@ -947,20 +984,20 @@ function editProvinces() {
|
|||
|
||||
function downloadProvincesData() {
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
let data = 'Id,Province,Form,State,Color,Capital,Area ' + unit + ',Total Population,Rural Population,Urban Population\n'; // headers
|
||||
let data = 'Id,Province,Full Name,Form,State,Color,Capital,Area ' + unit + ',Total Population,Rural Population,Urban Population\n'; // headers
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
let key = parseInt(el.dataset.id);
|
||||
data += el.dataset.id + ',';
|
||||
const key = parseInt(el.dataset.id);
|
||||
const provincePack = pack.provinces[key];
|
||||
data += el.dataset.name + ',';
|
||||
data += el.dataset.form + ',';
|
||||
data += el.dataset.state + ',';
|
||||
data += provincePack.fullName + ',';
|
||||
data += el.dataset.color + ',';
|
||||
data += el.dataset.capital + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + ',';
|
||||
data += `${Math.round(pack.provinces[key].rural * populationRate)},`;
|
||||
data += `${Math.round(pack.provinces[key].urban * populationRate * urbanization)}\n`;
|
||||
data += `${Math.round(provincePack.rural * populationRate)},`;
|
||||
data += `${Math.round(provincePack.urban * populationRate * urbanization)}\n`;
|
||||
});
|
||||
|
||||
const name = getFileName('Provinces') + '.csv';
|
||||
|
|
|
|||
1288
modules/ui/provinces-editor.js.orig
Normal file
1288
modules/ui/provinces-editor.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,49 +1,53 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
function overviewRegiments(state) {
|
||||
if (customization) return;
|
||||
closeDialogs(".stable");
|
||||
if (!layerIsOn("toggleMilitary")) toggleMilitary();
|
||||
closeDialogs('.stable');
|
||||
if (!layerIsOn('toggleMilitary')) toggleMilitary();
|
||||
|
||||
const body = document.getElementById("regimentsBody");
|
||||
const body = document.getElementById('regimentsBody');
|
||||
updateFilter(state);
|
||||
addLines();
|
||||
$("#regimentsOverview").dialog();
|
||||
$('#regimentsOverview').dialog();
|
||||
|
||||
if (modules.overviewRegiments) return;
|
||||
modules.overviewRegiments = true;
|
||||
updateHeaders();
|
||||
|
||||
$("#regimentsOverview").dialog({
|
||||
title: "Regiments Overview", resizable: false, width: fitContent(),
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
$('#regimentsOverview').dialog({
|
||||
title: 'Regiments Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("regimentsOverviewRefresh").addEventListener("click", addLines);
|
||||
document.getElementById("regimentsPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("regimentsAddNew").addEventListener("click", toggleAdd);
|
||||
document.getElementById("regimentsExport").addEventListener("click", downloadRegimentsData);
|
||||
document.getElementById("regimentsFilter").addEventListener("change", addLines);
|
||||
document.getElementById('regimentsOverviewRefresh').addEventListener('click', addLines);
|
||||
document.getElementById('regimentsPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('regimentsAddNew').addEventListener('click', toggleAdd);
|
||||
document.getElementById('regimentsExport').addEventListener('click', downloadRegimentsData);
|
||||
document.getElementById('regimentsFilter').addEventListener('change', addLines);
|
||||
|
||||
// update military types in header and tooltips
|
||||
function updateHeaders() {
|
||||
const header = document.getElementById("regimentsHeader");
|
||||
header.querySelectorAll(".removable").forEach(el => el.remove());
|
||||
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
|
||||
const header = document.getElementById('regimentsHeader');
|
||||
header.querySelectorAll('.removable').forEach((el) => el.remove());
|
||||
const insert = (html) => document.getElementById('regimentsTotal').insertAdjacentHTML('beforebegin', html);
|
||||
for (const u of options.military) {
|
||||
const label = capitalize(u.name.replace(/_/g, ' '));
|
||||
insert(`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label} </div>`);
|
||||
}
|
||||
header.querySelectorAll(".removable").forEach(function(e) {
|
||||
e.addEventListener("click", function() {sortLines(this);});
|
||||
header.querySelectorAll('.removable').forEach(function (e) {
|
||||
e.addEventListener('click', function () {
|
||||
sortLines(this);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add line for each state
|
||||
function addLines() {
|
||||
const state = +regimentsFilter.value;
|
||||
body.innerHTML = "";
|
||||
let lines = "";
|
||||
body.innerHTML = '';
|
||||
let lines = '';
|
||||
const regiments = [];
|
||||
|
||||
for (const s of pack.states) {
|
||||
|
|
@ -51,8 +55,8 @@ function overviewRegiments(state) {
|
|||
if (state !== -1 && s.i !== state) continue; // specific state is selected
|
||||
|
||||
for (const r of s.military) {
|
||||
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name]||0}`).join(" ");
|
||||
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name]||0}</div>`).join(" ");
|
||||
const sortData = options.military.map((u) => `data-${u.name}=${r.u[u.name] || 0}`).join(' ');
|
||||
const lineData = options.military.map((u) => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`).join(' ');
|
||||
|
||||
lines += `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
|
||||
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
|
||||
|
|
@ -70,90 +74,98 @@ function overviewRegiments(state) {
|
|||
|
||||
lines += `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
|
||||
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
|
||||
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name]||0)))}</div>`).join(" ")}
|
||||
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
|
||||
${options.military.map((u) => `<div style="width:5em">${si(d3.sum(regiments.map((r) => r.u[u.name] || 0)))}</div>`).join(' ')}
|
||||
<div style="width:5em">${si(d3.sum(regiments.map((r) => r.a)))}</div>
|
||||
</div>`;
|
||||
|
||||
body.insertAdjacentHTML("beforeend", lines);
|
||||
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
|
||||
body.insertAdjacentHTML('beforeend', lines);
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
togglePercentageMode();
|
||||
}
|
||||
applySorting(regimentsHeader);
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => regimentHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => regimentHighlightOff(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => regimentHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => regimentHighlightOff(ev)));
|
||||
}
|
||||
|
||||
function updateFilter(state) {
|
||||
const filter = document.getElementById("regimentsFilter");
|
||||
const filter = document.getElementById('regimentsFilter');
|
||||
filter.options.length = 0; // remove all options
|
||||
filter.options.add(new Option(`all`, -1, false, state === -1));
|
||||
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1);
|
||||
statesSorted.forEach(s => filter.options.add(new Option(s.name, s.i, false, s.i == state)));
|
||||
const statesSorted = pack.states.filter((s) => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
statesSorted.forEach((s) => filter.options.add(new Option(s.name, s.i, false, s.i == state)));
|
||||
}
|
||||
|
||||
function regimentHighlightOn(event) {
|
||||
const state = +event.target.dataset.s;
|
||||
const id = +event.target.dataset.id;
|
||||
if (customization || !state) return;
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(2000).style("fill", "#ff0000");
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(2000).style('fill', '#ff0000');
|
||||
}
|
||||
|
||||
function regimentHighlightOff(event) {
|
||||
const state = +event.target.dataset.s;
|
||||
const id = +event.target.dataset.id;
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(1000).style("fill", null);
|
||||
armies.select(`g > g#regiment${state}-${id}`).transition().duration(1000).style('fill', null);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
const lines = body.querySelectorAll(":scope > div:not(.totalLine)");
|
||||
const array = Array.from(lines), cache = [];
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const lines = body.querySelectorAll(':scope > div:not(.totalLine)');
|
||||
const array = Array.from(lines),
|
||||
cache = [];
|
||||
|
||||
const total = function(type) {
|
||||
const total = function (type) {
|
||||
if (cache[type]) cache[type];
|
||||
cache[type] = d3.sum(array.map(el => +el.dataset[type]));
|
||||
cache[type] = d3.sum(array.map((el) => +el.dataset[type]));
|
||||
return cache[type];
|
||||
}
|
||||
};
|
||||
|
||||
lines.forEach(function(el) {
|
||||
el.querySelectorAll("div").forEach(function(div) {
|
||||
lines.forEach(function (el) {
|
||||
el.querySelectorAll('div').forEach(function (div) {
|
||||
const type = div.dataset.type;
|
||||
if (type === "rate") return;
|
||||
div.textContent = total(type) ? rn(+el.dataset[type] / total(type) * 100) + "%" : "0%";
|
||||
if (type === 'rate') return;
|
||||
div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + '%' : '0%';
|
||||
});
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
body.dataset.type = 'absolute';
|
||||
addLines();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAdd() {
|
||||
document.getElementById("regimentsAddNew").classList.toggle("pressed");
|
||||
if (document.getElementById("regimentsAddNew").classList.contains("pressed")) {
|
||||
viewbox.style("cursor", "crosshair").on("click", addRegimentOnClick);
|
||||
tip("Click on map to create new regiment or fleet", true);
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.add("pressed");
|
||||
document.getElementById('regimentsAddNew').classList.toggle('pressed');
|
||||
if (document.getElementById('regimentsAddNew').classList.contains('pressed')) {
|
||||
viewbox.style('cursor', 'crosshair').on('click', addRegimentOnClick);
|
||||
tip('Click on map to create new regiment or fleet', true);
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.add('pressed');
|
||||
} else {
|
||||
clearMainTip();
|
||||
viewbox.on("click", clicked).style("cursor", "default");
|
||||
viewbox.on('click', clicked).style('cursor', 'default');
|
||||
addLines();
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.remove("pressed");
|
||||
if (regimentAdd.offsetParent) regimentAdd.classList.remove('pressed');
|
||||
}
|
||||
}
|
||||
|
||||
function addRegimentOnClick() {
|
||||
const state = +regimentsFilter.value;
|
||||
if (state === -1) {tip("Please select state from the list", false, "error"); return;}
|
||||
if (state === -1) {
|
||||
tip('Please select state from the list', false, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const point = d3.mouse(this);
|
||||
const cell = findCell(point[0], point[1]);
|
||||
const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1];
|
||||
const x = pack.cells.p[cell][0],
|
||||
y = pack.cells.p[cell][1];
|
||||
const military = pack.states[state].military;
|
||||
const i = military.length ? last(military).i + 1 : 0;
|
||||
const n = +(pack.cells.h[cell] < 20); // naval or land
|
||||
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, state, icon:"🛡️"};
|
||||
const reg = {a: 0, cell, i, n, u: {}, x, y, bx: x, by: y, state, icon: '🛡️'};
|
||||
reg.name = Military.getName(reg, military);
|
||||
military.push(reg);
|
||||
Military.generateNote(reg, pack.states[state]); // add legend
|
||||
|
|
@ -162,19 +174,18 @@ function overviewRegiments(state) {
|
|||
}
|
||||
|
||||
function downloadRegimentsData() {
|
||||
const units = options.military.map(u => u.name);
|
||||
let data = "State,Id,Name,"+units.map(u => capitalize(u)).join(",")+",Total\n"; // headers
|
||||
const units = options.military.map((u) => u.name);
|
||||
let data = 'State,Id,Name,' + units.map((u) => capitalize(u)).join(',') + ',Total\n'; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function(el) {
|
||||
data += el.dataset.state + ",";
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.name + ",";
|
||||
data += units.map(u => el.dataset[u]).join(",") + ",";
|
||||
data += el.dataset.total + "\n";
|
||||
body.querySelectorAll(':scope > div:not(.totalLine)').forEach(function (el) {
|
||||
data += el.dataset.state + ',';
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.name + ',';
|
||||
data += units.map((u) => el.dataset[u]).join(',') + ',';
|
||||
data += el.dataset.total + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName("Regiments") + ".csv";
|
||||
const name = getFileName('Regiments') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,23 +1,23 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
function createRiver() {
|
||||
if (customization) return;
|
||||
closeDialogs();
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
|
||||
document.getElementById("toggleCells").dataset.forced = +!layerIsOn("toggleCells");
|
||||
if (!layerIsOn("toggleCells")) toggleCells();
|
||||
document.getElementById('toggleCells').dataset.forced = +!layerIsOn('toggleCells');
|
||||
if (!layerIsOn('toggleCells')) toggleCells();
|
||||
|
||||
tip("Click to add river point, click again to remove", true);
|
||||
debug.append("g").attr("id", "controlCells");
|
||||
viewbox.style("cursor", "crosshair").on("click", onCellClick);
|
||||
tip('Click to add river point, click again to remove', true);
|
||||
debug.append('g').attr('id', 'controlCells');
|
||||
viewbox.style('cursor', 'crosshair').on('click', onCellClick);
|
||||
|
||||
createRiver.cells = [];
|
||||
const body = document.getElementById("riverCreatorBody");
|
||||
const body = document.getElementById('riverCreatorBody');
|
||||
|
||||
$("#riverCreator").dialog({
|
||||
title: "Create River",
|
||||
$('#riverCreator').dialog({
|
||||
title: 'Create River',
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "#map"},
|
||||
position: {my: 'left top', at: 'left+10 top+10', of: '#map'},
|
||||
close: closeRiverCreator
|
||||
});
|
||||
|
||||
|
|
@ -25,14 +25,14 @@ function createRiver() {
|
|||
modules.createRiver = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("riverCreatorComplete").addEventListener("click", addRiver);
|
||||
document.getElementById("riverCreatorCancel").addEventListener("click", () => $("#riverCreator").dialog("close"));
|
||||
body.addEventListener("click", function (ev) {
|
||||
document.getElementById('riverCreatorComplete').addEventListener('click', addRiver);
|
||||
document.getElementById('riverCreatorCancel').addEventListener('click', () => $('#riverCreator').dialog('close'));
|
||||
body.addEventListener('click', function (ev) {
|
||||
const el = ev.target;
|
||||
const cl = el.classList;
|
||||
const cell = +el.parentNode.dataset.cell;
|
||||
if (cl.contains("editFlux")) pack.cells.fl[cell] = +el.value;
|
||||
else if (cl.contains("icon-trash-empty")) removeCell(cell);
|
||||
if (cl.contains('editFlux')) pack.cells.fl[cell] = +el.value;
|
||||
else if (cl.contains('icon-trash-empty')) removeCell(cell);
|
||||
});
|
||||
|
||||
function onCellClick() {
|
||||
|
|
@ -57,19 +57,19 @@ function createRiver() {
|
|||
}
|
||||
|
||||
function removeCell(cell) {
|
||||
createRiver.cells = createRiver.cells.filter(c => c !== cell);
|
||||
createRiver.cells = createRiver.cells.filter((c) => c !== cell);
|
||||
drawCells(createRiver.cells);
|
||||
body.querySelector(`div[data-cell='${cell}']`)?.remove();
|
||||
}
|
||||
|
||||
function drawCells(cells) {
|
||||
debug
|
||||
.select("#controlCells")
|
||||
.select('#controlCells')
|
||||
.selectAll(`polygon`)
|
||||
.data(cells)
|
||||
.join("polygon")
|
||||
.attr("points", d => getPackPolygon(d))
|
||||
.attr("class", "current");
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('class', 'current');
|
||||
}
|
||||
|
||||
function addRiver() {
|
||||
|
|
@ -77,12 +77,12 @@ function createRiver() {
|
|||
const {addMeandering, getApproximateLength, getWidth, getOffset, getName, getRiverPath, getBasin} = Rivers;
|
||||
|
||||
const riverCells = createRiver.cells;
|
||||
if (riverCells.length < 2) return tip("Add at least 2 cells", false, "error");
|
||||
if (riverCells.length < 2) return tip('Add at least 2 cells', false, 'error');
|
||||
|
||||
const riverId = rivers.length ? last(rivers).i + 1 : 1;
|
||||
const parent = cells.r[last(riverCells)] || riverId;
|
||||
|
||||
riverCells.forEach(cell => {
|
||||
riverCells.forEach((cell) => {
|
||||
if (!cells.r[cell]) cells.r[cell] = riverId;
|
||||
});
|
||||
|
||||
|
|
@ -99,27 +99,24 @@ function createRiver() {
|
|||
const name = getName(mouth);
|
||||
const basin = getBasin(parent);
|
||||
|
||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: "River"});
|
||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: 'River'});
|
||||
const id = 'river' + riverId;
|
||||
|
||||
// render river
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
viewbox
|
||||
.select("#rivers")
|
||||
.append("path")
|
||||
.attr("id", "river" + riverId)
|
||||
.attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
||||
viewbox.select('#rivers').append('path').attr('id', id).attr('d', getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
||||
|
||||
editRiver(riverId);
|
||||
editRiver(id);
|
||||
}
|
||||
|
||||
function closeRiverCreator() {
|
||||
body.innerHTML = "";
|
||||
debug.select("#controlCells").remove();
|
||||
body.innerHTML = '';
|
||||
debug.select('#controlCells').remove();
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
|
||||
const forced = +document.getElementById("toggleCells").dataset.forced;
|
||||
document.getElementById("toggleCells").dataset.forced = 0;
|
||||
if (forced && layerIsOn("toggleCells")) toggleCells();
|
||||
const forced = +document.getElementById('toggleCells').dataset.forced;
|
||||
document.getElementById('toggleCells').dataset.forced = 0;
|
||||
if (forced && layerIsOn('toggleCells')) toggleCells();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ function editRiver(id) {
|
|||
document.getElementById('toggleCells').dataset.forced = +!layerIsOn('toggleCells');
|
||||
if (!layerIsOn('toggleCells')) toggleCells();
|
||||
|
||||
elSelected = d3.select('#' + id);
|
||||
elSelected = d3.select('#' + id).on('click', addControlPoint);
|
||||
|
||||
tip('Drag control points to change the river course. For major changes please create a new river instead', true);
|
||||
tip('Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead', true);
|
||||
debug.append('g').attr('id', 'controlCells');
|
||||
debug.append('g').attr('id', 'controlPoints');
|
||||
|
||||
|
|
@ -19,8 +19,8 @@ function editRiver(id) {
|
|||
const river = getRiver();
|
||||
const {cells, points} = river;
|
||||
const riverPoints = Rivers.getRiverPoints(cells, points);
|
||||
drawControlPoints(riverPoints, cells);
|
||||
drawCells(cells, 'current');
|
||||
drawControlPoints(riverPoints);
|
||||
drawCells(cells);
|
||||
|
||||
$('#riverEditor').dialog({
|
||||
title: 'Edit River',
|
||||
|
|
@ -92,37 +92,35 @@ function editRiver(id) {
|
|||
document.getElementById('riverWidth').value = width;
|
||||
}
|
||||
|
||||
function drawControlPoints(points, cells) {
|
||||
function drawControlPoints(points) {
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.data(points)
|
||||
.enter()
|
||||
.append('circle')
|
||||
.join('circle')
|
||||
.attr('cx', (d) => d[0])
|
||||
.attr('cy', (d) => d[1])
|
||||
.attr('r', 0.6)
|
||||
.attr('data-cell', (d, i) => cells[i])
|
||||
.attr('data-i', (d, i) => i)
|
||||
.call(d3.drag().on('start', dragControlPoint));
|
||||
.call(d3.drag().on('start', dragControlPoint))
|
||||
.on('click', removeControlPoint);
|
||||
}
|
||||
|
||||
function drawCells(cells, type) {
|
||||
function drawCells(cells) {
|
||||
const validCells = [...new Set(cells)].filter((i) => pack.cells.i[i]);
|
||||
debug
|
||||
.select('#controlCells')
|
||||
.selectAll(`polygon.${type}`)
|
||||
.data(cells.filter((i) => pack.cells.i[i]))
|
||||
.selectAll(`polygon`)
|
||||
.data(validCells)
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('class', type);
|
||||
.attr('points', (d) => getPackPolygon(d));
|
||||
}
|
||||
|
||||
function dragControlPoint() {
|
||||
const {i, r, fl} = pack.cells;
|
||||
const {r, fl} = pack.cells;
|
||||
const river = getRiver();
|
||||
|
||||
const initCell = +this.dataset.cell;
|
||||
const index = +this.dataset.i;
|
||||
const {x: x0, y: y0} = d3.event;
|
||||
const initCell = findCell(x0, y0);
|
||||
|
||||
let movedToCell = null;
|
||||
|
||||
|
|
@ -136,22 +134,18 @@ function editRiver(id) {
|
|||
this.setAttribute('cy', y);
|
||||
this.__data__ = [rn(x, 1), rn(y, 1)];
|
||||
redrawRiver();
|
||||
drawCells(river.cells);
|
||||
});
|
||||
|
||||
d3.event.on('end', () => {
|
||||
if (movedToCell) {
|
||||
this.dataset.cell = movedToCell;
|
||||
river.cells[index] = movedToCell;
|
||||
drawCells(river.cells, 'current');
|
||||
|
||||
if (!r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
}
|
||||
if (movedToCell && !r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
redrawRiver();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -159,8 +153,10 @@ function editRiver(id) {
|
|||
function redrawRiver() {
|
||||
const river = getRiver();
|
||||
river.points = debug.selectAll('#controlPoints > *').data();
|
||||
const {cells, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(cells, river.points);
|
||||
river.cells = river.points.map(([x, y]) => findCell(x, y));
|
||||
|
||||
const {widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(river.cells, river.points);
|
||||
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||
|
|
@ -170,6 +166,27 @@ function editRiver(id) {
|
|||
if (modules.elevation) showEPForRiver(elSelected.node());
|
||||
}
|
||||
|
||||
function addControlPoint() {
|
||||
const [x, y] = d3.mouse(this);
|
||||
const point = [rn(x, 1), rn(y, 1)];
|
||||
|
||||
const river = getRiver();
|
||||
if (!river.points) river.points = debug.selectAll('#controlPoints > *').data();
|
||||
|
||||
const index = getSegmentId(river.points, point, 2);
|
||||
river.points.splice(index, 0, point);
|
||||
drawControlPoints(river.points);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function removeControlPoint() {
|
||||
this.remove();
|
||||
redrawRiver();
|
||||
|
||||
const {cells} = getRiver();
|
||||
drawCells(cells);
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
getRiver().name = this.value;
|
||||
}
|
||||
|
|
@ -244,6 +261,8 @@ function editRiver(id) {
|
|||
function closeRiverEditor() {
|
||||
debug.select('#controlPoints').remove();
|
||||
debug.select('#controlCells').remove();
|
||||
|
||||
elSelected.on('click', null);
|
||||
unselect();
|
||||
clearMainTip();
|
||||
|
||||
|
|
|
|||
334
modules/ui/rivers-editor.js.orig
Normal file
334
modules/ui/rivers-editor.js.orig
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
'use strict';
|
||||
function editRiver(id) {
|
||||
if (customization) return;
|
||||
if (elSelected && id === elSelected.attr('id')) return;
|
||||
closeDialogs('.stable');
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
|
||||
document.getElementById('toggleCells').dataset.forced = +!layerIsOn('toggleCells');
|
||||
if (!layerIsOn('toggleCells')) toggleCells();
|
||||
|
||||
<<<<<<< HEAD
|
||||
elSelected = d3.select('#' + id);
|
||||
|
||||
tip('Drag control points to change the river course. For major changes please create a new river instead', true);
|
||||
debug.append('g').attr('id', 'controlCells');
|
||||
debug.append('g').attr('id', 'controlPoints');
|
||||
=======
|
||||
elSelected = d3.select("#" + id).on("click", addControlPoint);
|
||||
|
||||
tip("Drag control points to change the river course. Click on point to remove it. Click on river to add additional control point. For major changes please create a new river instead", true);
|
||||
debug.append("g").attr("id", "controlCells");
|
||||
debug.append("g").attr("id", "controlPoints");
|
||||
>>>>>>> master
|
||||
|
||||
updateRiverData();
|
||||
|
||||
const river = getRiver();
|
||||
const {cells, points} = river;
|
||||
const riverPoints = Rivers.getRiverPoints(cells, points);
|
||||
<<<<<<< HEAD
|
||||
drawControlPoints(riverPoints, cells);
|
||||
drawCells(cells, 'current');
|
||||
=======
|
||||
drawControlPoints(riverPoints);
|
||||
drawCells(cells);
|
||||
>>>>>>> master
|
||||
|
||||
$('#riverEditor').dialog({
|
||||
title: 'Edit River',
|
||||
resizable: false,
|
||||
position: {my: 'left top', at: 'left+10 top+10', of: '#map'},
|
||||
close: closeRiverEditor
|
||||
});
|
||||
|
||||
if (modules.editRiver) return;
|
||||
modules.editRiver = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById('riverCreateSelectingCells').addEventListener('click', createRiver);
|
||||
document.getElementById('riverEditStyle').addEventListener('click', () => editStyle('rivers'));
|
||||
document.getElementById('riverElevationProfile').addEventListener('click', showElevationProfile);
|
||||
document.getElementById('riverLegend').addEventListener('click', editRiverLegend);
|
||||
document.getElementById('riverRemove').addEventListener('click', removeRiver);
|
||||
document.getElementById('riverName').addEventListener('input', changeName);
|
||||
document.getElementById('riverType').addEventListener('input', changeType);
|
||||
document.getElementById('riverNameCulture').addEventListener('click', generateNameCulture);
|
||||
document.getElementById('riverNameRandom').addEventListener('click', generateNameRandom);
|
||||
document.getElementById('riverMainstem').addEventListener('change', changeParent);
|
||||
document.getElementById('riverSourceWidth').addEventListener('input', changeSourceWidth);
|
||||
document.getElementById('riverWidthFactor').addEventListener('input', changeWidthFactor);
|
||||
|
||||
function getRiver() {
|
||||
const riverId = +elSelected.attr('id').slice(5);
|
||||
const river = pack.rivers.find((r) => r.i === riverId);
|
||||
return river;
|
||||
}
|
||||
|
||||
function updateRiverData() {
|
||||
const r = getRiver();
|
||||
|
||||
document.getElementById('riverName').value = r.name;
|
||||
document.getElementById('riverType').value = r.type;
|
||||
|
||||
const parentSelect = document.getElementById('riverMainstem');
|
||||
parentSelect.options.length = 0;
|
||||
const parent = r.parent || r.i;
|
||||
const sortedRivers = pack.rivers.slice().sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
sortedRivers.forEach((river) => {
|
||||
const opt = new Option(river.name, river.i, false, river.i === parent);
|
||||
parentSelect.options.add(opt);
|
||||
});
|
||||
document.getElementById('riverBasin').value = pack.rivers.find((river) => river.i === r.basin).name;
|
||||
|
||||
document.getElementById('riverDischarge').value = r.discharge + ' m³/s';
|
||||
document.getElementById('riverSourceWidth').value = r.sourceWidth;
|
||||
document.getElementById('riverWidthFactor').value = r.widthFactor;
|
||||
|
||||
updateRiverLength(r);
|
||||
updateRiverWidth(r);
|
||||
}
|
||||
|
||||
function updateRiverLength(river) {
|
||||
river.length = rn(elSelected.node().getTotalLength() / 2, 2);
|
||||
const lengthUI = `${rn(river.length * distanceScaleInput.value)} ${distanceUnitInput.value}`;
|
||||
document.getElementById('riverLength').value = lengthUI;
|
||||
}
|
||||
|
||||
function updateRiverWidth(river) {
|
||||
const {addMeandering, getWidth, getOffset} = Rivers;
|
||||
const {cells, discharge, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = addMeandering(cells);
|
||||
river.width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth));
|
||||
|
||||
const width = `${rn(river.width * distanceScaleInput.value, 3)} ${distanceUnitInput.value}`;
|
||||
document.getElementById('riverWidth').value = width;
|
||||
}
|
||||
|
||||
function drawControlPoints(points) {
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.data(points)
|
||||
<<<<<<< HEAD
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('cx', (d) => d[0])
|
||||
.attr('cy', (d) => d[1])
|
||||
.attr('r', 0.6)
|
||||
.attr('data-cell', (d, i) => cells[i])
|
||||
.attr('data-i', (d, i) => i)
|
||||
.call(d3.drag().on('start', dragControlPoint));
|
||||
=======
|
||||
.join("circle")
|
||||
.attr("cx", d => d[0])
|
||||
.attr("cy", d => d[1])
|
||||
.attr("r", 0.6)
|
||||
.call(d3.drag().on("start", dragControlPoint))
|
||||
.on("click", removeControlPoint);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function drawCells(cells) {
|
||||
const validCells = [...new Set(cells)].filter(i => pack.cells.i[i]);
|
||||
debug
|
||||
<<<<<<< HEAD
|
||||
.select('#controlCells')
|
||||
.selectAll(`polygon.${type}`)
|
||||
.data(cells.filter((i) => pack.cells.i[i]))
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('class', type);
|
||||
=======
|
||||
.select("#controlCells")
|
||||
.selectAll(`polygon`)
|
||||
.data(validCells)
|
||||
.join("polygon")
|
||||
.attr("points", d => getPackPolygon(d));
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function dragControlPoint() {
|
||||
const {r, fl} = pack.cells;
|
||||
const river = getRiver();
|
||||
|
||||
const {x: x0, y: y0} = d3.event;
|
||||
const initCell = findCell(x0, y0);
|
||||
|
||||
let movedToCell = null;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const {x, y} = d3.event;
|
||||
const currentCell = findCell(x, y);
|
||||
|
||||
movedToCell = initCell !== currentCell ? currentCell : null;
|
||||
|
||||
this.setAttribute('cx', x);
|
||||
this.setAttribute('cy', y);
|
||||
this.__data__ = [rn(x, 1), rn(y, 1)];
|
||||
redrawRiver();
|
||||
drawCells(river.cells);
|
||||
});
|
||||
|
||||
<<<<<<< HEAD
|
||||
d3.event.on('end', () => {
|
||||
if (movedToCell) {
|
||||
this.dataset.cell = movedToCell;
|
||||
river.cells[index] = movedToCell;
|
||||
drawCells(river.cells, 'current');
|
||||
|
||||
if (!r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
}
|
||||
=======
|
||||
d3.event.on("end", () => {
|
||||
if (movedToCell && !r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
redrawRiver();
|
||||
>>>>>>> master
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function redrawRiver() {
|
||||
const river = getRiver();
|
||||
<<<<<<< HEAD
|
||||
river.points = debug.selectAll('#controlPoints > *').data();
|
||||
const {cells, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(cells, river.points);
|
||||
=======
|
||||
river.points = debug.selectAll("#controlPoints > *").data();
|
||||
river.cells = river.points.map(([x, y]) => findCell(x, y));
|
||||
|
||||
const {widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(river.cells, river.points);
|
||||
>>>>>>> master
|
||||
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||
elSelected.attr('d', path);
|
||||
|
||||
updateRiverLength(river);
|
||||
if (modules.elevation) showEPForRiver(elSelected.node());
|
||||
}
|
||||
|
||||
function addControlPoint() {
|
||||
const [x, y] = d3.mouse(this);
|
||||
const point = [rn(x, 1), rn(y, 1)];
|
||||
|
||||
const river = getRiver();
|
||||
if (!river.points) river.points = debug.selectAll("#controlPoints > *").data();
|
||||
|
||||
const index = getSegmentId(river.points, point, 2);
|
||||
river.points.splice(index, 0, point);
|
||||
drawControlPoints(river.points);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function removeControlPoint() {
|
||||
this.remove();
|
||||
redrawRiver();
|
||||
|
||||
const {cells} = getRiver();
|
||||
drawCells(cells);
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
getRiver().name = this.value;
|
||||
}
|
||||
|
||||
function changeType() {
|
||||
getRiver().type = this.value;
|
||||
}
|
||||
|
||||
function generateNameCulture() {
|
||||
const r = getRiver();
|
||||
r.name = riverName.value = Rivers.getName(r.mouth);
|
||||
}
|
||||
|
||||
function generateNameRandom() {
|
||||
const r = getRiver();
|
||||
if (r) r.name = riverName.value = Names.getBase(rand(nameBases.length - 1));
|
||||
}
|
||||
|
||||
function changeParent() {
|
||||
const r = getRiver();
|
||||
r.parent = +this.value;
|
||||
r.basin = pack.rivers.find((river) => river.i === r.parent).basin;
|
||||
document.getElementById('riverBasin').value = pack.rivers.find((river) => river.i === r.basin).name;
|
||||
}
|
||||
|
||||
function changeSourceWidth() {
|
||||
const river = getRiver();
|
||||
river.sourceWidth = +this.value;
|
||||
updateRiverWidth(river);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function changeWidthFactor() {
|
||||
const river = getRiver();
|
||||
river.widthFactor = +this.value;
|
||||
updateRiverWidth(river);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function showElevationProfile() {
|
||||
modules.elevation = true;
|
||||
showEPForRiver(elSelected.node());
|
||||
}
|
||||
|
||||
function editRiverLegend() {
|
||||
const id = elSelected.attr('id');
|
||||
const river = getRiver();
|
||||
editNotes(id, river.name + ' ' + river.type);
|
||||
}
|
||||
|
||||
function removeRiver() {
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the river and all its tributaries';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
width: '22em',
|
||||
title: 'Remove river and tributaries',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
const river = +elSelected.attr('id').slice(5);
|
||||
Rivers.remove(river);
|
||||
elSelected.remove();
|
||||
$('#riverEditor').dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeRiverEditor() {
|
||||
<<<<<<< HEAD
|
||||
debug.select('#controlPoints').remove();
|
||||
debug.select('#controlCells').remove();
|
||||
=======
|
||||
debug.select("#controlPoints").remove();
|
||||
debug.select("#controlCells").remove();
|
||||
|
||||
elSelected.on("click", null);
|
||||
>>>>>>> master
|
||||
unselect();
|
||||
clearMainTip();
|
||||
|
||||
const forced = +document.getElementById('toggleCells').dataset.forced;
|
||||
document.getElementById('toggleCells').dataset.forced = 0;
|
||||
if (forced && layerIsOn('toggleCells')) toggleCells();
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ function overviewRivers() {
|
|||
function zoomToRiver() {
|
||||
const r = +this.parentNode.dataset.id;
|
||||
const river = rivers.select('#river' + r).node();
|
||||
highlightElement(river);
|
||||
highlightElement(river, 3);
|
||||
}
|
||||
|
||||
function toggleBasinsHightlight() {
|
||||
|
|
|
|||
187
modules/ui/rivers-overview.js.orig
Normal file
187
modules/ui/rivers-overview.js.orig
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
'use strict';
|
||||
function overviewRivers() {
|
||||
if (customization) return;
|
||||
closeDialogs('#riversOverview, .stable');
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
|
||||
const body = document.getElementById('riversBody');
|
||||
riversOverviewAddLines();
|
||||
$('#riversOverview').dialog();
|
||||
|
||||
if (modules.overviewRivers) return;
|
||||
modules.overviewRivers = true;
|
||||
|
||||
$('#riversOverview').dialog({
|
||||
title: 'Rivers Overview',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById('riversOverviewRefresh').addEventListener('click', riversOverviewAddLines);
|
||||
document.getElementById('addNewRiver').addEventListener('click', toggleAddRiver);
|
||||
document.getElementById('riverCreateNew').addEventListener('click', createRiver);
|
||||
document.getElementById('riversBasinHighlight').addEventListener('click', toggleBasinsHightlight);
|
||||
document.getElementById('riversExport').addEventListener('click', downloadRiversData);
|
||||
document.getElementById('riversRemoveAll').addEventListener('click', triggerAllRiversRemove);
|
||||
|
||||
// add line for each river
|
||||
function riversOverviewAddLines() {
|
||||
body.innerHTML = '';
|
||||
let lines = '';
|
||||
const unit = distanceUnitInput.value;
|
||||
|
||||
for (const r of pack.rivers) {
|
||||
const discharge = r.discharge + ' m³/s';
|
||||
const length = rn(r.length * distanceScaleInput.value) + ' ' + unit;
|
||||
const width = rn(r.width * distanceScaleInput.value, 3) + ' ' + unit;
|
||||
const basin = pack.rivers.find((river) => river.i === r.basin)?.name;
|
||||
|
||||
lines += `<div class="states" data-id=${r.i} data-name="${r.name}" data-type="${r.type}" data-discharge="${r.discharge}" data-length="${r.length}" data-width="${r.width}" data-basin="${basin}">
|
||||
<span data-tip="Click to focus on river" class="icon-dot-circled pointer"></span>
|
||||
<div data-tip="River name" class="riverName">${r.name}</div>
|
||||
<div data-tip="River type name" class="riverType">${r.type}</div>
|
||||
<div data-tip="River discharge (flux power)" class="biomeArea">${discharge}</div>
|
||||
<div data-tip="River length from source to mouth" class="biomeArea">${length}</div>
|
||||
<div data-tip="River mouth width" class="biomeArea">${width}</div>
|
||||
<input data-tip="River basin (name of the main stem)" class="stateName" value="${basin}" disabled>
|
||||
<span data-tip="Edit river" class="icon-pencil"></span>
|
||||
<span data-tip="Remove river" class="icon-trash-empty"></span>
|
||||
</div>`;
|
||||
}
|
||||
body.insertAdjacentHTML('beforeend', lines);
|
||||
|
||||
// update footer
|
||||
riversFooterNumber.innerHTML = pack.rivers.length;
|
||||
const averageDischarge = rn(d3.mean(pack.rivers.map((r) => r.discharge)));
|
||||
riversFooterDischarge.innerHTML = averageDischarge + ' m³/s';
|
||||
const averageLength = rn(d3.mean(pack.rivers.map((r) => r.length)));
|
||||
riversFooterLength.innerHTML = averageLength * distanceScaleInput.value + ' ' + unit;
|
||||
const averageWidth = rn(d3.mean(pack.rivers.map((r) => r.width)), 3);
|
||||
riversFooterWidth.innerHTML = rn(averageWidth * distanceScaleInput.value, 3) + ' ' + unit;
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => riverHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => riverHighlightOff(ev)));
|
||||
body.querySelectorAll('div > span.icon-dot-circled').forEach((el) => el.addEventListener('click', zoomToRiver));
|
||||
body.querySelectorAll('div > span.icon-pencil').forEach((el) => el.addEventListener('click', openRiverEditor));
|
||||
body.querySelectorAll('div > span.icon-trash-empty').forEach((el) => el.addEventListener('click', triggerRiverRemove));
|
||||
|
||||
applySorting(riversHeader);
|
||||
}
|
||||
|
||||
function riverHighlightOn(event) {
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
const r = +event.target.dataset.id;
|
||||
rivers
|
||||
.select('#river' + r)
|
||||
.attr('stroke', 'red')
|
||||
.attr('stroke-width', 1);
|
||||
}
|
||||
|
||||
function riverHighlightOff(e) {
|
||||
const r = +e.target.dataset.id;
|
||||
rivers
|
||||
.select('#river' + r)
|
||||
.attr('stroke', null)
|
||||
.attr('stroke-width', null);
|
||||
}
|
||||
|
||||
function zoomToRiver() {
|
||||
const r = +this.parentNode.dataset.id;
|
||||
<<<<<<< HEAD
|
||||
const river = rivers.select('#river' + r).node();
|
||||
highlightElement(river);
|
||||
=======
|
||||
const river = rivers.select("#river" + r).node();
|
||||
highlightElement(river, 3);
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function toggleBasinsHightlight() {
|
||||
if (rivers.attr('data-basin') === 'hightlighted') {
|
||||
rivers.selectAll('*').attr('fill', null);
|
||||
rivers.attr('data-basin', null);
|
||||
} else {
|
||||
rivers.attr('data-basin', 'hightlighted');
|
||||
const basins = [...new Set(pack.rivers.map((r) => r.basin))];
|
||||
const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'];
|
||||
|
||||
basins.forEach((b, i) => {
|
||||
const color = colors[i % colors.length];
|
||||
pack.rivers
|
||||
.filter((r) => r.basin === b)
|
||||
.forEach((r) => {
|
||||
rivers.select('#river' + r.i).attr('fill', color);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function downloadRiversData() {
|
||||
let data = 'Id,River,Type,Discharge,Length,Width,Basin\n'; // headers
|
||||
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
const d = el.dataset;
|
||||
const discharge = d.discharge + ' m³/s';
|
||||
const length = rn(d.length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
const width = rn(d.width * distanceScaleInput.value, 3) + ' ' + distanceUnitInput.value;
|
||||
data += [d.id, d.name, d.type, discharge, length, width, d.basin].join(',') + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName('Rivers') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function openRiverEditor() {
|
||||
const id = 'river' + this.parentNode.dataset.id;
|
||||
editRiver(id);
|
||||
}
|
||||
|
||||
function triggerRiverRemove() {
|
||||
const river = +this.parentNode.dataset.id;
|
||||
alertMessage.innerHTML = `Are you sure you want to remove the river?
|
||||
All tributaries will be auto-removed`;
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
width: '22em',
|
||||
title: 'Remove river',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
Rivers.remove(river);
|
||||
riversOverviewAddLines();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllRiversRemove() {
|
||||
alertMessage.innerHTML = `Are you sure you want to remove all rivers?`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove all rivers',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
removeAllRivers();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllRivers() {
|
||||
pack.rivers = [];
|
||||
pack.cells.r = new Uint16Array(pack.cells.i.length);
|
||||
rivers.selectAll('*').remove();
|
||||
riversOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
|
@ -502,6 +502,7 @@ function editStates() {
|
|||
pack.cells.province.forEach((pr, i) => {
|
||||
if (pr === p) pack.cells.province[i] = 0;
|
||||
});
|
||||
|
||||
const coaId = 'provinceCOA' + p;
|
||||
if (document.getElementById(coaId)) document.getElementById(coaId).remove();
|
||||
emblems.select(`#provinceEmblems > use[data-i='${p}']`).remove();
|
||||
|
|
@ -568,19 +569,20 @@ function editStates() {
|
|||
|
||||
function showStatesChart() {
|
||||
// build hierarchy tree
|
||||
const data = pack.states.filter((s) => !s.removed);
|
||||
const statesData = pack.states.filter((s) => !s.removed);
|
||||
if (statesData.length < 2) return tip('There are no states to show', false, 'error');
|
||||
|
||||
const root = d3
|
||||
.stratify()
|
||||
.id((d) => d.i)
|
||||
.parentId((d) => (d.i ? 0 : null))(data)
|
||||
.parentId((d) => (d.i ? 0 : null))(statesData)
|
||||
.sum((d) => d.area)
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
const width = 150 + 200 * uiSizeOutput.value,
|
||||
height = 150 + 200 * uiSizeOutput.value;
|
||||
const size = 150 + 200 * uiSizeOutput.value;
|
||||
const margin = {top: 0, right: -50, bottom: 0, left: -50};
|
||||
const w = width - margin.left - margin.right;
|
||||
const h = height - margin.top - margin.bottom;
|
||||
const w = size - margin.left - margin.right;
|
||||
const h = size - margin.top - margin.bottom;
|
||||
const treeLayout = d3.pack().size([w, h]).padding(3);
|
||||
|
||||
// prepare svg
|
||||
|
|
@ -592,12 +594,13 @@ function editStates() {
|
|||
<option value="burgs">Burgs number</option>
|
||||
</select>`;
|
||||
alertMessage.innerHTML += `<div id='statesInfo' class='chartInfo'>‍</div>`;
|
||||
|
||||
const svg = d3
|
||||
.select('#alertMessage')
|
||||
.insert('svg', '#statesInfo')
|
||||
.attr('id', 'statesTree')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.attr('width', size)
|
||||
.attr('height', size)
|
||||
.style('font-family', 'Almendra SC')
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'central');
|
||||
|
|
@ -819,9 +822,9 @@ function editStates() {
|
|||
}
|
||||
|
||||
function applyStatesManualAssignent() {
|
||||
const cells = pack.cells,
|
||||
affectedStates = [],
|
||||
affectedProvinces = [];
|
||||
const {cells} = pack;
|
||||
const affectedStates = [];
|
||||
const affectedProvinces = [];
|
||||
|
||||
statesBody
|
||||
.select('#temp')
|
||||
|
|
@ -837,77 +840,143 @@ function editStates() {
|
|||
|
||||
if (affectedStates.length) {
|
||||
refreshStatesEditor();
|
||||
if (!layerIsOn('toggleStates')) toggleStates();
|
||||
else drawStates();
|
||||
layerIsOn('toggleStates') ? drawStates() : toggleStates();
|
||||
if (adjustLabels.checked) BurgsAndStates.drawStateLabels([...new Set(affectedStates)]);
|
||||
adjustProvinces([...new Set(affectedProvinces)]);
|
||||
if (!layerIsOn('toggleBorders')) toggleBorders();
|
||||
else drawBorders();
|
||||
layerIsOn('toggleBorders') ? drawBorders() : toggleBorders();
|
||||
if (layerIsOn('toggleProvinces')) drawProvinces();
|
||||
}
|
||||
|
||||
exitStatesManualAssignment();
|
||||
}
|
||||
|
||||
function adjustProvinces(affectedProvinces) {
|
||||
const {cells, provinces, states} = pack;
|
||||
const form = {Zone: 1, Area: 1, Territory: 2, Province: 1};
|
||||
|
||||
affectedProvinces.forEach((p) => {
|
||||
if (!p) return; // do nothing if neutral lands are captured
|
||||
const old = provinces[p].state;
|
||||
|
||||
// remove province from state provinces list
|
||||
if (states[old]?.provinces?.includes(p)) states[old].provinces.splice(states[old].provinces.indexOf(p), 1);
|
||||
const {cells, provinces, states, burgs} = pack;
|
||||
|
||||
affectedProvinces.forEach((provinceId) => {
|
||||
// find states owning at least 1 province cell
|
||||
const provCells = cells.i.filter((i) => cells.province[i] === p);
|
||||
const provCells = cells.i.filter((i) => cells.state[i] && cells.province[i] === provinceId);
|
||||
const provStates = [...new Set(provCells.map((i) => cells.state[i]))];
|
||||
|
||||
// assign province to its center owner; if center is neutral, remove province
|
||||
const owner = cells.state[provinces[p].center];
|
||||
if (owner) {
|
||||
const name = provinces[p].name;
|
||||
// province is captured completely => change owner or remove
|
||||
if (provinceId && provStates.length === 1) return changeProvinceOwner(provinceId, provStates[0], provCells);
|
||||
|
||||
// if province is a historical part of another state's province, unite with old province
|
||||
const part = states[owner].provinces.find((n) => name.includes(provinces[n].name));
|
||||
if (part) {
|
||||
provinces[p].removed = true;
|
||||
provCells.filter((i) => cells.state[i] === owner).forEach((i) => (cells.province[i] = part));
|
||||
} else {
|
||||
provinces[p].state = owner;
|
||||
states[owner].provinces.push(p);
|
||||
provinces[p].color = getMixedColor(states[owner].color);
|
||||
}
|
||||
} else {
|
||||
provinces[p].removed = true;
|
||||
provCells.filter((i) => !cells.state[i]).forEach((i) => (cells.province[i] = 0));
|
||||
}
|
||||
|
||||
// create new provinces for non-main part
|
||||
provStates
|
||||
.filter((s) => s && s !== owner)
|
||||
.forEach((s) =>
|
||||
createProvince(
|
||||
p,
|
||||
s,
|
||||
provCells.filter((i) => cells.state[i] === s)
|
||||
)
|
||||
);
|
||||
// province is captured partially => split province
|
||||
splitProvince(provinceId, provStates, provCells);
|
||||
});
|
||||
|
||||
function createProvince(initProv, state, provCells) {
|
||||
const province = provinces.length;
|
||||
provCells.forEach((i) => (cells.province[i] = province));
|
||||
function changeProvinceOwner(provinceId, newOwnerId, provinceCells) {
|
||||
const province = provinces[provinceId];
|
||||
const prevOwner = states[province.state];
|
||||
|
||||
const burgCell = provCells.find((i) => cells.burg[i]);
|
||||
const center = burgCell ? burgCell : provCells[0];
|
||||
const burg = burgCell ? cells.burg[burgCell] : 0;
|
||||
// remove province from old owner list
|
||||
prevOwner.provinces = prevOwner.provinces.filter((province) => province !== provinceId);
|
||||
|
||||
const name = burgCell && P(0.7) ? getAdjective(pack.burgs[burg].name) : getAdjective(states[state].name) + ' ' + provinces[initProv].name.split(' ').slice(-1)[0];
|
||||
const formName = name.split(' ').length > 1 ? provinces[initProv].formName : rw(form);
|
||||
const fullName = name + ' ' + formName;
|
||||
const color = getMixedColor(states[state].color);
|
||||
provinces.push({i: province, state, center, burg, name, formName, fullName, color});
|
||||
if (newOwnerId) {
|
||||
// new owner is a state => change owner
|
||||
province.state = newOwnerId;
|
||||
states[newOwnerId].provinces.push(provinceId);
|
||||
} else {
|
||||
// new owner is neutral => remove province
|
||||
provinces[provinceId] = {i: provinceId, removed: true};
|
||||
provinceCells.forEach((i) => {
|
||||
cells.province[i] = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function splitProvince(provinceId, provinceStates, provinceCells) {
|
||||
const province = provinces[provinceId];
|
||||
const prevOwner = states[province.state];
|
||||
const provinceCenterOwner = cells.state[province.center];
|
||||
|
||||
provinceStates.forEach((stateId) => {
|
||||
const stateProvinceCells = provinceCells.filter((i) => cells.state[i] === stateId);
|
||||
|
||||
if (stateId === provinceCenterOwner) {
|
||||
// province center is owned by the same state => do nothing for this state
|
||||
if (stateId === prevOwner.i) return;
|
||||
|
||||
// province center is captured by neutrals => remove state
|
||||
if (!stateId) {
|
||||
provinces[provinceId] = {i: provinceId, removed: true};
|
||||
stateProvinceCells.forEach((i) => {
|
||||
cells.province[i] = 0;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// reassign province ownership to province center owner
|
||||
prevOwner.provinces = prevOwner.provinces.filter((province) => province !== provinceId);
|
||||
province.state = stateId;
|
||||
province.color = getMixedColor(states[stateId].color);
|
||||
states[stateId].provinces.push(provinceId);
|
||||
return;
|
||||
}
|
||||
|
||||
// province cells captured by neutrals => clear province
|
||||
if (!stateId) {
|
||||
stateProvinceCells.forEach((i) => {
|
||||
cells.province[i] = 0;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// a few province cells owned by state => add to closes province
|
||||
if (stateProvinceCells.length < 20) {
|
||||
const closestProvince = findClosestProvince(provinceId, stateId, stateProvinceCells);
|
||||
if (closestProvince) {
|
||||
stateProvinceCells.forEach((i) => {
|
||||
cells.province[i] = closestProvince;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// some province cells owned by state => create new province
|
||||
createProvince(province, stateId, stateProvinceCells);
|
||||
});
|
||||
}
|
||||
|
||||
function createProvince(oldProvince, stateId, provinceCells) {
|
||||
const newProvinceId = provinces.length;
|
||||
const burgCell = provinceCells.find((i) => cells.burg[i]);
|
||||
const center = burgCell ? burgCell : provinceCells[0];
|
||||
const burgId = burgCell ? cells.burg[burgCell] : 0;
|
||||
const burg = burgId ? burgs[burgId] : null;
|
||||
const culture = cells.culture[center];
|
||||
|
||||
const nameByBurg = burgCell && P(0.5);
|
||||
const name = nameByBurg ? burg.name : oldProvince.name || Names.getState(Names.getCultureShort(culture), culture);
|
||||
|
||||
const formOptions = ['Zone', 'Area', 'Territory', 'Province'];
|
||||
const formName = burgCell && oldProvince.formName ? oldProvince.formName : ra(formOptions);
|
||||
|
||||
const color = getMixedColor(states[stateId].color);
|
||||
|
||||
const kinship = nameByBurg ? 0.8 : 0.4;
|
||||
const type = BurgsAndStates.getType(center, burg?.port);
|
||||
const coa = COA.generate(burg?.coa || states[stateId].coa, kinship, burg ? null : 0.9, type);
|
||||
coa.shield = COA.getShield(culture, stateId);
|
||||
|
||||
provinces.push({i: newProvinceId, state: stateId, center, burg: burgId, name, formName, fullName: `${name} ${formName}`, color, coa});
|
||||
|
||||
provinceCells.forEach((i) => {
|
||||
cells.province[i] = newProvinceId;
|
||||
});
|
||||
|
||||
states[stateId].provinces.push(newProvinceId);
|
||||
}
|
||||
|
||||
function findClosestProvince(provinceId, stateId, sourceCells) {
|
||||
const borderCell = sourceCells.find((i) =>
|
||||
cells.c[i].some((c) => {
|
||||
return cells.state[c] === stateId && cells.province[c] && cells.province[c] !== provinceId;
|
||||
})
|
||||
);
|
||||
|
||||
const closesProvince = borderCell && cells.c[borderCell].map((c) => cells.province[c]).find((province) => province && province !== provinceId);
|
||||
return closesProvince;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1007,7 +1076,22 @@ function editStates() {
|
|||
cells.state[center] = newState;
|
||||
cells.province[center] = 0;
|
||||
|
||||
states.push({i: newState, name, diplomacy, provinces: [], color, expansionism: 0.5, capital: burg, type: 'Generic', center, culture, military: [], alert: 1, coa, pole});
|
||||
states.push({
|
||||
i: newState,
|
||||
name,
|
||||
diplomacy,
|
||||
provinces: [],
|
||||
color,
|
||||
expansionism: 0.5,
|
||||
capital: burg,
|
||||
type: 'Generic',
|
||||
center,
|
||||
culture,
|
||||
military: [],
|
||||
alert: 1,
|
||||
coa,
|
||||
pole
|
||||
});
|
||||
BurgsAndStates.collectStatistics();
|
||||
BurgsAndStates.defineStateForms([newState]);
|
||||
adjustProvinces([cells.province[center]]);
|
||||
|
|
@ -1050,12 +1134,13 @@ function editStates() {
|
|||
|
||||
function downloadStatesData() {
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
let data = 'Id,State,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area ' + unit + ',Total Population,Rural Population,Urban Population\n'; // headers
|
||||
|
||||
let data = 'Id,State,Full Name,Form,Color,Capital,Culture,Type,Expansionism,Cells,Burgs,Area ' + unit + ',Total Population,Rural Population,Urban Population\n'; // headers
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
const key = parseInt(el.dataset.id);
|
||||
const statePack = pack.states[key];
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.name + ',';
|
||||
data += (statePack.fullName ? statePack.fullName : '') + ',';
|
||||
data += el.dataset.form + ',';
|
||||
data += el.dataset.color + ',';
|
||||
data += el.dataset.capital + ',';
|
||||
|
|
@ -1066,8 +1151,8 @@ function editStates() {
|
|||
data += el.dataset.burgs + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + ',';
|
||||
data += `${Math.round(pack.states[key].rural * populationRate)},`;
|
||||
data += `${Math.round(pack.states[key].urban * populationRate * urbanization)}\n`;
|
||||
data += `${Math.round(statePack.rural * populationRate)},`;
|
||||
data += `${Math.round(statePack.urban * populationRate * urbanization)}\n`;
|
||||
});
|
||||
|
||||
const name = getFileName('States') + '.csv';
|
||||
|
|
|
|||
1351
modules/ui/states-editor.js.orig
Normal file
1351
modules/ui/states-editor.js.orig
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
1701
modules/ui/style.js.orig
Normal file
1701
modules/ui/style.js.orig
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,15 +1,15 @@
|
|||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
'use strict';
|
||||
// module to control the Tools options (click to edit, to re-geenerate, tp add)
|
||||
|
||||
toolsContent.addEventListener('click', function (event) {
|
||||
if (customization) {
|
||||
tip('Please exit the customization mode first', false, 'warning');
|
||||
return;
|
||||
}
|
||||
if (event.target.tagName !== 'BUTTON') return;
|
||||
if (!['BUTTON', 'I'].includes(event.target.tagName)) return;
|
||||
const button = event.target.id;
|
||||
|
||||
// Click to open Editor buttons
|
||||
// click on open Editor buttons
|
||||
if (button === 'editHeightmapButton') editHeightmap();
|
||||
else if (button === 'editBiomesButton') editBiomes();
|
||||
else if (button === 'editStatesButton') editStates();
|
||||
|
|
@ -17,7 +17,6 @@ toolsContent.addEventListener('click', function (event) {
|
|||
else if (button === 'editDiplomacyButton') editDiplomacy();
|
||||
else if (button === 'editCulturesButton') editCultures();
|
||||
else if (button === 'editReligions') editReligions();
|
||||
else if (button === 'editResources') editResources();
|
||||
else if (button === 'editEmblemButton') openEmblemEditor();
|
||||
else if (button === 'editNamesBaseButton') editNamesbase();
|
||||
else if (button === 'editUnitsButton') editUnits();
|
||||
|
|
@ -26,9 +25,10 @@ toolsContent.addEventListener('click', function (event) {
|
|||
else if (button === 'overviewBurgsButton') overviewBurgs();
|
||||
else if (button === 'overviewRiversButton') overviewRivers();
|
||||
else if (button === 'overviewMilitaryButton') overviewMilitary();
|
||||
else if (button === 'overviewMarkersButton') overviewMarkers();
|
||||
else if (button === 'overviewCellsButton') viewCellDetails();
|
||||
|
||||
// Click to Regenerate buttons
|
||||
// click on Regenerate buttons
|
||||
if (event.target.parentNode.id === 'regenerateFeature') {
|
||||
if (sessionStorage.getItem('regenerateFeatureDontAsk')) {
|
||||
processFeatureRegeneration(event, button);
|
||||
|
|
@ -61,7 +61,10 @@ toolsContent.addEventListener('click', function (event) {
|
|||
});
|
||||
}
|
||||
|
||||
// Click to Add buttons
|
||||
// click on Configure regenerate buttons
|
||||
if (button === 'configRegenerateMarkers') configMarkersGeneration();
|
||||
|
||||
// click on Add buttons
|
||||
if (button === 'addLabel') toggleAddLabel();
|
||||
else if (button === 'addBurgTool') toggleAddBurg();
|
||||
else if (button === 'addRiver') toggleAddRiver();
|
||||
|
|
@ -84,13 +87,12 @@ function processFeatureRegeneration(event, button) {
|
|||
else if (button === 'regenerateStates') regenerateStates();
|
||||
else if (button === 'regenerateProvinces') regenerateProvinces();
|
||||
else if (button === 'regenerateBurgs') regenerateBurgs();
|
||||
else if (button === 'regenerateResources') regenerateResources();
|
||||
else if (button === 'regenerateEmblems') regenerateEmblems();
|
||||
else if (button === 'regenerateReligions') regenerateReligions();
|
||||
else if (button === 'regenerateCultures') regenerateCultures();
|
||||
else if (button === 'regenerateMilitary') regenerateMilitary();
|
||||
else if (button === 'regenerateIce') regenerateIce();
|
||||
else if (button === 'regenerateMarkers') regenerateMarkers(event);
|
||||
else if (button === 'regenerateMarkers') regenerateMarkers();
|
||||
else if (button === 'regenerateZones') regenerateZones(event);
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +121,7 @@ function regenerateRivers() {
|
|||
Lakes.defineGroup();
|
||||
Rivers.specify();
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
else drawRivers();
|
||||
}
|
||||
|
||||
function recalculatePopulation() {
|
||||
|
|
@ -137,21 +140,11 @@ function recalculatePopulation() {
|
|||
function regenerateStates() {
|
||||
const localSeed = Math.floor(Math.random() * 1e9); // new random seed
|
||||
Math.random = aleaPRNG(localSeed);
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
if (!burgs.length) {
|
||||
tip('No burgs to generate states. Please create burgs first', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (burgs.length < +regionsInput.value) {
|
||||
tip(`Not enough burgs to generate ${regionsInput.value} states. Will generate only ${burgs.length} states`, false, 'warn');
|
||||
}
|
||||
|
||||
// burg local ids sorted by a bit randomized population:
|
||||
const sorted = burgs
|
||||
.map((b, i) => [i, b.population * Math.random()])
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map((b) => b[0]);
|
||||
const capitalsTree = d3.quadtree();
|
||||
const statesCount = +regionsInput.value;
|
||||
const burgs = pack.burgs.filter((b) => b.i && !b.removed);
|
||||
if (!burgs.length) return tip('There are no any burgs to generate states. Please create burgs first', false, 'error');
|
||||
if (burgs.length < statesCount) tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, 'warn');
|
||||
|
||||
// turn all old capitals into towns
|
||||
burgs
|
||||
|
|
@ -168,8 +161,7 @@ function regenerateStates() {
|
|||
|
||||
unfog();
|
||||
|
||||
// if desired states number is 0
|
||||
if (regionsInput.value == 0) {
|
||||
if (!statesCount) {
|
||||
tip(`Cannot generate zero states. Please check the <i>States Number</i> option`, false, 'warn');
|
||||
pack.states = pack.states.slice(0, 1); // remove all except of neutrals
|
||||
pack.states[0].diplomacy = []; // clear diplomacy
|
||||
|
|
@ -185,26 +177,34 @@ function regenerateStates() {
|
|||
return;
|
||||
}
|
||||
|
||||
const neutral = pack.states[0].name;
|
||||
const count = Math.min(+regionsInput.value, burgs.length);
|
||||
// burg local ids sorted by a bit randomized population:
|
||||
const sortedBurgs = burgs
|
||||
.map((b, i) => [b, b.population * Math.random()])
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map((b) => b[0]);
|
||||
const capitalsTree = d3.quadtree();
|
||||
|
||||
const neutral = pack.states[0].name; // neutrals name
|
||||
const count = Math.min(statesCount, burgs.length) + 1; // +1 for neutral
|
||||
let spacing = (graphWidth + graphHeight) / 2 / count; // min distance between capitals
|
||||
|
||||
pack.states = d3.range(count).map((i) => {
|
||||
if (!i) return {i, name: neutral};
|
||||
|
||||
let capital = null,
|
||||
x = 0,
|
||||
y = 0;
|
||||
for (const i of sorted) {
|
||||
capital = burgs[i];
|
||||
(x = capital.x), (y = capital.y);
|
||||
if (capitalsTree.find(x, y, spacing) === undefined) break;
|
||||
let capital = null;
|
||||
for (const burg of sortedBurgs) {
|
||||
const {x, y} = burg;
|
||||
if (capitalsTree.find(x, y, spacing) === undefined) {
|
||||
burg.capital = 1;
|
||||
capital = burg;
|
||||
capitalsTree.add([x, y]);
|
||||
moveBurgToGroup(burg.i, 'cities');
|
||||
break;
|
||||
}
|
||||
|
||||
spacing = Math.max(spacing - 1, 1);
|
||||
}
|
||||
|
||||
capitalsTree.add([x, y]);
|
||||
capital.capital = 1;
|
||||
moveBurgToGroup(capital.i, 'cities');
|
||||
|
||||
const culture = capital.culture;
|
||||
const basename = capital.name.length < 9 && capital.cell % 5 === 0 ? capital.name : Names.getCulture(culture, 3, 6, '', 0);
|
||||
const name = Names.getState(basename, culture);
|
||||
|
|
@ -337,13 +337,6 @@ function regenerateBurgs() {
|
|||
if (document.getElementById('statesEditorRefresh').offsetParent) statesEditorRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateResources() {
|
||||
Resources.generate();
|
||||
goods.selectAll('*').remove();
|
||||
if (layerIsOn('toggleResources')) drawResources();
|
||||
refreshAllEditors();
|
||||
}
|
||||
|
||||
function regenerateEmblems() {
|
||||
// remove old emblems
|
||||
document.querySelectorAll('[id^=stateCOA]').forEach((el) => el.remove());
|
||||
|
|
@ -424,23 +417,11 @@ function regenerateIce() {
|
|||
drawIce();
|
||||
}
|
||||
|
||||
function regenerateMarkers(event) {
|
||||
if (isCtrlClick(event)) prompt('Please provide markers number multiplier', {default: 1, step: 0.01, min: 0, max: 100}, (v) => addNumberOfMarkers(v));
|
||||
else addNumberOfMarkers(gauss(1, 0.5, 0.3, 5, 2));
|
||||
|
||||
function addNumberOfMarkers(number) {
|
||||
// remove existing markers and assigned notes
|
||||
markers
|
||||
.selectAll('use')
|
||||
.each(function () {
|
||||
const index = notes.findIndex((n) => n.id === this.id);
|
||||
if (index != -1) notes.splice(index, 1);
|
||||
})
|
||||
.remove();
|
||||
|
||||
addMarkers(number);
|
||||
if (!layerIsOn('toggleMarkers')) toggleMarkers();
|
||||
}
|
||||
function regenerateMarkers() {
|
||||
Markers.regenerate();
|
||||
turnButtonOn('toggleMarkers');
|
||||
drawMarkers();
|
||||
if (document.getElementById('markersOverviewRefresh').offsetParent) markersOverviewRefresh.click();
|
||||
}
|
||||
|
||||
function regenerateZones(event) {
|
||||
|
|
@ -485,7 +466,10 @@ function addLabelOnClick() {
|
|||
const name = Names.getCulture(culture);
|
||||
const id = getNextId('label');
|
||||
|
||||
let group = labels.select('#addedLabels');
|
||||
// use most recently selected label group
|
||||
let selected = labelGroupSelect.value;
|
||||
const symbol = selected ? '#' + selected : '#addedLabels';
|
||||
let group = labels.select(symbol);
|
||||
if (!group.size())
|
||||
group = labels
|
||||
.append('g')
|
||||
|
|
@ -495,7 +479,6 @@ function addLabelOnClick() {
|
|||
.attr('stroke', '#3a3a3a')
|
||||
.attr('stroke-width', 0)
|
||||
.attr('font-family', 'Almendra SC')
|
||||
.attr('data-font', 'Almendra+SC')
|
||||
.attr('font-size', 18)
|
||||
.attr('data-size', 18)
|
||||
.attr('filter', null);
|
||||
|
|
@ -697,7 +680,7 @@ function addRouteOnClick() {
|
|||
}
|
||||
|
||||
function toggleAddMarker() {
|
||||
const pressed = document.getElementById('addMarker').classList.contains('pressed');
|
||||
const pressed = document.getElementById('addMarker')?.classList.contains('pressed');
|
||||
if (pressed) {
|
||||
unpressClickToAddButton();
|
||||
return;
|
||||
|
|
@ -705,45 +688,115 @@ function toggleAddMarker() {
|
|||
|
||||
addFeature.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed'));
|
||||
addMarker.classList.add('pressed');
|
||||
closeDialogs('.stable');
|
||||
markersAddFromOverview.classList.add('pressed');
|
||||
|
||||
viewbox.style('cursor', 'crosshair').on('click', addMarkerOnClick);
|
||||
tip('Click on map to add a marker. Hold Shift to add multiple', true);
|
||||
if (!layerIsOn('toggleMarkers')) toggleMarkers();
|
||||
}
|
||||
|
||||
function addMarkerOnClick() {
|
||||
const {markers} = pack;
|
||||
const point = d3.mouse(this);
|
||||
const x = rn(point[0], 2),
|
||||
y = rn(point[1], 2);
|
||||
const id = getNextId('markerElement');
|
||||
const x = rn(point[0], 2);
|
||||
const y = rn(point[1], 2);
|
||||
const i = last(markers).i + 1;
|
||||
|
||||
const selected = markerSelectGroup.value;
|
||||
const valid =
|
||||
selected &&
|
||||
d3
|
||||
.select('#defs-markers')
|
||||
.select('#' + selected)
|
||||
.size();
|
||||
const symbol = valid ? '#' + selected : '#marker0';
|
||||
const added = markers.select("[data-id='" + symbol + "']").size();
|
||||
let desired = valid && added ? markers.select("[data-id='" + symbol + "']").attr('data-size') : 1;
|
||||
if (isNaN(desired)) desired = 1;
|
||||
const size = desired * 5 + 25 / scale;
|
||||
const isMarkerSelected = elSelected?.node()?.parentElement?.id === 'markers';
|
||||
const selectedMarker = isMarkerSelected ? markers.find((marker) => marker.i === +elSelected.attr('id').slice(6)) : null;
|
||||
const baseMarker = selectedMarker || {icon: '❓'};
|
||||
const marker = {...baseMarker, i, x, y};
|
||||
|
||||
markers
|
||||
.append('use')
|
||||
.attr('id', id)
|
||||
.attr('xlink:href', symbol)
|
||||
.attr('data-id', symbol)
|
||||
.attr('data-x', x)
|
||||
.attr('data-y', y)
|
||||
.attr('x', x - size / 2)
|
||||
.attr('y', y - size)
|
||||
.attr('data-size', desired)
|
||||
.attr('width', size)
|
||||
.attr('height', size);
|
||||
markers.push(marker);
|
||||
const markersElement = document.getElementById('markers');
|
||||
const rescale = +markersElement.getAttribute('rescale');
|
||||
markersElement.insertAdjacentHTML('beforeend', drawMarker(marker, rescale));
|
||||
|
||||
if (d3.event.shiftKey === false) unpressClickToAddButton();
|
||||
if (d3.event.shiftKey === false) {
|
||||
document.getElementById('markerAdd').classList.remove('pressed');
|
||||
document.getElementById('markersAddFromOverview').classList.remove('pressed');
|
||||
unpressClickToAddButton();
|
||||
}
|
||||
}
|
||||
|
||||
function configMarkersGeneration() {
|
||||
drawConfigTable();
|
||||
|
||||
function drawConfigTable() {
|
||||
const {markers} = pack;
|
||||
const config = Markers.getConfig();
|
||||
const headers = `<thead style='font-weight:bold'><tr>
|
||||
<td data-tip="Marker type name">Type</td>
|
||||
<td data-tip="Marker icon">Icon</td>
|
||||
<td data-tip="Marker number multiplier">Multiplier</td>
|
||||
<td data-tip="Number of markers of that type on the current map">Number</td>
|
||||
</tr></thead>`;
|
||||
const lines = config.map(({type, icon, multiplier}, index) => {
|
||||
const inputId = `markerIconInput${index}`;
|
||||
return `<tr>
|
||||
<td><input value="${type}" /></td>
|
||||
<td>
|
||||
<input id="${inputId}" style="width: 5em" value="${icon}" />
|
||||
<i class="icon-edit pointer" style="position: absolute; margin:.4em 0 0 -1.4em; font-size:.85em"></i>
|
||||
</td>
|
||||
<td><input type="number" min="0" max="100" step="0.1" value="${multiplier}" /></td>
|
||||
<td style="text-align:center">${markers.filter((marker) => marker.type === type).length}</td>
|
||||
</tr>`;
|
||||
});
|
||||
const table = `<table class="table">${headers}<tbody>${lines.join('')}</tbody></table>`;
|
||||
alertMessage.innerHTML = table;
|
||||
|
||||
alertMessage.querySelectorAll('i').forEach((selectIconButton) => {
|
||||
selectIconButton.addEventListener('click', function () {
|
||||
const input = this.previousElementSibling;
|
||||
selectIcon(input.value, (icon) => (input.value = icon));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const applyChanges = () => {
|
||||
const rows = alertMessage.querySelectorAll('tbody > tr');
|
||||
const rowsData = Array.from(rows).map((row) => {
|
||||
const inputs = row.querySelectorAll('input');
|
||||
return {
|
||||
type: inputs[0].value,
|
||||
icon: inputs[1].value,
|
||||
multiplier: parseFloat(inputs[2].value)
|
||||
};
|
||||
});
|
||||
|
||||
const config = Markers.getConfig();
|
||||
const newConfig = config.map((markerType, index) => {
|
||||
const {type, icon, multiplier} = rowsData[index];
|
||||
return {...markerType, type, icon, multiplier};
|
||||
});
|
||||
|
||||
Markers.setConfig(newConfig);
|
||||
};
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Markers generation settings',
|
||||
position: {my: 'left top', at: 'left+10 top+10', of: 'svg', collision: 'fit'},
|
||||
buttons: {
|
||||
Regenerate: () => {
|
||||
applyChanges();
|
||||
regenerateMarkers();
|
||||
drawConfigTable();
|
||||
},
|
||||
Close: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
open: function () {
|
||||
const buttons = $(this).dialog('widget').find('.ui-dialog-buttonset > button');
|
||||
buttons[0].addEventListener('mousemove', () => tip('Apply changes and regenerate markers'));
|
||||
buttons[1].addEventListener('mousemove', () => tip('Close the window'));
|
||||
},
|
||||
close: function () {
|
||||
$(this).dialog('destroy');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function viewCellDetails() {
|
||||
|
|
|
|||
1014
modules/ui/tools.js.orig
Normal file
1014
modules/ui/tools.js.orig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -31,6 +31,8 @@ function editUnits() {
|
|||
document.getElementById('populationRateInput').addEventListener('change', changePopulationRate);
|
||||
document.getElementById('urbanizationOutput').addEventListener('input', changeUrbanizationRate);
|
||||
document.getElementById('urbanizationInput').addEventListener('change', changeUrbanizationRate);
|
||||
document.getElementById('urbanDensityOutput').addEventListener('input', changeUrbanDensity);
|
||||
document.getElementById('urbanDensityInput').addEventListener('change', changeUrbanDensity);
|
||||
|
||||
document.getElementById('addLinearRuler').addEventListener('click', addRuler);
|
||||
document.getElementById('addOpisometer').addEventListener('click', toggleOpisometerMode);
|
||||
|
|
@ -93,6 +95,10 @@ function editUnits() {
|
|||
urbanization = +this.value;
|
||||
}
|
||||
|
||||
function changeUrbanDensity() {
|
||||
urbanDensity = +this.value;
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
// distanceScale
|
||||
document.getElementById('distanceScaleOutput').value = 3;
|
||||
|
|
@ -135,8 +141,9 @@ function editUnits() {
|
|||
// population
|
||||
populationRate = populationRateOutput.value = populationRateInput.value = 1000;
|
||||
urbanization = urbanizationOutput.value = urbanizationInput.value = 1;
|
||||
localStorage.removeItem('populationRate');
|
||||
urbanDensity = urbanDensityOutput.value = urbanDensityInput.value = 10;
|
||||
localStorage.removeItem('urbanization');
|
||||
localStorage.removeItem('urbanDensity');
|
||||
}
|
||||
|
||||
function addRuler() {
|
||||
|
|
|
|||
329
modules/ui/units-editor.js.orig
Normal file
329
modules/ui/units-editor.js.orig
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
'use strict';
|
||||
function editUnits() {
|
||||
closeDialogs('#unitsEditor, .stable');
|
||||
$('#unitsEditor').dialog();
|
||||
|
||||
if (modules.editUnits) return;
|
||||
modules.editUnits = true;
|
||||
|
||||
$('#unitsEditor').dialog({
|
||||
title: 'Units Editor',
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
<<<<<<< HEAD
|
||||
document.getElementById('distanceUnitInput').addEventListener('change', changeDistanceUnit);
|
||||
document.getElementById('distanceScaleOutput').addEventListener('input', changeDistanceScale);
|
||||
document.getElementById('distanceScaleInput').addEventListener('change', changeDistanceScale);
|
||||
document.getElementById('heightUnit').addEventListener('change', changeHeightUnit);
|
||||
document.getElementById('heightExponentInput').addEventListener('input', changeHeightExponent);
|
||||
document.getElementById('heightExponentOutput').addEventListener('input', changeHeightExponent);
|
||||
document.getElementById('temperatureScale').addEventListener('change', changeTemperatureScale);
|
||||
document.getElementById('barSizeOutput').addEventListener('input', drawScaleBar);
|
||||
document.getElementById('barSizeInput').addEventListener('input', drawScaleBar);
|
||||
document.getElementById('barLabel').addEventListener('input', drawScaleBar);
|
||||
document.getElementById('barPosX').addEventListener('input', fitScaleBar);
|
||||
document.getElementById('barPosY').addEventListener('input', fitScaleBar);
|
||||
document.getElementById('barBackOpacity').addEventListener('input', changeScaleBarOpacity);
|
||||
document.getElementById('barBackColor').addEventListener('input', changeScaleBarColor);
|
||||
|
||||
document.getElementById('populationRateOutput').addEventListener('input', changePopulationRate);
|
||||
document.getElementById('populationRateInput').addEventListener('change', changePopulationRate);
|
||||
document.getElementById('urbanizationOutput').addEventListener('input', changeUrbanizationRate);
|
||||
document.getElementById('urbanizationInput').addEventListener('change', changeUrbanizationRate);
|
||||
|
||||
document.getElementById('addLinearRuler').addEventListener('click', addRuler);
|
||||
document.getElementById('addOpisometer').addEventListener('click', toggleOpisometerMode);
|
||||
document.getElementById('addRouteOpisometer').addEventListener('click', toggleRouteOpisometerMode);
|
||||
document.getElementById('addPlanimeter').addEventListener('click', togglePlanimeterMode);
|
||||
document.getElementById('removeRulers').addEventListener('click', removeAllRulers);
|
||||
document.getElementById('unitsRestore').addEventListener('click', restoreDefaultUnits);
|
||||
=======
|
||||
document.getElementById("distanceUnitInput").addEventListener("change", changeDistanceUnit);
|
||||
document.getElementById("distanceScaleOutput").addEventListener("input", changeDistanceScale);
|
||||
document.getElementById("distanceScaleInput").addEventListener("change", changeDistanceScale);
|
||||
document.getElementById("heightUnit").addEventListener("change", changeHeightUnit);
|
||||
document.getElementById("heightExponentInput").addEventListener("input", changeHeightExponent);
|
||||
document.getElementById("heightExponentOutput").addEventListener("input", changeHeightExponent);
|
||||
document.getElementById("temperatureScale").addEventListener("change", changeTemperatureScale);
|
||||
document.getElementById("barSizeOutput").addEventListener("input", drawScaleBar);
|
||||
document.getElementById("barSizeInput").addEventListener("input", drawScaleBar);
|
||||
document.getElementById("barLabel").addEventListener("input", drawScaleBar);
|
||||
document.getElementById("barPosX").addEventListener("input", fitScaleBar);
|
||||
document.getElementById("barPosY").addEventListener("input", fitScaleBar);
|
||||
document.getElementById("barBackOpacity").addEventListener("input", changeScaleBarOpacity);
|
||||
document.getElementById("barBackColor").addEventListener("input", changeScaleBarColor);
|
||||
|
||||
document.getElementById("populationRateOutput").addEventListener("input", changePopulationRate);
|
||||
document.getElementById("populationRateInput").addEventListener("change", changePopulationRate);
|
||||
document.getElementById("urbanizationOutput").addEventListener("input", changeUrbanizationRate);
|
||||
document.getElementById("urbanizationInput").addEventListener("change", changeUrbanizationRate);
|
||||
document.getElementById("urbanDensityOutput").addEventListener("input", changeUrbanDensity);
|
||||
document.getElementById("urbanDensityInput").addEventListener("change", changeUrbanDensity);
|
||||
|
||||
document.getElementById("addLinearRuler").addEventListener("click", addRuler);
|
||||
document.getElementById("addOpisometer").addEventListener("click", toggleOpisometerMode);
|
||||
document.getElementById("addRouteOpisometer").addEventListener("click", toggleRouteOpisometerMode);
|
||||
document.getElementById("addPlanimeter").addEventListener("click", togglePlanimeterMode);
|
||||
document.getElementById("removeRulers").addEventListener("click", removeAllRulers);
|
||||
document.getElementById("unitsRestore").addEventListener("click", restoreDefaultUnits);
|
||||
>>>>>>> master
|
||||
|
||||
function changeDistanceUnit() {
|
||||
if (this.value === 'custom_name') {
|
||||
prompt('Provide a custom name for a distance unit', {default: ''}, (custom) => {
|
||||
this.options.add(new Option(custom, custom, false, true));
|
||||
lock('distanceUnit');
|
||||
drawScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
drawScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
function changeDistanceScale() {
|
||||
drawScaleBar();
|
||||
calculateFriendlyGridSize();
|
||||
}
|
||||
|
||||
function changeHeightUnit() {
|
||||
if (this.value !== 'custom_name') return;
|
||||
|
||||
prompt('Provide a custom name for a height unit', {default: ''}, (custom) => {
|
||||
this.options.add(new Option(custom, custom, false, true));
|
||||
lock('heightUnit');
|
||||
});
|
||||
}
|
||||
|
||||
function changeHeightExponent() {
|
||||
calculateTemperatures();
|
||||
if (layerIsOn('toggleTemp')) drawTemp();
|
||||
}
|
||||
|
||||
function changeTemperatureScale() {
|
||||
if (layerIsOn('toggleTemp')) drawTemp();
|
||||
}
|
||||
|
||||
function changeScaleBarOpacity() {
|
||||
scaleBar.select('rect').attr('opacity', this.value);
|
||||
}
|
||||
|
||||
function changeScaleBarColor() {
|
||||
scaleBar.select('rect').attr('fill', this.value);
|
||||
}
|
||||
|
||||
function changePopulationRate() {
|
||||
populationRate = +this.value;
|
||||
}
|
||||
|
||||
function changeUrbanizationRate() {
|
||||
urbanization = +this.value;
|
||||
}
|
||||
|
||||
function changeUrbanDensity() {
|
||||
urbanDensity = +this.value;
|
||||
}
|
||||
|
||||
function restoreDefaultUnits() {
|
||||
// distanceScale
|
||||
document.getElementById('distanceScaleOutput').value = 3;
|
||||
document.getElementById('distanceScaleInput').value = 3;
|
||||
unlock('distanceScale');
|
||||
|
||||
// units
|
||||
const US = navigator.language === 'en-US';
|
||||
const UK = navigator.language === 'en-GB';
|
||||
distanceUnitInput.value = US || UK ? 'mi' : 'km';
|
||||
heightUnit.value = US || UK ? 'ft' : 'm';
|
||||
temperatureScale.value = US ? '°F' : '°C';
|
||||
areaUnit.value = 'square';
|
||||
localStorage.removeItem('distanceUnit');
|
||||
localStorage.removeItem('heightUnit');
|
||||
localStorage.removeItem('temperatureScale');
|
||||
localStorage.removeItem('areaUnit');
|
||||
calculateFriendlyGridSize();
|
||||
|
||||
// height exponent
|
||||
heightExponentInput.value = heightExponentOutput.value = 1.8;
|
||||
localStorage.removeItem('heightExponent');
|
||||
calculateTemperatures();
|
||||
|
||||
// scale bar
|
||||
barSizeOutput.value = barSizeInput.value = 2;
|
||||
barLabel.value = '';
|
||||
barBackOpacity.value = 0.2;
|
||||
barBackColor.value = '#ffffff';
|
||||
barPosX.value = barPosY.value = 99;
|
||||
|
||||
localStorage.removeItem('barSize');
|
||||
localStorage.removeItem('barLabel');
|
||||
localStorage.removeItem('barBackOpacity');
|
||||
localStorage.removeItem('barBackColor');
|
||||
localStorage.removeItem('barPosX');
|
||||
localStorage.removeItem('barPosY');
|
||||
drawScaleBar();
|
||||
|
||||
// population
|
||||
populationRate = populationRateOutput.value = populationRateInput.value = 1000;
|
||||
urbanization = urbanizationOutput.value = urbanizationInput.value = 1;
|
||||
<<<<<<< HEAD
|
||||
localStorage.removeItem('populationRate');
|
||||
localStorage.removeItem('urbanization');
|
||||
=======
|
||||
urbanDensity = urbanDensityOutput.value = urbanDensityInput.value = 10;
|
||||
localStorage.removeItem("populationRate");
|
||||
localStorage.removeItem("urbanization");
|
||||
localStorage.removeItem("urbanDensity");
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
function addRuler() {
|
||||
if (!layerIsOn('toggleRulers')) toggleRulers();
|
||||
const pt = document.getElementById('map').createSVGPoint();
|
||||
(pt.x = graphWidth / 2), (pt.y = graphHeight / 4);
|
||||
const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse());
|
||||
const dx = graphWidth / 4 / scale;
|
||||
const dy = (rulers.data.length * 40) % (graphHeight / 2);
|
||||
const from = [(p.x - dx) | 0, (p.y + dy) | 0];
|
||||
const to = [(p.x + dx) | 0, (p.y + dy) | 0];
|
||||
rulers.create(Ruler, [from, to]).draw();
|
||||
}
|
||||
|
||||
function toggleOpisometerMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
this.classList.remove('pressed');
|
||||
} else {
|
||||
if (!layerIsOn('toggleRulers')) toggleRulers();
|
||||
tip('Draw a curve to measure length. Hold Shift to disallow path optimization', true);
|
||||
unitsBottom.querySelectorAll('.pressed').forEach((button) => button.classList.remove('pressed'));
|
||||
this.classList.add('pressed');
|
||||
viewbox.style('cursor', 'crosshair').call(
|
||||
d3.drag().on('start', function () {
|
||||
const point = d3.mouse(this);
|
||||
const opisometer = rulers.create(Opisometer, [point]).draw();
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const point = d3.mouse(this);
|
||||
opisometer.addPoint(point);
|
||||
});
|
||||
|
||||
d3.event.on('end', function () {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addOpisometer.classList.remove('pressed');
|
||||
if (opisometer.points.length < 2) rulers.remove(opisometer.id);
|
||||
if (!d3.event.sourceEvent.shiftKey) opisometer.optimize();
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRouteOpisometerMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
this.classList.remove('pressed');
|
||||
} else {
|
||||
if (!layerIsOn('toggleRulers')) toggleRulers();
|
||||
tip('Draw a curve along routes to measure length. Hold Shift to measure away from roads.', true);
|
||||
unitsBottom.querySelectorAll('.pressed').forEach((button) => button.classList.remove('pressed'));
|
||||
this.classList.add('pressed');
|
||||
viewbox.style('cursor', 'crosshair').call(
|
||||
d3.drag().on('start', function () {
|
||||
const cells = pack.cells;
|
||||
const burgs = pack.burgs;
|
||||
const point = d3.mouse(this);
|
||||
const c = findCell(point[0], point[1]);
|
||||
if (cells.road[c] || d3.event.sourceEvent.shiftKey) {
|
||||
const b = cells.burg[c];
|
||||
const x = b ? burgs[b].x : cells.p[c][0];
|
||||
const y = b ? burgs[b].y : cells.p[c][1];
|
||||
const routeOpisometer = rulers.create(RouteOpisometer, [[x, y]]).draw();
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const point = d3.mouse(this);
|
||||
const c = findCell(point[0], point[1]);
|
||||
if (cells.road[c] || d3.event.sourceEvent.shiftKey) {
|
||||
routeOpisometer.trackCell(c, true);
|
||||
}
|
||||
});
|
||||
|
||||
d3.event.on('end', function () {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addRouteOpisometer.classList.remove('pressed');
|
||||
if (routeOpisometer.points.length < 2) {
|
||||
rulers.remove(routeOpisometer.id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addRouteOpisometer.classList.remove('pressed');
|
||||
tip('Must start in a cell with a route in it', false, 'error');
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function togglePlanimeterMode() {
|
||||
if (this.classList.contains('pressed')) {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
this.classList.remove('pressed');
|
||||
} else {
|
||||
if (!layerIsOn('toggleRulers')) toggleRulers();
|
||||
tip('Draw a curve to measure its area. Hold Shift to disallow path optimization', true);
|
||||
unitsBottom.querySelectorAll('.pressed').forEach((button) => button.classList.remove('pressed'));
|
||||
this.classList.add('pressed');
|
||||
viewbox.style('cursor', 'crosshair').call(
|
||||
d3.drag().on('start', function () {
|
||||
const point = d3.mouse(this);
|
||||
const planimeter = rulers.create(Planimeter, [point]).draw();
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const point = d3.mouse(this);
|
||||
planimeter.addPoint(point);
|
||||
});
|
||||
|
||||
d3.event.on('end', function () {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addPlanimeter.classList.remove('pressed');
|
||||
if (planimeter.points.length < 3) rulers.remove(planimeter.id);
|
||||
else if (!d3.event.sourceEvent.shiftKey) planimeter.optimize();
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function removeAllRulers() {
|
||||
if (!rulers.data.length) return;
|
||||
alertMessage.innerHTML = `
|
||||
Are you sure you want to remove all placed rulers?
|
||||
<br>If you just want to hide rulers, toggle the Rulers layer off in Menu`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove all rulers',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
rulers.undraw();
|
||||
rulers = new Rulers();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,33 @@
|
|||
function editWorld() {
|
||||
if (customization) return;
|
||||
$("#worldConfigurator").dialog({title: "Configure World", resizable: false, width: "42em",
|
||||
$('#worldConfigurator').dialog({
|
||||
title: 'Configure World',
|
||||
resizable: false,
|
||||
width: '42em',
|
||||
buttons: {
|
||||
"Whole World": () => applyWorldPreset(100, 50),
|
||||
"Northern": () => applyWorldPreset(33, 25),
|
||||
"Tropical": () => applyWorldPreset(33, 50),
|
||||
"Southern": () => applyWorldPreset(33, 75),
|
||||
"Restore Winds": restoreDefaultWinds
|
||||
}, open: function() {
|
||||
const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
|
||||
buttons[0].addEventListener("mousemove", () => tip("Click to set map size to cover the whole World"));
|
||||
buttons[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes"));
|
||||
buttons[2].addEventListener("mousemove", () => tip("Click to set map size to cover the Tropical latitudes"));
|
||||
buttons[3].addEventListener("mousemove", () => tip("Click to set map size to cover the Southern latitudes"));
|
||||
buttons[4].addEventListener("mousemove", () => tip("Click to restore default wind directions"));
|
||||
'Whole World': () => applyWorldPreset(100, 50),
|
||||
Northern: () => applyWorldPreset(33, 25),
|
||||
Tropical: () => applyWorldPreset(33, 50),
|
||||
Southern: () => applyWorldPreset(33, 75),
|
||||
'Restore Winds': restoreDefaultWinds
|
||||
},
|
||||
open: function () {
|
||||
const buttons = $(this).dialog('widget').find('.ui-dialog-buttonset > button');
|
||||
buttons[0].addEventListener('mousemove', () => tip('Click to set map size to cover the whole World'));
|
||||
buttons[1].addEventListener('mousemove', () => tip('Click to set map size to cover the Northern latitudes'));
|
||||
buttons[2].addEventListener('mousemove', () => tip('Click to set map size to cover the Tropical latitudes'));
|
||||
buttons[3].addEventListener('mousemove', () => tip('Click to set map size to cover the Southern latitudes'));
|
||||
buttons[4].addEventListener('mousemove', () => tip('Click to restore default wind directions'));
|
||||
},
|
||||
close: function () {
|
||||
$(this).dialog('destroy');
|
||||
}
|
||||
});
|
||||
|
||||
const globe = d3.select("#globe");
|
||||
const globe = d3.select('#globe');
|
||||
const clr = d3.scaleSequential(d3.interpolateSpectral);
|
||||
const tMax = 30, tMin = -25; // temperature extremes
|
||||
const tMax = 30,
|
||||
tMin = -25; // temperature extremes
|
||||
const projection = d3.geoOrthographic().translate([100, 100]).scale(100);
|
||||
const path = d3.geoPath(projection);
|
||||
|
||||
|
|
@ -29,15 +37,15 @@ function editWorld() {
|
|||
if (modules.editWorld) return;
|
||||
modules.editWorld = true;
|
||||
|
||||
document.getElementById("worldControls").addEventListener("input", (e) => updateWorld(e.target));
|
||||
globe.select("#globeWindArrows").on("click", changeWind);
|
||||
globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule
|
||||
document.getElementById('worldControls').addEventListener('input', (e) => updateWorld(e.target));
|
||||
globe.select('#globeWindArrows').on('click', changeWind);
|
||||
globe.select('#globeGraticule').attr('d', round(path(d3.geoGraticule()()))); // globe graticule
|
||||
updateWindDirections();
|
||||
|
||||
function updateWorld(el) {
|
||||
if (el) {
|
||||
document.getElementById(el.dataset.stored+"Input").value = el.value;
|
||||
document.getElementById(el.dataset.stored+"Output").value = el.value;
|
||||
document.getElementById(el.dataset.stored + 'Input').value = el.value;
|
||||
document.getElementById(el.dataset.stored + 'Output').value = el.value;
|
||||
if (el.dataset.stored) lock(el.dataset.stored);
|
||||
}
|
||||
|
||||
|
|
@ -52,84 +60,94 @@ function editWorld() {
|
|||
pack.cells.h = new Float32Array(heights);
|
||||
defineBiomes();
|
||||
|
||||
if (layerIsOn("toggleTemp")) drawTemp();
|
||||
if (layerIsOn("togglePrec")) drawPrec();
|
||||
if (layerIsOn("toggleBiomes")) drawBiomes();
|
||||
if (layerIsOn("toggleCoordinates")) drawCoordinates();
|
||||
if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500);
|
||||
if (layerIsOn('toggleTemp')) drawTemp();
|
||||
if (layerIsOn('togglePrec')) drawPrec();
|
||||
if (layerIsOn('toggleBiomes')) drawBiomes();
|
||||
if (layerIsOn('toggleCoordinates')) drawCoordinates();
|
||||
if (layerIsOn('toggleRivers')) drawRivers();
|
||||
if (document.getElementById('canvas3d')) setTimeout(ThreeD.update(), 500);
|
||||
}
|
||||
|
||||
function updateGlobePosition() {
|
||||
const size = +document.getElementById("mapSizeOutput").value;
|
||||
const eqD = graphHeight / 2 * 100 / size;
|
||||
const size = +document.getElementById('mapSizeOutput').value;
|
||||
const eqD = ((graphHeight / 2) * 100) / size;
|
||||
|
||||
calculateMapCoordinates();
|
||||
const mc = mapCoordinates; // shortcut
|
||||
const scale = +distanceScaleInput.value, unit = distanceUnitInput.value;
|
||||
const scale = +distanceScaleInput.value,
|
||||
unit = distanceUnitInput.value;
|
||||
const meridian = toKilometer(eqD * 2 * scale);
|
||||
document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
|
||||
document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
|
||||
document.getElementById("meridianLength").innerHTML = rn(eqD * 2);
|
||||
document.getElementById("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
|
||||
document.getElementById("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : "";
|
||||
document.getElementById("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;
|
||||
document.getElementById('mapSize').innerHTML = `${graphWidth}x${graphHeight}`;
|
||||
document.getElementById('mapSizeFriendly').innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
|
||||
document.getElementById('meridianLength').innerHTML = rn(eqD * 2);
|
||||
document.getElementById('meridianLengthFriendly').innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
|
||||
document.getElementById('meridianLengthEarth').innerHTML = meridian ? ' = ' + rn(meridian / 200) + '%🌏' : '';
|
||||
document.getElementById('mapCoordinates').innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;
|
||||
|
||||
function toKilometer(v) {
|
||||
if (unit === "km") return v;
|
||||
else if (unit === "mi") return v * 1.60934;
|
||||
else if (unit === "lg") return v * 5.556;
|
||||
else if (unit === "vr") return v * 1.0668;
|
||||
if (unit === 'km') return v;
|
||||
else if (unit === 'mi') return v * 1.60934;
|
||||
else if (unit === 'lg') return v * 5.556;
|
||||
else if (unit === 'vr') return v * 1.0668;
|
||||
return 0; // 0 if distanceUnitInput is a custom unit
|
||||
}
|
||||
|
||||
function lat(lat) {return lat > 0 ? Math.abs(rn(lat)) + "°N" : Math.abs(rn(lat)) + "°S";} // parse latitude value
|
||||
const area = d3.geoGraticule().extent([[mc.lonW, mc.latN], [mc.lonE, mc.latS]]);
|
||||
globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area
|
||||
function lat(lat) {
|
||||
return lat > 0 ? Math.abs(rn(lat)) + '°N' : Math.abs(rn(lat)) + '°S';
|
||||
} // parse latitude value
|
||||
const area = d3.geoGraticule().extent([
|
||||
[mc.lonW, mc.latN],
|
||||
[mc.lonE, mc.latS]
|
||||
]);
|
||||
globe.select('#globeArea').attr('d', round(path(area.outline()))); // map area
|
||||
}
|
||||
|
||||
function updateGlobeTemperature() {
|
||||
const tEq = +document.getElementById("temperatureEquatorOutput").value;
|
||||
document.getElementById("temperatureEquatorF").innerHTML = rn(tEq * 9/5 + 32);
|
||||
const tPole = +document.getElementById("temperaturePoleOutput").value;
|
||||
document.getElementById("temperaturePoleF").innerHTML = rn(tPole * 9/5 + 32);
|
||||
globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin)));
|
||||
globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 2/3 - tMin) / (tMax - tMin)));
|
||||
globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 1/3 - tMin) / (tMax - tMin)));
|
||||
globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin)));
|
||||
const tEq = +document.getElementById('temperatureEquatorOutput').value;
|
||||
document.getElementById('temperatureEquatorF').innerHTML = rn((tEq * 9) / 5 + 32);
|
||||
const tPole = +document.getElementById('temperaturePoleOutput').value;
|
||||
document.getElementById('temperaturePoleF').innerHTML = rn((tPole * 9) / 5 + 32);
|
||||
globe.selectAll('.tempGradient90').attr('stop-color', clr(1 - (tPole - tMin) / (tMax - tMin)));
|
||||
globe.selectAll('.tempGradient60').attr('stop-color', clr(1 - (tEq - ((tEq - tPole) * 2) / 3 - tMin) / (tMax - tMin)));
|
||||
globe.selectAll('.tempGradient30').attr('stop-color', clr(1 - (tEq - ((tEq - tPole) * 1) / 3 - tMin) / (tMax - tMin)));
|
||||
globe.select('.tempGradient0').attr('stop-color', clr(1 - (tEq - tMin) / (tMax - tMin)));
|
||||
}
|
||||
|
||||
function updateWindDirections() {
|
||||
globe.select("#globeWindArrows").selectAll("path").each(function(d, i) {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
this.setAttribute("transform", `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`);
|
||||
});
|
||||
globe
|
||||
.select('#globeWindArrows')
|
||||
.selectAll('path')
|
||||
.each(function (d, i) {
|
||||
const tr = parseTransform(this.getAttribute('transform'));
|
||||
this.setAttribute('transform', `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`);
|
||||
});
|
||||
}
|
||||
|
||||
function changeWind() {
|
||||
const arrow = d3.event.target.nextElementSibling;
|
||||
const tier = +arrow.dataset.tier;
|
||||
options.winds[tier] = (options.winds[tier] + 45) % 360;
|
||||
const tr = parseTransform(arrow.getAttribute("transform"));
|
||||
arrow.setAttribute("transform", `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`);
|
||||
localStorage.setItem("winds", options.winds);
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
|
||||
const tr = parseTransform(arrow.getAttribute('transform'));
|
||||
arrow.setAttribute('transform', `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`);
|
||||
localStorage.setItem('winds', options.winds);
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map((c) => ((90 - c) / 30) | 0);
|
||||
if (mapTiers.includes(tier)) updateWorld();
|
||||
}
|
||||
|
||||
function restoreDefaultWinds() {
|
||||
const defaultWinds = [225, 45, 225, 315, 135, 315];
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
|
||||
const update = mapTiers.some(t => options.winds[t] != defaultWinds[t]);
|
||||
const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map((c) => ((90 - c) / 30) | 0);
|
||||
const update = mapTiers.some((t) => options.winds[t] != defaultWinds[t]);
|
||||
options.winds = defaultWinds;
|
||||
updateWindDirections();
|
||||
if (update) updateWorld();
|
||||
}
|
||||
|
||||
function applyWorldPreset(size, lat) {
|
||||
document.getElementById("mapSizeInput").value = document.getElementById("mapSizeOutput").value = size;
|
||||
document.getElementById("latitudeInput").value = document.getElementById("latitudeOutput").value = lat;
|
||||
lock("mapSize");
|
||||
lock("latitude");
|
||||
document.getElementById('mapSizeInput').value = document.getElementById('mapSizeOutput').value = size;
|
||||
document.getElementById('latitudeInput').value = document.getElementById('latitudeOutput').value = lat;
|
||||
lock('mapSize');
|
||||
lock('latitude');
|
||||
updateWorld();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +1,83 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
function editZones() {
|
||||
closeDialogs();
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
const body = document.getElementById("zonesBodySection");
|
||||
if (!layerIsOn('toggleZones')) toggleZones();
|
||||
const body = document.getElementById('zonesBodySection');
|
||||
zonesEditorAddLines();
|
||||
|
||||
if (modules.editZones) return;
|
||||
modules.editZones = true;
|
||||
|
||||
$("#zonesEditor").dialog({
|
||||
title: "Zones Editor",
|
||||
$('#zonesEditor').dialog({
|
||||
title: 'Zones Editor',
|
||||
resizable: false,
|
||||
width: fitContent(),
|
||||
close: () => exitZonesManualAssignment("close"),
|
||||
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
|
||||
close: () => exitZonesManualAssignment('close'),
|
||||
position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}
|
||||
});
|
||||
|
||||
// add listeners
|
||||
document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines);
|
||||
document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones"));
|
||||
document.getElementById("zonesLegend").addEventListener("click", toggleLegend);
|
||||
document.getElementById("zonesPercentage").addEventListener("click", togglePercentageMode);
|
||||
document.getElementById("zonesManually").addEventListener("click", enterZonesManualAssignent);
|
||||
document.getElementById("zonesManuallyApply").addEventListener("click", applyZonesManualAssignent);
|
||||
document.getElementById("zonesManuallyCancel").addEventListener("click", cancelZonesManualAssignent);
|
||||
document.getElementById("zonesAdd").addEventListener("click", addZonesLayer);
|
||||
document.getElementById("zonesExport").addEventListener("click", downloadZonesData);
|
||||
document.getElementById("zonesRemove").addEventListener("click", toggleEraseMode);
|
||||
document.getElementById('zonesEditorRefresh').addEventListener('click', zonesEditorAddLines);
|
||||
document.getElementById('zonesEditStyle').addEventListener('click', () => editStyle('zones'));
|
||||
document.getElementById('zonesLegend').addEventListener('click', toggleLegend);
|
||||
document.getElementById('zonesPercentage').addEventListener('click', togglePercentageMode);
|
||||
document.getElementById('zonesManually').addEventListener('click', enterZonesManualAssignent);
|
||||
document.getElementById('zonesManuallyApply').addEventListener('click', applyZonesManualAssignent);
|
||||
document.getElementById('zonesManuallyCancel').addEventListener('click', cancelZonesManualAssignent);
|
||||
document.getElementById('zonesAdd').addEventListener('click', addZonesLayer);
|
||||
document.getElementById('zonesExport').addEventListener('click', downloadZonesData);
|
||||
document.getElementById('zonesRemove').addEventListener('click', toggleEraseMode);
|
||||
|
||||
body.addEventListener("click", function (ev) {
|
||||
body.addEventListener('click', function (ev) {
|
||||
const el = ev.target,
|
||||
cl = el.classList,
|
||||
zone = el.parentNode.dataset.id;
|
||||
if (cl.contains("culturePopulation")) {
|
||||
if (cl.contains('culturePopulation')) {
|
||||
changePopulation(zone);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("icon-trash-empty")) {
|
||||
if (cl.contains('icon-trash-empty')) {
|
||||
zoneRemove(zone);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("icon-eye")) {
|
||||
if (cl.contains('icon-eye')) {
|
||||
toggleVisibility(el);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("icon-pin")) {
|
||||
if (cl.contains('icon-pin')) {
|
||||
toggleFog(zone, cl);
|
||||
return;
|
||||
}
|
||||
if (cl.contains("fillRect")) {
|
||||
if (cl.contains('fillRect')) {
|
||||
changeFill(el);
|
||||
return;
|
||||
}
|
||||
if (customization) selectZone(el);
|
||||
});
|
||||
|
||||
body.addEventListener("input", function (ev) {
|
||||
body.addEventListener('input', function (ev) {
|
||||
const el = ev.target,
|
||||
zone = el.parentNode.dataset.id;
|
||||
if (el.classList.contains("religionName")) zones.select("#" + zone).attr("data-description", el.value);
|
||||
if (el.classList.contains('religionName')) zones.select('#' + zone).attr('data-description', el.value);
|
||||
});
|
||||
|
||||
// add line for each zone
|
||||
function zonesEditorAddLines() {
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
let lines = "";
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
let lines = '';
|
||||
|
||||
zones.selectAll("g").each(function () {
|
||||
const c = this.dataset.cells ? this.dataset.cells.split(",").map(c => +c) : [];
|
||||
zones.selectAll('g').each(function () {
|
||||
const c = this.dataset.cells ? this.dataset.cells.split(',').map((c) => +c) : [];
|
||||
const description = this.dataset.description;
|
||||
const fill = this.getAttribute("fill");
|
||||
const area = d3.sum(c.map(i => pack.cells.area[i])) * distanceScaleInput.value ** 2;
|
||||
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate;
|
||||
const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization;
|
||||
const fill = this.getAttribute('fill');
|
||||
const area = d3.sum(c.map((i) => pack.cells.area[i])) * distanceScaleInput.value ** 2;
|
||||
const rural = d3.sum(c.map((i) => pack.cells.pop[i])) * populationRate;
|
||||
const urban = d3.sum(c.map((i) => pack.cells.burg[i]).map((b) => pack.burgs[b].population)) * populationRate * urbanization;
|
||||
const population = rural + urban;
|
||||
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`;
|
||||
const inactive = this.style.display === "none";
|
||||
const focused = defs.select("#fog #focus" + this.id).size();
|
||||
const inactive = this.style.display === 'none';
|
||||
const focused = defs.select('#fog #focus' + this.id).size();
|
||||
|
||||
lines += `<div class="states" data-id="${this.id}" data-fill="${fill}" data-description="${description}" data-cells=${c.length} data-area=${area} data-population=${population}>
|
||||
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg>
|
||||
|
|
@ -89,8 +89,8 @@ function editZones() {
|
|||
<span data-tip="${populationTip}" class="icon-male hide"></span>
|
||||
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
|
||||
<span data-tip="Drag to raise or lower the zone" class="icon-resize-vertical hide"></span>
|
||||
<span data-tip="Toggle zone focus" class="icon-pin ${focused ? "" : " inactive"} hide ${c.length ? "" : " placeholder"}"></span>
|
||||
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive ? " inactive" : ""} hide ${c.length ? "" : " placeholder"}"></span>
|
||||
<span data-tip="Toggle zone focus" class="icon-pin ${focused ? '' : ' inactive'} hide ${c.length ? '' : ' placeholder'}"></span>
|
||||
<span data-tip="Toggle zone visibility" class="icon-eye ${inactive ? ' inactive' : ''} hide ${c.length ? '' : ' placeholder'}"></span>
|
||||
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
});
|
||||
|
|
@ -99,73 +99,73 @@ function editZones() {
|
|||
|
||||
// update footer
|
||||
const totalArea = (zonesFooterArea.dataset.area = graphWidth * graphHeight * distanceScaleInput.value ** 2);
|
||||
const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) * populationRate;
|
||||
const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter((b) => !b.removed).map((b) => b.population)) * urbanization) * populationRate;
|
||||
zonesFooterPopulation.dataset.population = totalPop;
|
||||
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
|
||||
zonesFooterNumber.innerHTML = zones.selectAll('g').size();
|
||||
zonesFooterCells.innerHTML = pack.cells.i.length;
|
||||
zonesFooterArea.innerHTML = si(totalArea) + unit;
|
||||
zonesFooterPopulation.innerHTML = si(totalPop);
|
||||
|
||||
// add listeners
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => zoneHighlightOn(ev)));
|
||||
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => zoneHighlightOff(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseenter', (ev) => zoneHighlightOn(ev)));
|
||||
body.querySelectorAll('div.states').forEach((el) => el.addEventListener('mouseleave', (ev) => zoneHighlightOff(ev)));
|
||||
|
||||
if (body.dataset.type === "percentage") {
|
||||
body.dataset.type = "absolute";
|
||||
if (body.dataset.type === 'percentage') {
|
||||
body.dataset.type = 'absolute';
|
||||
togglePercentageMode();
|
||||
}
|
||||
$("#zonesEditor").dialog({width: fitContent()});
|
||||
$('#zonesEditor').dialog({width: fitContent()});
|
||||
}
|
||||
|
||||
function zoneHighlightOn(event) {
|
||||
const zone = event.target.dataset.id;
|
||||
zones.select("#" + zone).style("outline", "1px solid red");
|
||||
zones.select('#' + zone).style('outline', '1px solid red');
|
||||
}
|
||||
|
||||
function zoneHighlightOff(event) {
|
||||
const zone = event.target.dataset.id;
|
||||
zones.select("#" + zone).style("outline", null);
|
||||
zones.select('#' + zone).style('outline', null);
|
||||
}
|
||||
|
||||
$(body).sortable({items: "div.states", handle: ".icon-resize-vertical", containment: "parent", axis: "y", update: movezone});
|
||||
$(body).sortable({items: 'div.states', handle: '.icon-resize-vertical', containment: 'parent', axis: 'y', update: movezone});
|
||||
function movezone(ev, ui) {
|
||||
const zone = $("#" + ui.item.attr("data-id"));
|
||||
const prev = $("#" + ui.item.prev().attr("data-id"));
|
||||
const zone = $('#' + ui.item.attr('data-id'));
|
||||
const prev = $('#' + ui.item.prev().attr('data-id'));
|
||||
if (prev) {
|
||||
zone.insertAfter(prev);
|
||||
return;
|
||||
}
|
||||
const next = $("#" + ui.item.next().attr("data-id"));
|
||||
const next = $('#' + ui.item.next().attr('data-id'));
|
||||
if (next) zone.insertBefore(next);
|
||||
}
|
||||
|
||||
function enterZonesManualAssignent() {
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
if (!layerIsOn('toggleZones')) toggleZones();
|
||||
customization = 10;
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "none"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "inline-block";
|
||||
document.querySelectorAll('#zonesBottom > button').forEach((el) => (el.style.display = 'none'));
|
||||
document.getElementById('zonesManuallyButtons').style.display = 'inline-block';
|
||||
|
||||
zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
|
||||
zonesFooter.style.display = "none";
|
||||
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "none"));
|
||||
$("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
|
||||
zonesEditor.querySelectorAll('.hide').forEach((el) => el.classList.add('hidden'));
|
||||
zonesFooter.style.display = 'none';
|
||||
body.querySelectorAll('div > input, select, svg').forEach((e) => (e.style.pointerEvents = 'none'));
|
||||
$('#zonesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}});
|
||||
|
||||
tip("Click to select a zone, drag to paint a zone", true);
|
||||
viewbox.style("cursor", "crosshair").on("click", selectZoneOnMapClick).call(d3.drag().on("start", dragZoneBrush)).on("touchmove mousemove", moveZoneBrush);
|
||||
tip('Click to select a zone, drag to paint a zone', true);
|
||||
viewbox.style('cursor', 'crosshair').on('click', selectZoneOnMapClick).call(d3.drag().on('start', dragZoneBrush)).on('touchmove mousemove', moveZoneBrush);
|
||||
|
||||
body.querySelector("div").classList.add("selected");
|
||||
zones.selectAll("g").each(function () {
|
||||
this.setAttribute("data-init", this.getAttribute("data-cells"));
|
||||
body.querySelector('div').classList.add('selected');
|
||||
zones.selectAll('g').each(function () {
|
||||
this.setAttribute('data-init', this.getAttribute('data-cells'));
|
||||
});
|
||||
}
|
||||
|
||||
function selectZone(el) {
|
||||
body.querySelector("div.selected").classList.remove("selected");
|
||||
el.classList.add("selected");
|
||||
body.querySelector('div.selected').classList.remove('selected');
|
||||
el.classList.add('selected');
|
||||
}
|
||||
|
||||
function selectZoneOnMapClick() {
|
||||
if (d3.event.target.parentElement.parentElement.id !== "zones") return;
|
||||
if (d3.event.target.parentElement.parentElement.id !== 'zones') return;
|
||||
const zone = d3.event.target.parentElement.id;
|
||||
const el = body.querySelector("div[data-id='" + zone + "']");
|
||||
selectZone(el);
|
||||
|
|
@ -174,7 +174,7 @@ function editZones() {
|
|||
function dragZoneBrush() {
|
||||
const r = +zonesBrush.value;
|
||||
|
||||
d3.event.on("drag", () => {
|
||||
d3.event.on('drag', () => {
|
||||
if (!d3.event.dx && !d3.event.dy) return;
|
||||
const p = d3.mouse(this);
|
||||
moveCircle(p[0], p[1], r);
|
||||
|
|
@ -182,34 +182,34 @@ function editZones() {
|
|||
const selection = r > 5 ? findAll(p[0], p[1], r) : [findCell(p[0], p[1], r)];
|
||||
if (!selection) return;
|
||||
|
||||
const selected = body.querySelector("div.selected");
|
||||
const zone = zones.select("#" + selected.dataset.id);
|
||||
const base = zone.attr("id") + "_"; // id generic part
|
||||
const dataCells = zone.attr("data-cells");
|
||||
let cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
||||
const selected = body.querySelector('div.selected');
|
||||
const zone = zones.select('#' + selected.dataset.id);
|
||||
const base = zone.attr('id') + '_'; // id generic part
|
||||
const dataCells = zone.attr('data-cells');
|
||||
let cells = dataCells ? dataCells.split(',').map((i) => +i) : [];
|
||||
|
||||
const erase = document.getElementById("zonesRemove").classList.contains("pressed");
|
||||
const erase = document.getElementById('zonesRemove').classList.contains('pressed');
|
||||
if (erase) {
|
||||
// remove
|
||||
selection.forEach(i => {
|
||||
selection.forEach((i) => {
|
||||
const index = cells.indexOf(i);
|
||||
if (index === -1) return;
|
||||
zone.select("polygon#" + base + i).remove();
|
||||
zone.select('polygon#' + base + i).remove();
|
||||
cells.splice(index, 1);
|
||||
});
|
||||
} else {
|
||||
// add
|
||||
selection.forEach(i => {
|
||||
selection.forEach((i) => {
|
||||
if (cells.includes(i)) return;
|
||||
cells.push(i);
|
||||
zone
|
||||
.append("polygon")
|
||||
.attr("points", getPackPolygon(i))
|
||||
.attr("id", base + i);
|
||||
.append('polygon')
|
||||
.attr('points', getPackPolygon(i))
|
||||
.attr('id', base + i);
|
||||
});
|
||||
}
|
||||
|
||||
zone.attr("data-cells", cells);
|
||||
zone.attr('data-cells', cells);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -221,11 +221,11 @@ function editZones() {
|
|||
}
|
||||
|
||||
function applyZonesManualAssignent() {
|
||||
zones.selectAll("g").each(function () {
|
||||
zones.selectAll('g').each(function () {
|
||||
if (this.dataset.cells) return;
|
||||
// all zone cells are removed
|
||||
unfog("focusZone" + this.id);
|
||||
this.style.display = "block";
|
||||
unfog('focusZone' + this.id);
|
||||
this.style.display = 'block';
|
||||
});
|
||||
|
||||
zonesEditorAddLines();
|
||||
|
|
@ -234,20 +234,20 @@ function editZones() {
|
|||
|
||||
// restore initial zone cells
|
||||
function cancelZonesManualAssignent() {
|
||||
zones.selectAll("g").each(function () {
|
||||
zones.selectAll('g').each(function () {
|
||||
const zone = d3.select(this);
|
||||
const dataCells = zone.attr("data-init");
|
||||
const cells = dataCells ? dataCells.split(",").map(i => +i) : [];
|
||||
zone.attr("data-cells", cells);
|
||||
zone.selectAll("*").remove();
|
||||
const base = zone.attr("id") + "_"; // id generic part
|
||||
const dataCells = zone.attr('data-init');
|
||||
const cells = dataCells ? dataCells.split(',').map((i) => +i) : [];
|
||||
zone.attr('data-cells', cells);
|
||||
zone.selectAll('*').remove();
|
||||
const base = zone.attr('id') + '_'; // id generic part
|
||||
zone
|
||||
.selectAll("*")
|
||||
.selectAll('*')
|
||||
.data(cells)
|
||||
.enter()
|
||||
.append("polygon")
|
||||
.attr("points", d => getPackPolygon(d))
|
||||
.attr("id", d => base + d);
|
||||
.append('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('id', (d) => base + d);
|
||||
});
|
||||
|
||||
exitZonesManualAssignment();
|
||||
|
|
@ -256,97 +256,97 @@ function editZones() {
|
|||
function exitZonesManualAssignment(close) {
|
||||
customization = 0;
|
||||
removeCircle();
|
||||
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "inline-block"));
|
||||
document.getElementById("zonesManuallyButtons").style.display = "none";
|
||||
document.querySelectorAll('#zonesBottom > button').forEach((el) => (el.style.display = 'inline-block'));
|
||||
document.getElementById('zonesManuallyButtons').style.display = 'none';
|
||||
|
||||
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
|
||||
zonesFooter.style.display = "block";
|
||||
body.querySelectorAll("div > input, select, svg").forEach(e => (e.style.pointerEvents = "all"));
|
||||
if (!close) $("#zonesEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}});
|
||||
zonesEditor.querySelectorAll('.hide:not(.show)').forEach((el) => el.classList.remove('hidden'));
|
||||
zonesFooter.style.display = 'block';
|
||||
body.querySelectorAll('div > input, select, svg').forEach((e) => (e.style.pointerEvents = 'all'));
|
||||
if (!close) $('#zonesEditor').dialog({position: {my: 'right top', at: 'right-10 top+10', of: 'svg', collision: 'fit'}});
|
||||
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
zones.selectAll("g").each(function () {
|
||||
this.removeAttribute("data-init");
|
||||
zones.selectAll('g').each(function () {
|
||||
this.removeAttribute('data-init');
|
||||
});
|
||||
const selected = body.querySelector("div.selected");
|
||||
if (selected) selected.classList.remove("selected");
|
||||
const selected = body.querySelector('div.selected');
|
||||
if (selected) selected.classList.remove('selected');
|
||||
}
|
||||
|
||||
function changeFill(el) {
|
||||
const fill = el.getAttribute("fill");
|
||||
const fill = el.getAttribute('fill');
|
||||
const callback = function (fill) {
|
||||
el.setAttribute("fill", fill);
|
||||
document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute("fill", fill);
|
||||
el.setAttribute('fill', fill);
|
||||
document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute('fill', fill);
|
||||
};
|
||||
|
||||
openPicker(fill, callback);
|
||||
}
|
||||
|
||||
function toggleVisibility(el) {
|
||||
const zone = zones.select("#" + el.parentNode.dataset.id);
|
||||
const inactive = zone.style("display") === "none";
|
||||
inactive ? zone.style("display", "block") : zone.style("display", "none");
|
||||
el.classList.toggle("inactive");
|
||||
const zone = zones.select('#' + el.parentNode.dataset.id);
|
||||
const inactive = zone.style('display') === 'none';
|
||||
inactive ? zone.style('display', 'block') : zone.style('display', 'none');
|
||||
el.classList.toggle('inactive');
|
||||
}
|
||||
|
||||
function toggleFog(z, cl) {
|
||||
const dataCells = zones.select("#" + z).attr("data-cells");
|
||||
const dataCells = zones.select('#' + z).attr('data-cells');
|
||||
if (!dataCells) return;
|
||||
|
||||
const path =
|
||||
"M" +
|
||||
'M' +
|
||||
dataCells
|
||||
.split(",")
|
||||
.map(c => getPackPolygon(+c))
|
||||
.join("M") +
|
||||
"Z",
|
||||
id = "focusZone" + z;
|
||||
cl.contains("inactive") ? fog(id, path) : unfog(id);
|
||||
cl.toggle("inactive");
|
||||
.split(',')
|
||||
.map((c) => getPackPolygon(+c))
|
||||
.join('M') +
|
||||
'Z',
|
||||
id = 'focusZone' + z;
|
||||
cl.contains('inactive') ? fog(id, path) : unfog(id);
|
||||
cl.toggle('inactive');
|
||||
}
|
||||
|
||||
function toggleLegend() {
|
||||
if (legend.selectAll("*").size()) {
|
||||
if (legend.selectAll('*').size()) {
|
||||
clearLegend();
|
||||
return;
|
||||
} // hide legend
|
||||
const data = [];
|
||||
|
||||
zones.selectAll("g").each(function () {
|
||||
zones.selectAll('g').each(function () {
|
||||
const id = this.dataset.id;
|
||||
const description = this.dataset.description;
|
||||
const fill = this.getAttribute("fill");
|
||||
const fill = this.getAttribute('fill');
|
||||
data.push([id, fill, description]);
|
||||
});
|
||||
|
||||
drawLegend("Zones", data);
|
||||
drawLegend('Zones', data);
|
||||
}
|
||||
|
||||
function togglePercentageMode() {
|
||||
if (body.dataset.type === "absolute") {
|
||||
body.dataset.type = "percentage";
|
||||
if (body.dataset.type === 'absolute') {
|
||||
body.dataset.type = 'percentage';
|
||||
const totalCells = +zonesFooterCells.innerHTML;
|
||||
const totalArea = +zonesFooterArea.dataset.area;
|
||||
const totalPopulation = +zonesFooterPopulation.dataset.population;
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
el.querySelector(".stateCells").innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + "%";
|
||||
el.querySelector(".biomeArea").innerHTML = rn((+el.dataset.area / totalArea) * 100, 2) + "%";
|
||||
el.querySelector(".culturePopulation").innerHTML = rn((+el.dataset.population / totalPopulation) * 100, 2) + "%";
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
el.querySelector('.stateCells').innerHTML = rn((+el.dataset.cells / totalCells) * 100, 2) + '%';
|
||||
el.querySelector('.biomeArea').innerHTML = rn((+el.dataset.area / totalArea) * 100, 2) + '%';
|
||||
el.querySelector('.culturePopulation').innerHTML = rn((+el.dataset.population / totalPopulation) * 100, 2) + '%';
|
||||
});
|
||||
} else {
|
||||
body.dataset.type = "absolute";
|
||||
body.dataset.type = 'absolute';
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
}
|
||||
|
||||
function addZonesLayer() {
|
||||
const id = getNextId("zone");
|
||||
const description = "Unknown zone";
|
||||
const fill = "url(#hatch" + (id.slice(4) % 14) + ")";
|
||||
zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill);
|
||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||
const id = getNextId('zone');
|
||||
const description = 'Unknown zone';
|
||||
const fill = 'url(#hatch' + (id.slice(4) % 14) + ')';
|
||||
zones.append('g').attr('id', id).attr('data-description', description).attr('data-cells', '').attr('fill', fill);
|
||||
const unit = areaUnit.value === 'square' ? ' ' + distanceUnitInput.value + '²' : ' ' + areaUnit.value;
|
||||
|
||||
const line = `<div class="states" data-id="${id}" data-fill="${fill}" data-description="${description}" data-cells=0 data-area=0 data-population=0>
|
||||
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg>
|
||||
|
|
@ -363,53 +363,53 @@ function editZones() {
|
|||
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
|
||||
</div>`;
|
||||
|
||||
body.insertAdjacentHTML("beforeend", line);
|
||||
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
|
||||
body.insertAdjacentHTML('beforeend', line);
|
||||
zonesFooterNumber.innerHTML = zones.selectAll('g').size();
|
||||
}
|
||||
|
||||
function downloadZonesData() {
|
||||
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
|
||||
let data = "Id,Fill,Description,Cells,Area " + unit + ",Population\n"; // headers
|
||||
const unit = areaUnit.value === 'square' ? distanceUnitInput.value + '2' : areaUnit.value;
|
||||
let data = 'Id,Fill,Description,Cells,Area ' + unit + ',Population\n'; // headers
|
||||
|
||||
body.querySelectorAll(":scope > div").forEach(function (el) {
|
||||
data += el.dataset.id + ",";
|
||||
data += el.dataset.fill + ",";
|
||||
data += el.dataset.description + ",";
|
||||
data += el.dataset.cells + ",";
|
||||
data += el.dataset.area + ",";
|
||||
data += el.dataset.population + "\n";
|
||||
body.querySelectorAll(':scope > div').forEach(function (el) {
|
||||
data += el.dataset.id + ',';
|
||||
data += el.dataset.fill + ',';
|
||||
data += el.dataset.description + ',';
|
||||
data += el.dataset.cells + ',';
|
||||
data += el.dataset.area + ',';
|
||||
data += el.dataset.population + '\n';
|
||||
});
|
||||
|
||||
const name = getFileName("Zones") + ".csv";
|
||||
const name = getFileName('Zones') + '.csv';
|
||||
downloadFile(data, name);
|
||||
}
|
||||
|
||||
function toggleEraseMode() {
|
||||
this.classList.toggle("pressed");
|
||||
this.classList.toggle('pressed');
|
||||
}
|
||||
|
||||
function changePopulation(zone) {
|
||||
const dataCells = zones.select("#" + zone).attr("data-cells");
|
||||
const dataCells = zones.select('#' + zone).attr('data-cells');
|
||||
const cells = dataCells
|
||||
? dataCells
|
||||
.split(",")
|
||||
.map(i => +i)
|
||||
.filter(i => pack.cells.h[i] >= 20)
|
||||
.split(',')
|
||||
.map((i) => +i)
|
||||
.filter((i) => pack.cells.h[i] >= 20)
|
||||
: [];
|
||||
if (!cells.length) {
|
||||
tip("Zone does not have any land cells, cannot change population", false, "error");
|
||||
tip('Zone does not have any land cells, cannot change population', false, 'error');
|
||||
return;
|
||||
}
|
||||
const burgs = pack.burgs.filter(b => !b.removed && cells.includes(b.cell));
|
||||
const burgs = pack.burgs.filter((b) => !b.removed && cells.includes(b.cell));
|
||||
|
||||
const rural = rn(d3.sum(cells.map(i => pack.cells.pop[i])) * populationRate);
|
||||
const urban = rn(d3.sum(cells.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization);
|
||||
const rural = rn(d3.sum(cells.map((i) => pack.cells.pop[i])) * populationRate);
|
||||
const urban = rn(d3.sum(cells.map((i) => pack.cells.burg[i]).map((b) => pack.burgs[b].population)) * populationRate * urbanization);
|
||||
const total = rural + urban;
|
||||
const l = n => Number(n).toLocaleString();
|
||||
const l = (n) => Number(n).toLocaleString();
|
||||
|
||||
alertMessage.innerHTML = `
|
||||
Rural: <input type="number" min=0 step=1 id="ruralPop" value=${rural} style="width:6em">
|
||||
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"}>
|
||||
Urban: <input type="number" min=0 step=1 id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? '' : 'disabled'}>
|
||||
<p>Total population: ${l(total)} ⇒ <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
|
||||
|
||||
const update = function () {
|
||||
|
|
@ -422,41 +422,41 @@ function editZones() {
|
|||
ruralPop.oninput = () => update();
|
||||
urbanPop.oninput = () => update();
|
||||
|
||||
$("#alert").dialog({
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: "Change zone population",
|
||||
width: "24em",
|
||||
title: 'Change zone population',
|
||||
width: '24em',
|
||||
buttons: {
|
||||
Apply: function () {
|
||||
applyPopulationChange();
|
||||
$(this).dialog("close");
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
position: {my: 'center', at: 'center', of: 'svg'}
|
||||
});
|
||||
|
||||
function applyPopulationChange() {
|
||||
const ruralChange = ruralPop.value / rural;
|
||||
if (isFinite(ruralChange) && ruralChange !== 1) {
|
||||
cells.forEach(i => (pack.cells.pop[i] *= ruralChange));
|
||||
cells.forEach((i) => (pack.cells.pop[i] *= ruralChange));
|
||||
}
|
||||
if (!isFinite(ruralChange) && +ruralPop.value > 0) {
|
||||
const points = ruralPop.value / populationRate;
|
||||
const pop = rn(points / cells.length);
|
||||
cells.forEach(i => (pack.cells.pop[i] = pop));
|
||||
cells.forEach((i) => (pack.cells.pop[i] = pop));
|
||||
}
|
||||
|
||||
const urbanChange = urbanPop.value / urban;
|
||||
if (isFinite(urbanChange) && urbanChange !== 1) {
|
||||
burgs.forEach(b => (b.population = rn(b.population * urbanChange, 4)));
|
||||
burgs.forEach((b) => (b.population = rn(b.population * urbanChange, 4)));
|
||||
}
|
||||
if (!isFinite(urbanChange) && +urbanPop.value > 0) {
|
||||
const points = urbanPop.value / populationRate / urbanization;
|
||||
const population = rn(points / burgs.length, 4);
|
||||
burgs.forEach(b => (b.population = population));
|
||||
burgs.forEach((b) => (b.population = population));
|
||||
}
|
||||
|
||||
zonesEditorAddLines();
|
||||
|
|
@ -464,8 +464,8 @@ function editZones() {
|
|||
}
|
||||
|
||||
function zoneRemove(zone) {
|
||||
zones.select("#" + zone).remove();
|
||||
unfog("focusZone" + zone);
|
||||
zones.select('#' + zone).remove();
|
||||
unfog('focusZone' + zone);
|
||||
zonesEditorAddLines();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
821
modules/utils.js
821
modules/utils.js
File diff suppressed because one or more lines are too long
|
|
@ -10,27 +10,26 @@ class Voronoi {
|
|||
this.delaunay = delaunay;
|
||||
this.points = points;
|
||||
this.pointsN = pointsN;
|
||||
this.cells = { v: [], c: [], b: [] }; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell
|
||||
this.vertices = { p: [], v: [], c: [] }; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells
|
||||
this.cells = {v: [], c: [], b: []}; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell
|
||||
this.vertices = {p: [], v: [], c: []}; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells
|
||||
|
||||
// Half-edges are the indices into the delaunator outputs:
|
||||
// delaunay.triangles[e] gives the point ID where the half-edge starts
|
||||
// delaunay.triangles[e] returns either the opposite half-edge in the adjacent triangle, or -1 if there's not an adjacent triangle.
|
||||
// delaunay.halfedges[e] returns either the opposite half-edge in the adjacent triangle, or -1 if there's not an adjacent triangle.
|
||||
for (let e = 0; e < this.delaunay.triangles.length; e++) {
|
||||
|
||||
const p = this.delaunay.triangles[this.nextHalfedge(e)];
|
||||
if (p < this.pointsN && !this.cells.c[p]) {
|
||||
const edges = this.edgesAroundPoint(e);
|
||||
this.cells.v[p] = edges.map(e => this.triangleOfEdge(e)); // cell: adjacent vertex
|
||||
this.cells.c[p] = edges.map(e => this.delaunay.triangles[e]).filter(c => c < this.pointsN); // cell: adjacent valid cells
|
||||
this.cells.b[p] = edges.length > this.cells.c[p].length ? 1 : 0; // cell: is border
|
||||
this.cells.v[p] = edges.map((e) => this.triangleOfEdge(e)); // cell: adjacent vertex
|
||||
this.cells.c[p] = edges.map((e) => this.delaunay.triangles[e]).filter((c) => c < this.pointsN); // cell: adjacent valid cells
|
||||
this.cells.b[p] = edges.length > this.cells.c[p].length ? 1 : 0; // cell: is border
|
||||
}
|
||||
|
||||
const t = this.triangleOfEdge(e);
|
||||
if (!this.vertices.p[t]) {
|
||||
this.vertices.p[t] = this.triangleCenter(t); // vertex: coordinates
|
||||
this.vertices.p[t] = this.triangleCenter(t); // vertex: coordinates
|
||||
this.vertices.v[t] = this.trianglesAdjacentToTriangle(t); // vertex: adjacent vertices
|
||||
this.vertices.c[t] = this.pointsOfTriangle(t); // vertex: adjacent cells
|
||||
this.vertices.c[t] = this.pointsOfTriangle(t); // vertex: adjacent cells
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +40,7 @@ class Voronoi {
|
|||
* @returns {[number, number, number]} The IDs of the points comprising the given triangle.
|
||||
*/
|
||||
pointsOfTriangle(t) {
|
||||
return this.edgesOfTriangle(t).map(edge => this.delaunay.triangles[edge]);
|
||||
return this.edgesOfTriangle(t).map((edge) => this.delaunay.triangles[edge]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -80,7 +79,7 @@ class Voronoi {
|
|||
* @returns {[number, number]}
|
||||
*/
|
||||
triangleCenter(t) {
|
||||
let vertices = this.pointsOfTriangle(t).map(p => this.points[p]);
|
||||
let vertices = this.pointsOfTriangle(t).map((p) => this.points[p]);
|
||||
return this.circumcenter(vertices[0], vertices[1], vertices[2]);
|
||||
}
|
||||
|
||||
|
|
@ -89,28 +88,36 @@ class Voronoi {
|
|||
* @param {number} t The index of the triangle
|
||||
* @returns {[number, number, number]} The edges of the triangle.
|
||||
*/
|
||||
edgesOfTriangle(t) { return [3 * t, 3 * t + 1, 3 * t + 2]; }
|
||||
edgesOfTriangle(t) {
|
||||
return [3 * t, 3 * t + 1, 3 * t + 2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables lookup of a triangle, given one of the half-edges of that triangle. Taken from {@link https://mapbox.github.io/delaunator/#edge-and-triangle| the Delaunator docs.}
|
||||
* @param {number} e The index of the edge
|
||||
* @returns {number} The index of the triangle
|
||||
*/
|
||||
triangleOfEdge(e) { return Math.floor(e / 3); }
|
||||
triangleOfEdge(e) {
|
||||
return Math.floor(e / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves to the next half-edge of a triangle, given the current half-edge's index. Taken from {@link https://mapbox.github.io/delaunator/#edge-to-edges| the Delaunator docs.}
|
||||
* @param {number} e The index of the current half edge
|
||||
* @returns {number} The index of the next half edge
|
||||
*/
|
||||
nextHalfedge(e) { return (e % 3 === 2) ? e - 2 : e + 1; }
|
||||
nextHalfedge(e) {
|
||||
return e % 3 === 2 ? e - 2 : e + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves to the previous half-edge of a triangle, given the current half-edge's index. Taken from {@link https://mapbox.github.io/delaunator/#edge-to-edges| the Delaunator docs.}
|
||||
* @param {number} e The index of the current half edge
|
||||
* @returns {number} The index of the previous half edge
|
||||
*/
|
||||
prevHalfedge(e) { return (e % 3 === 0) ? e + 2 : e - 1; }
|
||||
prevHalfedge(e) {
|
||||
return e % 3 === 0 ? e + 2 : e - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the circumcenter of the triangle identified by points a, b, and c. Taken from {@link https://en.wikipedia.org/wiki/Circumscribed_circle#Circumcenter_coordinates| Wikipedia}
|
||||
|
|
@ -127,9 +134,6 @@ class Voronoi {
|
|||
const bd = bx * bx + by * by;
|
||||
const cd = cx * cx + cy * cy;
|
||||
const D = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
|
||||
return [
|
||||
Math.floor(1 / D * (ad * (by - cy) + bd * (cy - ay) + cd * (ay - by))),
|
||||
Math.floor(1 / D * (ad * (cx - bx) + bd * (ax - cx) + cd * (bx - ax)))
|
||||
];
|
||||
return [Math.floor((1 / D) * (ad * (by - cy) + bd * (cy - ay) + cd * (ay - by))), Math.floor((1 / D) * (ad * (cx - bx) + bd * (ax - cx) + cd * (bx - ax)))];
|
||||
}
|
||||
}
|
||||
15
node_modules/.bin/acorn
generated
vendored
Normal file
15
node_modules/.bin/acorn
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
"$basedir/node" "$basedir/../acorn/bin/acorn" "$@"
|
||||
ret=$?
|
||||
else
|
||||
node "$basedir/../acorn/bin/acorn" "$@"
|
||||
ret=$?
|
||||
fi
|
||||
exit $ret
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue