mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2026-02-04 17:41:23 +01:00
Merge af2853d698 into 0feca43b14
This commit is contained in:
commit
d1925fb2ce
43 changed files with 7773 additions and 8108 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@
|
||||||
/node_modules
|
/node_modules
|
||||||
/dist
|
/dist
|
||||||
/coverage
|
/coverage
|
||||||
|
CONTRIBUTING-UPSTREAM.md
|
||||||
|
|
|
||||||
250
GRID-NUMBERING-README.md
Normal file
250
GRID-NUMBERING-README.md
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
# Grid Auto-Numbering Feature for Azgaar Fantasy Map Generator
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Added sequential auto-numbering to the grid overlay system, allowing users to reference specific grid cells (e.g., "POI located in grid 0247"). Numbers are displayed centered within each grid cell with customizable size and color.
|
||||||
|
|
||||||
|
## Features Added
|
||||||
|
|
||||||
|
- ✅ Sequential numbering starting from top-left (0001, 0002, 0003...)
|
||||||
|
- ✅ Works with: **Pointy Hex**, **Square**, and **Truncated Square** grids
|
||||||
|
- ✅ Customizable font size (1-50px)
|
||||||
|
- ✅ Customizable color
|
||||||
|
- ✅ Toggle on/off via checkbox
|
||||||
|
- ✅ Proper centering in grid cells
|
||||||
|
- ✅ Full map coverage
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
**Grid Types Not Yet Supported**:
|
||||||
|
The following grid types require additional positioning calculations and are not currently supported:
|
||||||
|
- ❌ Hex grid (flat)
|
||||||
|
- ❌ Square 45 degrees grid
|
||||||
|
- ❌ Tetrakis square grid
|
||||||
|
- ❌ Triangle grid (horizontal)
|
||||||
|
- ❌ Triangle grid (vertical)
|
||||||
|
- ❌ Trihexagonal grid
|
||||||
|
- ❌ Rhombille grid
|
||||||
|
|
||||||
|
The numbering feature will display on these grid types but numbers will not align correctly with cell centers. Each grid type has unique geometry that requires specific positioning logic in the `getGridCellCenter()` function.
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
### 1. `index.html`
|
||||||
|
**Location**: Lines 889-910 (in `<tbody id="styleGrid">` section)
|
||||||
|
|
||||||
|
**Changes**: Added UI controls for grid numbering in the Style panel:
|
||||||
|
```html
|
||||||
|
<tr data-tip="Enable sequential numbering for grid cells (0001, 0002, etc.)">
|
||||||
|
<td colspan="2">
|
||||||
|
<input id="styleGridShowNumbers" class="checkbox" type="checkbox" />
|
||||||
|
<label for="styleGridShowNumbers" class="checkbox-label">Show grid numbers</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr data-tip="Set grid number font size">
|
||||||
|
<td>Number size</td>
|
||||||
|
<td>
|
||||||
|
<input id="styleGridNumberSize" type="number" min="1" max="50" step="0.5" value="8" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr data-tip="Set grid number color">
|
||||||
|
<td>Number color</td>
|
||||||
|
<td>
|
||||||
|
<input id="styleGridNumberColor" type="color" value="#808080" />
|
||||||
|
<output id="styleGridNumberColorOutput">#808080</output>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `modules/ui/layers.js`
|
||||||
|
**Location**: Lines 657-661, 664-754
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
1. Modified `drawGrid()` function to call `drawGridNumbers()` when enabled
|
||||||
|
2. Added three new functions:
|
||||||
|
- `drawGridNumbers()` - Main rendering logic
|
||||||
|
- `getGridCellDimensions()` - Returns cell dimensions for each grid type
|
||||||
|
- `getGridCellCenter()` - Calculates center position for each cell
|
||||||
|
|
||||||
|
**Key Implementation Details**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// In drawGrid() - Line 657-661
|
||||||
|
const showNumbers = gridOverlay.attr("data-show-numbers") === "1";
|
||||||
|
if (showNumbers) {
|
||||||
|
drawGridNumbers(maxWidth, maxHeight, scale, dx, dy);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. `modules/ui/style.js`
|
||||||
|
**Location**: Lines 212-215 (initialization), 520-525 (declarations), 543-557 (event handlers)
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
1. Added variable declarations for UI elements
|
||||||
|
2. Added initialization code to read grid numbering attributes
|
||||||
|
3. Added event handlers for checkbox, size input, and color picker
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Variable declarations (after styleGridType handler)
|
||||||
|
const styleGridShowNumbers = byId("styleGridShowNumbers");
|
||||||
|
const styleGridNumberSize = byId("styleGridNumberSize");
|
||||||
|
const styleGridNumberColor = byId("styleGridNumberColor");
|
||||||
|
const styleGridNumberColorOutput = byId("styleGridNumberColorOutput");
|
||||||
|
|
||||||
|
// Initialization in selectStyleElement()
|
||||||
|
styleGridShowNumbers.checked = el.attr("data-show-numbers") === "1";
|
||||||
|
styleGridNumberSize.value = el.attr("data-number-size") || 8;
|
||||||
|
styleGridNumberColor.value = styleGridNumberColorOutput.value =
|
||||||
|
el.attr("data-number-color") || "#808080";
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
styleGridShowNumbers.on("change", function () {
|
||||||
|
getEl().attr("data-show-numbers", this.checked ? "1" : "0");
|
||||||
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
|
});
|
||||||
|
|
||||||
|
styleGridNumberSize.on("input", function () {
|
||||||
|
getEl().attr("data-number-size", this.value);
|
||||||
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
|
});
|
||||||
|
|
||||||
|
styleGridNumberColor.on("input", function () {
|
||||||
|
styleGridNumberColorOutput.value = this.value;
|
||||||
|
getEl().attr("data-number-color", this.value);
|
||||||
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Position Calculation Math
|
||||||
|
|
||||||
|
### Pointy Hex Grid Positioning
|
||||||
|
|
||||||
|
After extensive testing and calibration, the following values provide perfect alignment:
|
||||||
|
|
||||||
|
**Row Spacing**: `cellHeight * 0.5`
|
||||||
|
- This is different from the geometric ideal of 0.75
|
||||||
|
- Empirically determined through visual alignment with hex centers
|
||||||
|
|
||||||
|
**Y-Offset (Vertical Center)**: `cellHeight * 0.35`
|
||||||
|
- Positions numbers vertically within hex
|
||||||
|
- Accounts for hex shape geometry
|
||||||
|
|
||||||
|
**X-Offset (Horizontal Center)**: `cellWidth / 2`
|
||||||
|
- Standard horizontal centering
|
||||||
|
|
||||||
|
**Row Offset Pattern**: Even rows (0, 2, 4...) are shifted right by `cellWidth / 2`
|
||||||
|
- This creates the interlocking hex pattern
|
||||||
|
|
||||||
|
### Grid Cell Dimensions (Base Size)
|
||||||
|
|
||||||
|
From SVG pattern definitions:
|
||||||
|
- Pointy Hex: width=25, height=43.4
|
||||||
|
- Flat Hex: width=43.4, height=25
|
||||||
|
- Square: width=25, height=25
|
||||||
|
- (Other grid types defined in `getGridCellDimensions()`)
|
||||||
|
|
||||||
|
### Row/Column Count Calculation
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const rowSpacing = cellHeight * 0.5;
|
||||||
|
const cols = Math.ceil(maxWidth / cellWidth) + 2;
|
||||||
|
const rows = Math.ceil(maxHeight / rowSpacing) + 2;
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Adding +2 ensures full map coverage including edges.
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
### For Users:
|
||||||
|
|
||||||
|
1. Enable the **Grid** layer (press `G` or click Grid toggle)
|
||||||
|
2. Open **Style** panel (right sidebar)
|
||||||
|
3. Select **"Grid"** from the element dropdown
|
||||||
|
4. Check **"Show grid numbers"** checkbox
|
||||||
|
5. Adjust **Number size** and **Number color** as desired
|
||||||
|
|
||||||
|
### For Developers:
|
||||||
|
|
||||||
|
Grid numbering state is stored as attributes on the `gridOverlay` SVG element:
|
||||||
|
- `data-show-numbers`: "1" or "0"
|
||||||
|
- `data-number-size`: Number (default 8)
|
||||||
|
- `data-number-color`: Hex color (default "#808080")
|
||||||
|
|
||||||
|
These can be set programmatically:
|
||||||
|
```javascript
|
||||||
|
gridOverlay.attr("data-show-numbers", "1");
|
||||||
|
gridOverlay.attr("data-number-size", "12");
|
||||||
|
gridOverlay.attr("data-number-color", "#000000");
|
||||||
|
drawGrid();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing & Calibration Process
|
||||||
|
|
||||||
|
The positioning values were determined through iterative testing:
|
||||||
|
|
||||||
|
1. Started with theoretical hex geometry (0.75 row spacing)
|
||||||
|
2. User placed visual markers at hex centers
|
||||||
|
3. Adjusted row spacing and Y-offset incrementally
|
||||||
|
4. Final values: rowSpacing=0.5, Y-offset=0.35
|
||||||
|
|
||||||
|
This empirical approach accounts for any rendering quirks or pattern definition nuances.
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
- Numbers are rendered as SVG text elements
|
||||||
|
- Typical full map: ~3000 grid cells
|
||||||
|
- Negligible performance impact on modern browsers
|
||||||
|
- Numbers are part of the gridOverlay group and clear when grid is toggled off
|
||||||
|
|
||||||
|
## Future Enhancement Ideas
|
||||||
|
|
||||||
|
- Save grid numbering preference to localStorage
|
||||||
|
- Export numbered grid with map export
|
||||||
|
- Click-to-copy grid number functionality
|
||||||
|
- Custom numbering patterns (A1, A2... or hex coordinates)
|
||||||
|
- Grid number search/highlight feature
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
### Why Not Pure Geometric Spacing?
|
||||||
|
|
||||||
|
The theoretical vertical spacing for pointy hex grids is `1.5 × sideLength = 0.75 × height`. However, we use `0.5 × height` because:
|
||||||
|
|
||||||
|
1. SVG pattern rendering may introduce sub-pixel differences
|
||||||
|
2. The pattern definition includes the full hex perimeter path
|
||||||
|
3. Visual alignment matters more than mathematical purity
|
||||||
|
4. The 0.5 value was empirically validated against user-placed markers
|
||||||
|
|
||||||
|
### Edge Case Handling
|
||||||
|
|
||||||
|
- Numbers extend slightly beyond visible map area (+2 buffer)
|
||||||
|
- Partial hex cells at edges still receive numbers
|
||||||
|
- Zoom level doesn't affect numbering (numbers don't scale)
|
||||||
|
|
||||||
|
## Implementation Summary
|
||||||
|
|
||||||
|
**Total Lines of Code Added**: ~150
|
||||||
|
**Files Modified**: 3
|
||||||
|
**New Functions Added**: 3
|
||||||
|
**UI Controls Added**: 3
|
||||||
|
|
||||||
|
This implementation integrates cleanly with Azgaar's existing grid system without modifying core rendering logic. All grid numbering code is isolated and can be easily maintained or removed.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Implementation developed through collaborative debugging session, December 2024.
|
||||||
|
Tested on Azgaar Fantasy Map Generator (latest version as of Dec 2024).
|
||||||
|
|
||||||
|
## Contact & Contribution
|
||||||
|
|
||||||
|
If integrating this into the official Azgaar repository, consider:
|
||||||
|
- Adding grid numbering to the style presets
|
||||||
|
- Including in map export metadata
|
||||||
|
- Adding to the wiki documentation
|
||||||
|
- Internationalizing the UI labels
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**License**: Same as Azgaar Fantasy Map Generator (MIT)
|
||||||
76
README.md
76
README.md
|
|
@ -1,3 +1,79 @@
|
||||||
|
# Azgaar Fantasy Map Generator - Enhanced Fork
|
||||||
|
|
||||||
|
This is a personal fork of [Azgaar's Fantasy Map Generator](https://github.com/Azgaar/Fantasy-Map-Generator) with custom enhancements for D&D campaign management.
|
||||||
|
|
||||||
|
## Original Project
|
||||||
|
|
||||||
|
**Original Repository**: [Azgaar/Fantasy-Map-Generator](https://github.com/Azgaar/Fantasy-Map-Generator)
|
||||||
|
**Original Author**: Max Haniyeu (Azgaar)
|
||||||
|
**License**: MIT License
|
||||||
|
|
||||||
|
Full credit to Azgaar for creating this incredible fantasy map generation tool. Please visit and star the original repository!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Custom Enhancements in This Fork
|
||||||
|
|
||||||
|
### ✨ Grid Auto-Numbering Feature
|
||||||
|
|
||||||
|
Added sequential numbering to grid cells for easy location referencing in D&D campaigns.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Sequential numbering (0001, 0002, 0003...) starting from top-left
|
||||||
|
- Customizable font size and color
|
||||||
|
- Toggle on/off in Style panel
|
||||||
|
- Perfect alignment for pointy hex, square, and truncated square grids
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
1. Enable Grid layer (press `G`)
|
||||||
|
2. Open Style panel → Select "Grid"
|
||||||
|
3. Check "Show grid numbers"
|
||||||
|
4. Adjust size and color as desired
|
||||||
|
|
||||||
|
**Documentation:** See [`GRID-NUMBERING-README.md`](./GRID-NUMBERING-README.md) for full implementation details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone this fork
|
||||||
|
git clone [your-fork-url]
|
||||||
|
cd Fantasy-Map-Generator
|
||||||
|
|
||||||
|
# Open in browser
|
||||||
|
# Just open index.html in your web browser
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing Back to Original Project
|
||||||
|
|
||||||
|
If you're interested in the grid numbering feature, please check out the [original Azgaar repository](https://github.com/Azgaar/Fantasy-Map-Generator) and consider starring it! The feature could potentially be contributed upstream if there's interest.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This fork maintains the original MIT License. See [LICENSE](./LICENSE) for full details.
|
||||||
|
|
||||||
|
**Copyright 2017-2024 Max Haniyeu (Azgaar)**
|
||||||
|
Grid numbering enhancements © 2024
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
- **Azgaar** - For creating and maintaining this fantastic map generator
|
||||||
|
- **Original Contributors** - Everyone who has contributed to the main project
|
||||||
|
- **D&D Community** - For inspiration and use cases
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Original README follows below:**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Fantasy Map Generator
|
# Fantasy Map Generator
|
||||||
|
|
||||||
Azgaar's _Fantasy Map Generator_ is a free web application that helps fantasy writers, game masters, and cartographers create and edit fantasy maps.
|
Azgaar's _Fantasy Map Generator_ is a free web application that helps fantasy writers, game masters, and cartographers create and edit fantasy maps.
|
||||||
|
|
|
||||||
1
images/fantasy-icons/battlefield.svg
Symbolic link
1
images/fantasy-icons/battlefield.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/bridge.svg
Symbolic link
1
images/fantasy-icons/bridge.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/brigand.svg
Symbolic link
1
images/fantasy-icons/brigand.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/burial.svg
Symbolic link
1
images/fantasy-icons/burial.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/canoe.svg
Symbolic link
1
images/fantasy-icons/canoe.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/cave.svg
Symbolic link
1
images/fantasy-icons/cave.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/circus.svg
Symbolic link
1
images/fantasy-icons/circus.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/dance.svg
Symbolic link
1
images/fantasy-icons/dance.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/dungeon.svg
Symbolic link
1
images/fantasy-icons/dungeon.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/encounter.svg
Symbolic link
1
images/fantasy-icons/encounter.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/fair.svg
Symbolic link
1
images/fantasy-icons/fair.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/forest.svg
Symbolic link
1
images/fantasy-icons/forest.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/hot-spring.svg
Symbolic link
1
images/fantasy-icons/hot-spring.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/inn.svg
Symbolic link
1
images/fantasy-icons/inn.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/joust.svg
Symbolic link
1
images/fantasy-icons/joust.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/library.svg
Symbolic link
1
images/fantasy-icons/library.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/lighthouse.svg
Symbolic link
1
images/fantasy-icons/lighthouse.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/migration.svg
Symbolic link
1
images/fantasy-icons/migration.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/mine.svg
Symbolic link
1
images/fantasy-icons/mine.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/mirage.svg
Symbolic link
1
images/fantasy-icons/mirage.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/monster.svg
Symbolic link
1
images/fantasy-icons/monster.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/necropolis.svg
Symbolic link
1
images/fantasy-icons/necropolis.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/pirate.svg
Symbolic link
1
images/fantasy-icons/pirate.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
15
images/fantasy-icons/placeholder.svg
Normal file
15
images/fantasy-icons/placeholder.svg
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!-- Placeholder fantasy icon - replace with detailed artwork -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#8B4513;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#4A2511;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<!-- Skull shape -->
|
||||||
|
<ellipse cx="16" cy="14" rx="10" ry="12" fill="#D3D3D3" stroke="#666" stroke-width="0.5"/>
|
||||||
|
<circle cx="12" cy="12" r="2" fill="#000"/>
|
||||||
|
<circle cx="20" cy="12" r="2" fill="#000"/>
|
||||||
|
<path d="M 13 18 L 15 20 L 17 20 L 19 18" stroke="#000" stroke-width="1" fill="none"/>
|
||||||
|
<text x="16" y="28" font-size="8" fill="#8B4513" text-anchor="middle" font-family="serif">⚔</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 804 B |
1
images/fantasy-icons/portal.svg
Symbolic link
1
images/fantasy-icons/portal.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/rift.svg
Symbolic link
1
images/fantasy-icons/rift.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/ruins.svg
Symbolic link
1
images/fantasy-icons/ruins.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/sacred-mountain.svg
Symbolic link
1
images/fantasy-icons/sacred-mountain.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/sea-monster.svg
Symbolic link
1
images/fantasy-icons/sea-monster.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/statue.svg
Symbolic link
1
images/fantasy-icons/statue.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/volcano.svg
Symbolic link
1
images/fantasy-icons/volcano.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/water-source.svg
Symbolic link
1
images/fantasy-icons/water-source.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
1
images/fantasy-icons/waterfall.svg
Symbolic link
1
images/fantasy-icons/waterfall.svg
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
placeholder.svg
|
||||||
|
|
@ -2421,3 +2421,4 @@ svg.button {
|
||||||
background: #25252a;
|
background: #25252a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.grid-label { font-size: 10px; font-family: sans-serif; pointer-events: none; text-shadow: 1px 1px 0 #fff, -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff; }
|
||||||
|
|
|
||||||
13862
index.html
13862
index.html
File diff suppressed because one or more lines are too long
1
maps/latest.json
Normal file
1
maps/latest.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -11,7 +11,7 @@ window.Markers = (function () {
|
||||||
/*
|
/*
|
||||||
Default markers config:
|
Default markers config:
|
||||||
type - short description (snake-case)
|
type - short description (snake-case)
|
||||||
icon - unicode character or url to image
|
icon - unicode character or url to image (using local fantasy icons)
|
||||||
dx: icon offset in x direction, in pixels
|
dx: icon offset in x direction, in pixels
|
||||||
dy: icon offset in y direction, in pixels
|
dy: icon offset in y direction, in pixels
|
||||||
min: minimum number of candidates to add at least 1 marker
|
min: minimum number of candidates to add at least 1 marker
|
||||||
|
|
@ -22,41 +22,41 @@ window.Markers = (function () {
|
||||||
*/
|
*/
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return [
|
return [
|
||||||
{type: "volcanoes", icon: "🌋", dx: 52, px: 13, min: 10, each: 500, multiplier: 1, list: listVolcanoes, add: addVolcano},
|
{ type: "volcanoes", icon: "./images/fantasy-icons/volcano.svg", dx: 50, dy: 50, px: 16, min: 10, each: 500, multiplier: 1, list: listVolcanoes, add: addVolcano },
|
||||||
{type: "hot-springs", icon: "♨️", dy: 52, min: 30, each: 1200, multiplier: 1, list: listHotSprings, add: addHotSpring},
|
{ type: "hot-springs", icon: "./images/fantasy-icons/hot-spring.svg", dx: 50, dy: 50, px: 16, min: 30, each: 1200, multiplier: 1, list: listHotSprings, add: addHotSpring },
|
||||||
{type: "water-sources", icon: "💧", min: 1, each: 1000, multiplier: 1, list: listWaterSources, add: addWaterSource},
|
{ type: "water-sources", icon: "./images/fantasy-icons/water-source.svg", dx: 50, dy: 50, px: 16, min: 1, each: 1000, multiplier: 1, list: listWaterSources, add: addWaterSource },
|
||||||
{type: "mines", icon: "⛏️", dx: 48, px: 13, min: 1, each: 15, multiplier: 1, list: listMines, add: addMine},
|
{ type: "mines", icon: "./images/fantasy-icons/mine.svg", dx: 50, dy: 50, px: 16, min: 1, each: 15, multiplier: 1, list: listMines, add: addMine },
|
||||||
{type: "bridges", icon: "🌉", px: 14, min: 1, each: 5, multiplier: 1, list: listBridges, add: addBridge},
|
{ type: "bridges", icon: "./images/fantasy-icons/bridge.svg", dx: 50, dy: 50, px: 16, min: 1, each: 5, multiplier: 1, list: listBridges, add: addBridge },
|
||||||
{type: "inns", icon: "🍻", px: 14, min: 1, each: 10, multiplier: 1, list: listInns, add: addInn},
|
{ type: "inns", icon: "./images/fantasy-icons/inn.svg", dx: 50, dy: 50, px: 16, min: 1, each: 10, multiplier: 1, list: listInns, add: addInn },
|
||||||
{type: "lighthouses", icon: "🚨", px: 14, min: 1, each: 2, multiplier: 1, list: listLighthouses, add: addLighthouse},
|
{ type: "lighthouses", icon: "./images/fantasy-icons/lighthouse.svg", dx: 50, dy: 50, px: 16, min: 1, each: 2, multiplier: 1, list: listLighthouses, add: addLighthouse },
|
||||||
{type: "waterfalls", icon: "⟱", dy: 54, px: 16, min: 1, each: 5, multiplier: 1, list: listWaterfalls, add: addWaterfall},
|
{ type: "waterfalls", icon: "./images/fantasy-icons/waterfall.svg", dx: 50, dy: 50, px: 16, min: 1, each: 5, multiplier: 1, list: listWaterfalls, add: addWaterfall },
|
||||||
{type: "battlefields", icon: "⚔️", dy: 52, min: 50, each: 700, multiplier: 1, list: listBattlefields, add: addBattlefield},
|
{ type: "battlefields", icon: "./images/fantasy-icons/battlefield.svg", dx: 50, dy: 50, px: 16, min: 50, each: 700, multiplier: 1, list: listBattlefields, add: addBattlefield },
|
||||||
{type: "dungeons", icon: "🗝️", dy: 51, px: 13, min: 30, each: 200, multiplier: 1, list: listDungeons, add: addDungeon},
|
{ type: "dungeons", icon: "./images/fantasy-icons/dungeon.svg", dx: 50, dy: 50, px: 16, min: 30, each: 200, multiplier: 1, list: listDungeons, add: addDungeon },
|
||||||
{type: "lake-monsters", icon: "🐉", dy: 48, min: 2, each: 10, multiplier: 1, list: listLakeMonsters, add: addLakeMonster},
|
{ type: "lake-monsters", icon: "./images/fantasy-icons/monster.svg", dx: 50, dy: 50, px: 16, min: 2, each: 10, multiplier: 1, list: listLakeMonsters, add: addLakeMonster },
|
||||||
{type: "sea-monsters", icon: "🦑", min: 50, each: 700, multiplier: 1, list: listSeaMonsters, add: addSeaMonster},
|
{ type: "sea-monsters", icon: "./images/fantasy-icons/sea-monster.svg", dx: 50, dy: 50, px: 16, min: 50, each: 700, multiplier: 1, list: listSeaMonsters, add: addSeaMonster },
|
||||||
{type: "hill-monsters", icon: "👹", dy: 54, px: 13, min: 30, each: 600, multiplier: 1, list: listHillMonsters, add: addHillMonster},
|
{ type: "hill-monsters", icon: "./images/fantasy-icons/monster.svg", dx: 50, dy: 50, px: 16, min: 30, each: 600, multiplier: 1, list: listHillMonsters, add: addHillMonster },
|
||||||
{type: "sacred-mountains", icon: "🗻", dy: 48, min: 1, each: 5, multiplier: 1, list: listSacredMountains, add: addSacredMountain},
|
{ type: "sacred-mountains", icon: "./images/fantasy-icons/sacred-mountain.svg", dx: 50, dy: 50, px: 16, min: 1, each: 5, multiplier: 1, list: listSacredMountains, add: addSacredMountain },
|
||||||
{type: "sacred-forests", icon: "🌳", min: 30, each: 1000, multiplier: 1, list: listSacredForests, add: addSacredForest},
|
{ type: "sacred-forests", icon: "./images/fantasy-icons/forest.svg", dx: 50, dy: 50, px: 16, min: 30, each: 1000, multiplier: 1, list: listSacredForests, add: addSacredForest },
|
||||||
{type: "sacred-pineries", icon: "🌲", px: 13, min: 30, each: 800, multiplier: 1, list: listSacredPineries, add: addSacredPinery},
|
{ type: "sacred-pineries", icon: "./images/fantasy-icons/forest.svg", dx: 50, dy: 50, px: 16, min: 30, each: 800, multiplier: 1, list: listSacredPineries, add: addSacredPinery },
|
||||||
{type: "sacred-palm-groves", icon: "🌴", px: 13, min: 1, each: 100, multiplier: 1, list: listSacredPalmGroves, add: addSacredPalmGrove},
|
{ type: "sacred-palm-groves", icon: "./images/fantasy-icons/forest.svg", dx: 50, dy: 50, px: 16, min: 1, each: 100, multiplier: 1, list: listSacredPalmGroves, add: addSacredPalmGrove },
|
||||||
{type: "brigands", icon: "💰", px: 13, min: 50, each: 100, multiplier: 1, list: listBrigands, add: addBrigands},
|
{ type: "brigands", icon: "./images/fantasy-icons/brigand.svg", dx: 50, dy: 50, px: 16, min: 50, each: 100, multiplier: 1, list: listBrigands, add: addBrigands },
|
||||||
{type: "pirates", icon: "🏴☠️", dx: 51, min: 40, each: 300, multiplier: 1, list: listPirates, add: addPirates},
|
{ type: "pirates", icon: "./images/fantasy-icons/pirate.svg", dx: 50, dy: 50, px: 16, min: 40, each: 300, multiplier: 1, list: listPirates, add: addPirates },
|
||||||
{type: "statues", icon: "🗿", min: 80, each: 1200, multiplier: 1, list: listStatues, add: addStatue},
|
{ type: "statues", icon: "./images/fantasy-icons/statue.svg", dx: 50, dy: 50, px: 16, min: 80, each: 1200, multiplier: 1, list: listStatues, add: addStatue },
|
||||||
{type: "ruins", icon: "🏺", min: 80, each: 1200, multiplier: 1, list: listRuins, add: addRuins},
|
{ type: "ruins", icon: "./images/fantasy-icons/ruins.svg", dx: 50, dy: 50, px: 16, min: 80, each: 1200, multiplier: 1, list: listRuins, add: addRuins },
|
||||||
{type: "libraries", icon: "📚", min: 10, each: 1200, multiplier: 1, list: listLibraries, add: addLibrary},
|
{ type: "libraries", icon: "./images/fantasy-icons/library.svg", dx: 50, dy: 50, px: 16, min: 10, each: 1200, multiplier: 1, list: listLibraries, add: addLibrary },
|
||||||
{type: "circuses", icon: "🎪", min: 80, each: 1000, multiplier: 1, list: listCircuses, add: addCircuse},
|
{ type: "circuses", icon: "./images/fantasy-icons/circus.svg", dx: 50, dy: 50, px: 16, min: 80, each: 1000, multiplier: 1, list: listCircuses, add: addCircuse },
|
||||||
{type: "jousts", icon: "🤺", dx: 48, min: 5, each: 500, multiplier: 1, list: listJousts, add: addJoust},
|
{ type: "jousts", icon: "./images/fantasy-icons/joust.svg", dx: 50, dy: 50, px: 16, min: 5, each: 500, multiplier: 1, list: listJousts, add: addJoust },
|
||||||
{type: "fairs", icon: "🎠", min: 50, each: 1000, multiplier: 1, list: listFairs, add: addFair},
|
{ type: "fairs", icon: "./images/fantasy-icons/fair.svg", dx: 50, dy: 50, px: 16, min: 50, each: 1000, multiplier: 1, list: listFairs, add: addFair },
|
||||||
{type: "canoes", icon: "🛶", min: 500, each: 2000, multiplier: 1, list: listCanoes, add: addCanoe},
|
{ type: "canoes", icon: "./images/fantasy-icons/canoe.svg", dx: 50, dy: 50, px: 16, min: 500, each: 2000, multiplier: 1, list: listCanoes, add: addCanoe },
|
||||||
{type: "migration", icon: "🐗", min: 20, each: 1000, multiplier: 1, list: listMigrations, add: addMigration},
|
{ type: "migration", icon: "./images/fantasy-icons/migration.svg", dx: 50, dy: 50, px: 16, min: 20, each: 1000, multiplier: 1, list: listMigrations, add: addMigration },
|
||||||
{type: "dances", icon: "💃🏽", min: 50, each: 1000, multiplier: 1, list: listDances, add: addDances},
|
{ type: "dances", icon: "./images/fantasy-icons/dance.svg", dx: 50, dy: 50, px: 16, min: 50, each: 1000, multiplier: 1, list: listDances, add: addDances },
|
||||||
{type: "mirage", icon: "💦", min: 10, each: 400, multiplier: 1, list: listMirage, add: addMirage},
|
{ type: "mirage", icon: "./images/fantasy-icons/mirage.svg", dx: 50, dy: 50, px: 16, min: 10, each: 400, multiplier: 1, list: listMirage, add: addMirage },
|
||||||
{type: "caves", icon:"🦇", min: 60, each: 1000, multiplier: 1, list: listCaves, add: addCave},
|
{ type: "caves", icon: "./images/fantasy-icons/cave.svg", dx: 50, dy: 50, px: 16, min: 60, each: 1000, multiplier: 1, list: listCaves, add: addCave },
|
||||||
{type: "portals", icon: "🌀", px: 14, min: 16, each: 8, multiplier: +isFantasy, list: listPortals, add: addPortal},
|
{ type: "portals", icon: "./images/fantasy-icons/portal.svg", dx: 50, dy: 50, px: 16, min: 16, each: 8, multiplier: +isFantasy, list: listPortals, add: addPortal },
|
||||||
{type: "rifts", icon: "🎆", min: 5, each: 3000, multiplier: +isFantasy, list: listRifts, add: addRift},
|
{ type: "rifts", icon: "./images/fantasy-icons/rift.svg", dx: 50, dy: 50, px: 16, min: 5, each: 3000, multiplier: +isFantasy, list: listRifts, add: addRift },
|
||||||
{type: "disturbed-burials", icon: "💀", min: 20, each: 3000, multiplier: +isFantasy, list: listDisturbedBurial, add: addDisturbedBurial},
|
{ type: "disturbed-burials", icon: "./images/fantasy-icons/burial.svg", dx: 50, dy: 50, px: 16, min: 20, each: 3000, multiplier: +isFantasy, list: listDisturbedBurial, add: addDisturbedBurial },
|
||||||
{type: "necropolises", icon: "🪦", min: 20, each: 1000, multiplier: 1, list: listNecropolis, add: addNecropolis},
|
{ type: "necropolises", icon: "./images/fantasy-icons/necropolis.svg", dx: 50, dy: 50, px: 16, min: 20, each: 1000, multiplier: 1, list: listNecropolis, add: addNecropolis },
|
||||||
{type: "encounters", icon: "🧙", min: 10, each: 600, multiplier: 1, list: listEncounters, add: addEncounter},
|
{ type: "encounters", icon: "./images/fantasy-icons/encounter.svg", dx: 50, dy: 50, px: 16, min: 10, each: 600, multiplier: 1, list: listEncounters, add: addEncounter },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@ window.Markers = (function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const regenerate = () => {
|
const regenerate = () => {
|
||||||
pack.markers = pack.markers.filter(({i, lock, cell}) => {
|
pack.markers = pack.markers.filter(({ i, lock, cell }) => {
|
||||||
if (lock) {
|
if (lock) {
|
||||||
occupied[cell] = true;
|
occupied[cell] = true;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -91,22 +91,22 @@ window.Markers = (function () {
|
||||||
const add = marker => {
|
const add = marker => {
|
||||||
const base = config.find(c => c.type === marker.type);
|
const base = config.find(c => c.type === marker.type);
|
||||||
if (base) {
|
if (base) {
|
||||||
const {icon, type, dx, dy, px} = base;
|
const { icon, type, dx, dy, px } = base;
|
||||||
marker = addMarker({icon, type, dx, dy, px}, marker);
|
marker = addMarker({ icon, type, dx, dy, px }, marker);
|
||||||
base.add("marker" + marker.i, marker.cell);
|
base.add("marker" + marker.i, marker.cell);
|
||||||
return marker;
|
return marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
const i = last(pack.markers)?.i + 1 || 0;
|
const i = last(pack.markers)?.i + 1 || 0;
|
||||||
pack.markers.push({...marker, i});
|
pack.markers.push({ ...marker, i });
|
||||||
occupied[marker.cell] = true;
|
occupied[marker.cell] = true;
|
||||||
return {...marker, i};
|
return { ...marker, i };
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateTypes() {
|
function generateTypes() {
|
||||||
TIME && console.time("addMarkers");
|
TIME && console.time("addMarkers");
|
||||||
|
|
||||||
config.forEach(({type, icon, dx, dy, px, min, each, multiplier, list, add}) => {
|
config.forEach(({ type, icon, dx, dy, px, min, each, multiplier, list, add }) => {
|
||||||
if (multiplier === 0) return;
|
if (multiplier === 0) return;
|
||||||
|
|
||||||
let candidates = Array.from(list(pack));
|
let candidates = Array.from(list(pack));
|
||||||
|
|
@ -116,7 +116,7 @@ window.Markers = (function () {
|
||||||
|
|
||||||
while (quantity && candidates.length) {
|
while (quantity && candidates.length) {
|
||||||
const [cell] = extractAnyElement(candidates);
|
const [cell] = extractAnyElement(candidates);
|
||||||
const marker = addMarker({icon, type, dx, dy, px}, {cell});
|
const marker = addMarker({ icon, type, dx, dy, px }, { cell });
|
||||||
if (!marker) continue;
|
if (!marker) continue;
|
||||||
add("marker" + marker.i, cell);
|
add("marker" + marker.i, cell);
|
||||||
quantity--;
|
quantity--;
|
||||||
|
|
@ -139,11 +139,11 @@ window.Markers = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMarkerCoordinates(cell) {
|
function getMarkerCoordinates(cell) {
|
||||||
const {cells, burgs} = pack;
|
const { cells, burgs } = pack;
|
||||||
const burgId = cells.burg[cell];
|
const burgId = cells.burg[cell];
|
||||||
|
|
||||||
if (burgId) {
|
if (burgId) {
|
||||||
const {x, y} = burgs[burgId];
|
const { x, y } = burgs[burgId];
|
||||||
return [x, y];
|
return [x, y];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,7 +154,7 @@ window.Markers = (function () {
|
||||||
if (marker.cell === undefined) return;
|
if (marker.cell === undefined) return;
|
||||||
const i = last(pack.markers)?.i + 1 || 0;
|
const i = last(pack.markers)?.i + 1 || 0;
|
||||||
const [x, y] = getMarkerCoordinates(marker.cell);
|
const [x, y] = getMarkerCoordinates(marker.cell);
|
||||||
marker = {...base, x, y, ...marker, i};
|
marker = { ...base, x, y, ...marker, i };
|
||||||
pack.markers.push(marker);
|
pack.markers.push(marker);
|
||||||
occupied[marker.cell] = true;
|
occupied[marker.cell] = true;
|
||||||
return marker;
|
return marker;
|
||||||
|
|
@ -166,40 +166,40 @@ window.Markers = (function () {
|
||||||
pack.markers = pack.markers.filter(m => m.i !== markerId);
|
pack.markers = pack.markers.filter(m => m.i !== markerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function listVolcanoes({cells}) {
|
function listVolcanoes({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 70);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 70);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addVolcano(id, cell) {
|
function addVolcano(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const proper = Names.getCulture(cells.culture[cell]);
|
const proper = Names.getCulture(cells.culture[cell]);
|
||||||
const name = P(0.3) ? "Mount " + proper : P(0.7) ? proper + " Volcano" : proper;
|
const name = P(0.3) ? "Mount " + proper : P(0.7) ? proper + " Volcano" : proper;
|
||||||
const status = P(0.6) ? "Dormant" : P(0.4) ? "Active" : "Erupting";
|
const status = P(0.6) ? "Dormant" : P(0.4) ? "Active" : "Erupting";
|
||||||
notes.push({id, name, legend: `${status} volcano. Height: ${getFriendlyHeight(cells.p[cell])}.`});
|
notes.push({ id, name, legend: `${status} volcano. Height: ${getFriendlyHeight(cells.p[cell])}.` });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listHotSprings({cells}) {
|
function listHotSprings({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] > 50 && cells.culture[i]);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] > 50 && cells.culture[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addHotSpring(id, cell) {
|
function addHotSpring(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const proper = Names.getCulture(cells.culture[cell]);
|
const proper = Names.getCulture(cells.culture[cell]);
|
||||||
const temp = convertTemperature(gauss(35, 15, 20, 100));
|
const temp = convertTemperature(gauss(35, 15, 20, 100));
|
||||||
const name = P(0.3) ? "Hot Springs of " + proper : P(0.7) ? proper + " Hot Springs" : proper;
|
const name = P(0.3) ? "Hot Springs of " + proper : P(0.7) ? proper + " Hot Springs" : proper;
|
||||||
const legend = `A geothermal springs with naturally heated water that provide relaxation and medicinal benefits. Average temperature is ${temp}.`;
|
const legend = `A geothermal springs with naturally heated water that provide relaxation and medicinal benefits. Average temperature is ${temp}.`;
|
||||||
|
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listWaterSources({cells}) {
|
function listWaterSources({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] > 30 && cells.r[i]);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] > 30 && cells.r[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWaterSource(id, cell) {
|
function addWaterSource(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const type = rw({
|
const type = rw({
|
||||||
"Healing Spring": 5,
|
"Healing Spring": 5,
|
||||||
|
|
@ -218,26 +218,26 @@ window.Markers = (function () {
|
||||||
const legend =
|
const legend =
|
||||||
"This legendary water source is whispered about in ancient tales and believed to possess mystical properties. The spring emanates crystal-clear water, shimmering with an otherworldly iridescence that sparkles even in the dimmest light.";
|
"This legendary water source is whispered about in ancient tales and believed to possess mystical properties. The spring emanates crystal-clear water, shimmering with an otherworldly iridescence that sparkles even in the dimmest light.";
|
||||||
|
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listMines({cells}) {
|
function listMines({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] > 47 && cells.burg[i]);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] > 47 && cells.burg[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMine(id, cell) {
|
function addMine(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const resources = {salt: 5, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1};
|
const resources = { salt: 5, gold: 2, silver: 4, copper: 2, iron: 3, lead: 1, tin: 1 };
|
||||||
const resource = rw(resources);
|
const resource = rw(resources);
|
||||||
const burg = pack.burgs[cells.burg[cell]];
|
const burg = pack.burgs[cells.burg[cell]];
|
||||||
const name = `${burg.name} — ${resource} mining town`;
|
const name = `${burg.name} — ${resource} mining town`;
|
||||||
const population = rn(burg.population * populationRate * urbanization);
|
const population = rn(burg.population * populationRate * urbanization);
|
||||||
const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine.`;
|
const legend = `${burg.name} is a mining town of ${population} people just nearby the ${resource} mine.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listBridges({cells, burgs}) {
|
function listBridges({ cells, burgs }) {
|
||||||
const meanFlux = d3.mean(cells.fl.filter(fl => fl));
|
const meanFlux = d3.mean(cells.fl.filter(fl => fl));
|
||||||
return cells.i.filter(
|
return cells.i.filter(
|
||||||
i =>
|
i =>
|
||||||
|
|
@ -251,7 +251,7 @@ window.Markers = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBridge(id, cell) {
|
function addBridge(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const burg = pack.burgs[cells.burg[cell]];
|
const burg = pack.burgs[cells.burg[cell]];
|
||||||
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
|
const river = pack.rivers.find(r => r.i === pack.cells.r[cell]);
|
||||||
|
|
@ -277,10 +277,10 @@ window.Markers = (function () {
|
||||||
? `A ${rw(weightedAdjectives)} bridge spans over the ${riverName} near ${burg.name}.`
|
? `A ${rw(weightedAdjectives)} bridge spans over the ${riverName} near ${burg.name}.`
|
||||||
: `An old crossing of the ${riverName}, rarely used since ${ra(barriers)}.`;
|
: `An old crossing of the ${riverName}, rarely used since ${ra(barriers)}.`;
|
||||||
|
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listInns({cells}) {
|
function listInns({ cells }) {
|
||||||
const crossRoads = cells.i.filter(i => !occupied[i] && cells.pop[i] > 5 && Routes.isCrossroad(i));
|
const crossRoads = cells.i.filter(i => !occupied[i] && cells.pop[i] > 5 && Routes.isCrossroad(i));
|
||||||
return crossRoads;
|
return crossRoads;
|
||||||
}
|
}
|
||||||
|
|
@ -540,17 +540,17 @@ window.Markers = (function () {
|
||||||
const course = `${ra(methods)} ${meal}`.toLowerCase();
|
const course = `${ra(methods)} ${meal}`.toLowerCase();
|
||||||
const drink = `${P(0.5) ? ra(types) : ra(colors)} ${ra(drinks)}`.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.`;
|
const legend = `A big and famous roadside ${typeName}. Delicious ${course} with ${drink} is served here.`;
|
||||||
notes.push({id, name: "The " + name, legend});
|
notes.push({ id, name: "The " + name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listLighthouses({cells}) {
|
function listLighthouses({ cells }) {
|
||||||
return cells.i.filter(
|
return cells.i.filter(
|
||||||
i => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && Routes.isConnected(c))
|
i => !occupied[i] && cells.harbor[i] > 6 && cells.c[i].some(c => cells.h[c] < 20 && Routes.isConnected(c))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLighthouse(id, cell) {
|
function addLighthouse(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
||||||
notes.push({
|
notes.push({
|
||||||
|
|
@ -560,14 +560,14 @@ window.Markers = (function () {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function listWaterfalls({cells}) {
|
function listWaterfalls({ cells }) {
|
||||||
return cells.i.filter(
|
return 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])
|
i => cells.r[i] && !occupied[i] && cells.h[i] >= 50 && cells.c[i].some(c => cells.h[c] < 40 && cells.r[c])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWaterfall(id, cell) {
|
function addWaterfall(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const descriptions = [
|
const descriptions = [
|
||||||
"A gorgeous waterfall flows here.",
|
"A gorgeous waterfall flows here.",
|
||||||
|
|
@ -579,17 +579,17 @@ window.Markers = (function () {
|
||||||
];
|
];
|
||||||
|
|
||||||
const proper = cells.burg[cell] ? pack.burgs[cells.burg[cell]].name : Names.getCulture(cells.culture[cell]);
|
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)}`});
|
notes.push({ id, name: getAdjective(proper) + " Waterfall" + name, legend: `${ra(descriptions)}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listBattlefields({cells}) {
|
function listBattlefields({ cells }) {
|
||||||
return cells.i.filter(
|
return cells.i.filter(
|
||||||
i => !occupied[i] && cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25
|
i => !occupied[i] && cells.state[i] && cells.pop[i] > 2 && cells.h[i] < 50 && cells.h[i] > 25
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBattlefield(id, cell) {
|
function addBattlefield(id, cell) {
|
||||||
const {cells, states} = pack;
|
const { cells, states } = pack;
|
||||||
|
|
||||||
const state = states[cells.state[cell]];
|
const state = states[cells.state[cell]];
|
||||||
if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state);
|
if (!state.campaigns) state.campaigns = BurgsAndStates.generateCampaign(state);
|
||||||
|
|
@ -597,10 +597,10 @@ window.Markers = (function () {
|
||||||
const date = generateDate(campaign.start, campaign.end);
|
const date = generateDate(campaign.start, campaign.end);
|
||||||
const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
|
const name = Names.getCulture(cells.culture[cell]) + " Battlefield";
|
||||||
const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}.`;
|
const legend = `A historical battle of the ${campaign.name}. \r\nDate: ${date} ${options.era}.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listDungeons({cells}) {
|
function listDungeons({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.pop[i] && cells.pop[i] < 3);
|
return cells.i.filter(i => !occupied[i] && cells.pop[i] && cells.pop[i] < 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -608,10 +608,10 @@ window.Markers = (function () {
|
||||||
const dungeonSeed = `${seed}${cell}`;
|
const dungeonSeed = `${seed}${cell}`;
|
||||||
const name = "Dungeon";
|
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="pointer-events: none;" src="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" sandbox="allow-scripts allow-same-origin"></iframe>`;
|
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="pointer-events: none;" src="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" sandbox="allow-scripts allow-same-origin"></iframe>`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listLakeMonsters({features}) {
|
function listLakeMonsters({ features }) {
|
||||||
return features
|
return features
|
||||||
.filter(feature => feature.type === "lake" && feature.group === "freshwater" && !occupied[feature.firstCell])
|
.filter(feature => feature.type === "lake" && feature.group === "freshwater" && !occupied[feature.firstCell])
|
||||||
.map(feature => feature.firstCell);
|
.map(feature => feature.firstCell);
|
||||||
|
|
@ -637,13 +637,12 @@ window.Markers = (function () {
|
||||||
"Journeying folk",
|
"Journeying folk",
|
||||||
"Tales"
|
"Tales"
|
||||||
];
|
];
|
||||||
const legend = `${ra(subjects)} say a relic monster of ${length} ${heightUnit.value} long inhabits ${
|
const legend = `${ra(subjects)} say a relic monster of ${length} ${heightUnit.value} long inhabits ${lake.name
|
||||||
lake.name
|
} Lake. Truth or lie, folks are afraid to fish in the lake.`;
|
||||||
} Lake. Truth or lie, folks are afraid to fish in the lake.`;
|
notes.push({ id, name, legend });
|
||||||
notes.push({id, name, legend});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function listSeaMonsters({cells, features}) {
|
function listSeaMonsters({ cells, features }) {
|
||||||
return cells.i.filter(
|
return cells.i.filter(
|
||||||
i => !occupied[i] && cells.h[i] < 20 && Routes.isConnected(i) && features[cells.f[i]].type === "ocean"
|
i => !occupied[i] && cells.h[i] < 20 && Routes.isConnected(i) && features[cells.f[i]].type === "ocean"
|
||||||
);
|
);
|
||||||
|
|
@ -653,15 +652,15 @@ window.Markers = (function () {
|
||||||
const name = `${Names.getCultureShort(0)} Monster`;
|
const name = `${Names.getCultureShort(0)} Monster`;
|
||||||
const length = gauss(25, 10, 10, 100);
|
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.`;
|
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});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listHillMonsters({cells}) {
|
function listHillMonsters({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addHillMonster(id, cell) {
|
function addHillMonster(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const adjectives = [
|
const adjectives = [
|
||||||
"great",
|
"great",
|
||||||
|
|
@ -729,11 +728,11 @@ window.Markers = (function () {
|
||||||
const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${monster} who inhabits ${toponym} hills and ${ra(
|
const legend = `${ra(subjects)} speak of a ${ra(adjectives)} ${monster} who inhabits ${toponym} hills and ${ra(
|
||||||
modusOperandi
|
modusOperandi
|
||||||
)}.`;
|
)}.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sacred mountains spawn on lonely mountains
|
// Sacred mountains spawn on lonely mountains
|
||||||
function listSacredMountains({cells}) {
|
function listSacredMountains({ cells }) {
|
||||||
return cells.i.filter(
|
return cells.i.filter(
|
||||||
i =>
|
i =>
|
||||||
!occupied[i] &&
|
!occupied[i] &&
|
||||||
|
|
@ -744,50 +743,50 @@ window.Markers = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSacredMountain(id, cell) {
|
function addSacredMountain(id, cell) {
|
||||||
const {cells, religions} = pack;
|
const { cells, religions } = pack;
|
||||||
|
|
||||||
const culture = cells.c[cell].map(c => cells.culture[c]).find(c => c);
|
const culture = cells.c[cell].map(c => cells.culture[c]).find(c => c);
|
||||||
const religion = cells.religion[cell];
|
const religion = cells.religion[cell];
|
||||||
const name = `${Names.getCulture(culture)} Mountain`;
|
const name = `${Names.getCulture(culture)} Mountain`;
|
||||||
const height = getFriendlyHeight(cells.p[cell]);
|
const height = getFriendlyHeight(cells.p[cell]);
|
||||||
const legend = `A sacred mountain of ${religions[religion].name}. Height: ${height}.`;
|
const legend = `A sacred mountain of ${religions[religion].name}. Height: ${height}.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sacred forests spawn on temperate forests
|
// Sacred forests spawn on temperate forests
|
||||||
function listSacredForests({cells}) {
|
function listSacredForests({ cells }) {
|
||||||
return cells.i.filter(
|
return cells.i.filter(
|
||||||
i => !occupied[i] && cells.culture[i] && cells.religion[i] && [6, 8].includes(cells.biome[i])
|
i => !occupied[i] && cells.culture[i] && cells.religion[i] && [6, 8].includes(cells.biome[i])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSacredForest(id, cell) {
|
function addSacredForest(id, cell) {
|
||||||
const {cells, religions} = pack;
|
const { cells, religions } = pack;
|
||||||
|
|
||||||
const culture = cells.culture[cell];
|
const culture = cells.culture[cell];
|
||||||
const religion = cells.religion[cell];
|
const religion = cells.religion[cell];
|
||||||
const name = `${Names.getCulture(culture)} Forest`;
|
const name = `${Names.getCulture(culture)} Forest`;
|
||||||
const legend = `A forest sacred to local ${religions[religion].name}.`;
|
const legend = `A forest sacred to local ${religions[religion].name}.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sacred pineries spawn on boreal forests
|
// Sacred pineries spawn on boreal forests
|
||||||
function listSacredPineries({cells}) {
|
function listSacredPineries({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.religion[i] && cells.biome[i] === 9);
|
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.religion[i] && cells.biome[i] === 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSacredPinery(id, cell) {
|
function addSacredPinery(id, cell) {
|
||||||
const {cells, religions} = pack;
|
const { cells, religions } = pack;
|
||||||
|
|
||||||
const culture = cells.culture[cell];
|
const culture = cells.culture[cell];
|
||||||
const religion = cells.religion[cell];
|
const religion = cells.religion[cell];
|
||||||
const name = `${Names.getCulture(culture)} Pinery`;
|
const name = `${Names.getCulture(culture)} Pinery`;
|
||||||
const legend = `A pinery sacred to local ${religions[religion].name}.`;
|
const legend = `A pinery sacred to local ${religions[religion].name}.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sacred palm groves spawn on oasises
|
// Sacred palm groves spawn on oasises
|
||||||
function listSacredPalmGroves({cells}) {
|
function listSacredPalmGroves({ cells }) {
|
||||||
return cells.i.filter(
|
return cells.i.filter(
|
||||||
i =>
|
i =>
|
||||||
!occupied[i] &&
|
!occupied[i] &&
|
||||||
|
|
@ -800,21 +799,21 @@ window.Markers = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSacredPalmGrove(id, cell) {
|
function addSacredPalmGrove(id, cell) {
|
||||||
const {cells, religions} = pack;
|
const { cells, religions } = pack;
|
||||||
|
|
||||||
const culture = cells.culture[cell];
|
const culture = cells.culture[cell];
|
||||||
const religion = cells.religion[cell];
|
const religion = cells.religion[cell];
|
||||||
const name = `${Names.getCulture(culture)} Palm Grove`;
|
const name = `${Names.getCulture(culture)} Palm Grove`;
|
||||||
const legend = `A palm grove sacred to local ${religions[religion].name}.`;
|
const legend = `A palm grove sacred to local ${religions[religion].name}.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listBrigands({cells}) {
|
function listBrigands({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && Routes.hasRoad(i));
|
return cells.i.filter(i => !occupied[i] && cells.culture[i] && Routes.hasRoad(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBrigands(id, cell) {
|
function addBrigands(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const animals = [
|
const animals = [
|
||||||
"Apes",
|
"Apes",
|
||||||
|
|
@ -848,7 +847,7 @@ window.Markers = (function () {
|
||||||
"Wolverines",
|
"Wolverines",
|
||||||
"Falcons"
|
"Falcons"
|
||||||
];
|
];
|
||||||
const types = {brigands: 4, bandits: 3, robbers: 1, highwaymen: 1};
|
const types = { brigands: 4, bandits: 3, robbers: 1, highwaymen: 1 };
|
||||||
|
|
||||||
const culture = cells.culture[cell];
|
const culture = cells.culture[cell];
|
||||||
const biome = cells.biome[cell];
|
const biome = cells.biome[cell];
|
||||||
|
|
@ -865,26 +864,26 @@ window.Markers = (function () {
|
||||||
|
|
||||||
const name = `${Names.getCulture(culture)} ${ra(animals)}`;
|
const name = `${Names.getCulture(culture)} ${ra(animals)}`;
|
||||||
const legend = `A gang of ${locality} ${rw(types)}.`;
|
const legend = `A gang of ${locality} ${rw(types)}.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pirates spawn on sea routes
|
// Pirates spawn on sea routes
|
||||||
function listPirates({cells}) {
|
function listPirates({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && Routes.isConnected(i));
|
return cells.i.filter(i => !occupied[i] && cells.h[i] < 20 && Routes.isConnected(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPirates(id, cell) {
|
function addPirates(id, cell) {
|
||||||
const name = "Pirates";
|
const name = "Pirates";
|
||||||
const legend = "Pirate ships have been spotted in these waters.";
|
const legend = "Pirate ships have been spotted in these waters.";
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listStatues({cells}) {
|
function listStatues({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.h[i] < 40);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.h[i] < 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addStatue(id, cell) {
|
function addStatue(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const variants = [
|
const variants = [
|
||||||
"Statue",
|
"Statue",
|
||||||
|
|
@ -919,10 +918,10 @@ window.Markers = (function () {
|
||||||
.join("");
|
.join("");
|
||||||
const legend = `An ancient ${variant.toLowerCase()}. It has an inscription, but no one can translate it:
|
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>`;
|
<div style="font-size: 1.8em; line-break: anywhere;">${inscription}</div>`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listRuins({cells}) {
|
function listRuins({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && cells.h[i] < 60);
|
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && cells.h[i] < 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -946,24 +945,24 @@ window.Markers = (function () {
|
||||||
const ruinType = ra(types);
|
const ruinType = ra(types);
|
||||||
const name = `Ruined ${ruinType}`;
|
const name = `Ruined ${ruinType}`;
|
||||||
const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. Untold riches may lie within.`;
|
const legend = `Ruins of an ancient ${ruinType.toLowerCase()}. Untold riches may lie within.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listLibraries({cells}) {
|
function listLibraries({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.burg[i] && cells.pop[i] > 10);
|
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.burg[i] && cells.pop[i] > 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLibrary(id, cell) {
|
function addLibrary(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const type = rw({Library: 3, Archive: 1, Collection: 1});
|
const type = rw({ Library: 3, Archive: 1, Collection: 1 });
|
||||||
const name = `${Names.getCulture(cells.culture[cell])} ${type}`;
|
const name = `${Names.getCulture(cells.culture[cell])} ${type}`;
|
||||||
const legend = "A vast collection of knowledge, including many rare and ancient tomes.";
|
const legend = "A vast collection of knowledge, including many rare and ancient tomes.";
|
||||||
|
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listCircuses({cells}) {
|
function listCircuses({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && Routes.isConnected(i));
|
return cells.i.filter(i => !occupied[i] && cells.culture[i] && cells.h[i] >= 20 && Routes.isConnected(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -982,15 +981,15 @@ window.Markers = (function () {
|
||||||
const adjective = ra(adjectives);
|
const adjective = ra(adjectives);
|
||||||
const name = `Travelling ${adjective} Circus`;
|
const name = `Travelling ${adjective} Circus`;
|
||||||
const legend = `Roll up, roll up, this ${adjective.toLowerCase()} circus is here for a limited time only.`;
|
const legend = `Roll up, roll up, this ${adjective.toLowerCase()} circus is here for a limited time only.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listJousts({cells, burgs}) {
|
function listJousts({ cells, burgs }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population > 20);
|
return cells.i.filter(i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population > 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addJoust(id, cell) {
|
function addJoust(id, cell) {
|
||||||
const {cells, burgs} = pack;
|
const { cells, burgs } = pack;
|
||||||
const types = ["Joust", "Competition", "Melee", "Tournament", "Contest"];
|
const types = ["Joust", "Competition", "Melee", "Tournament", "Contest"];
|
||||||
const virtues = ["cunning", "might", "speed", "the greats", "acumen", "brutality"];
|
const virtues = ["cunning", "might", "speed", "the greats", "acumen", "brutality"];
|
||||||
|
|
||||||
|
|
@ -1001,17 +1000,17 @@ window.Markers = (function () {
|
||||||
|
|
||||||
const name = `${burgName} ${type}`;
|
const name = `${burgName} ${type}`;
|
||||||
const legend = `Warriors from around the land gather for a ${type.toLowerCase()} of ${virtue} in ${burgName}, with fame, fortune and favour on offer to the victor.`;
|
const legend = `Warriors from around the land gather for a ${type.toLowerCase()} of ${virtue} in ${burgName}, with fame, fortune and favour on offer to the victor.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listFairs({cells, burgs}) {
|
function listFairs({ cells, burgs }) {
|
||||||
return cells.i.filter(
|
return cells.i.filter(
|
||||||
i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population < 20 && burgs[cells.burg[i]].population < 5
|
i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population < 20 && burgs[cells.burg[i]].population < 5
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFair(id, cell) {
|
function addFair(id, cell) {
|
||||||
const {cells, burgs} = pack;
|
const { cells, burgs } = pack;
|
||||||
if (!cells.burg[cell]) return;
|
if (!cells.burg[cell]) return;
|
||||||
|
|
||||||
const burgName = burgs[cells.burg[cell]].name;
|
const burgName = burgs[cells.burg[cell]].name;
|
||||||
|
|
@ -1019,10 +1018,10 @@ window.Markers = (function () {
|
||||||
|
|
||||||
const name = `${burgName} ${type}`;
|
const name = `${burgName} ${type}`;
|
||||||
const legend = `A fair is being held in ${burgName}, with all manner of local and foreign goods and services on offer.`;
|
const legend = `A fair is being held in ${burgName}, with all manner of local and foreign goods and services on offer.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listCanoes({cells}) {
|
function listCanoes({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.r[i]);
|
return cells.i.filter(i => !occupied[i] && cells.r[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1032,10 +1031,10 @@ window.Markers = (function () {
|
||||||
const name = `Minor Jetty`;
|
const name = `Minor Jetty`;
|
||||||
const riverName = river ? `${river.name} ${river.type}` : "river";
|
const riverName = river ? `${river.name} ${river.type}` : "river";
|
||||||
const legend = `A small location along the ${riverName} to launch boats from sits here, along with a weary looking owner, willing to sell passage along the river.`;
|
const legend = `A small location along the ${riverName} to launch boats from sits here, along with a weary looking owner, willing to sell passage along the river.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listMigrations({cells}) {
|
function listMigrations({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] <= 2);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] <= 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1097,15 +1096,15 @@ window.Markers = (function () {
|
||||||
|
|
||||||
const name = `${animalChoice} migration`;
|
const name = `${animalChoice} migration`;
|
||||||
const legend = `A huge group of ${animalChoice.toLowerCase()} are migrating, whether part of their annual routine, or something more extraordinary.`;
|
const legend = `A huge group of ${animalChoice.toLowerCase()} are migrating, whether part of their annual routine, or something more extraordinary.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listDances({cells, burgs}) {
|
function listDances({ cells, burgs }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population > 15);
|
return cells.i.filter(i => !occupied[i] && cells.burg[i] && burgs[cells.burg[i]].population > 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDances(id, cell) {
|
function addDances(id, cell) {
|
||||||
const {cells, burgs} = pack;
|
const { cells, burgs } = pack;
|
||||||
const burgName = burgs[cells.burg[cell]].name;
|
const burgName = burgs[cells.burg[cell]].name;
|
||||||
const socialTypes = [
|
const socialTypes = [
|
||||||
"gala",
|
"gala",
|
||||||
|
|
@ -1136,10 +1135,10 @@ window.Markers = (function () {
|
||||||
const legend = `A ${socialType} has been organised at ${burgName} as a chance to gather the ${ra(
|
const legend = `A ${socialType} has been organised at ${burgName} as a chance to gather the ${ra(
|
||||||
people
|
people
|
||||||
)} of the area together to be merry, make alliances and scheme around the crisis.`;
|
)} of the area together to be merry, make alliances and scheme around the crisis.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listMirage({cells}) {
|
function listMirage({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.biome[i] === 1);
|
return cells.i.filter(i => !occupied[i] && cells.biome[i] === 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1149,15 +1148,15 @@ window.Markers = (function () {
|
||||||
const mirageAdjective = ra(adjectives);
|
const mirageAdjective = ra(adjectives);
|
||||||
const name = `${mirageAdjective} mirage`;
|
const name = `${mirageAdjective} mirage`;
|
||||||
const legend = `This ${mirageAdjective.toLowerCase()} mirage has been luring travellers out of their way for eons.`;
|
const legend = `This ${mirageAdjective.toLowerCase()} mirage has been luring travellers out of their way for eons.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listCaves({cells}) {
|
function listCaves({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 50 && cells.pop[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCave(id, cell) {
|
function addCave(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const formations = {
|
const formations = {
|
||||||
Cave: 10,
|
Cave: 10,
|
||||||
|
|
@ -1186,28 +1185,28 @@ window.Markers = (function () {
|
||||||
}
|
}
|
||||||
const name = `${toponym} ${formation}`;
|
const name = `${toponym} ${formation}`;
|
||||||
const legend = `The ${name}. Locals claim that it is ${rw(status)}.`;
|
const legend = `The ${name}. Locals claim that it is ${rw(status)}.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listPortals({burgs}) {
|
function listPortals({ burgs }) {
|
||||||
return burgs
|
return burgs
|
||||||
.slice(1, Math.ceil(burgs.length / 10) + 1)
|
.slice(1, Math.ceil(burgs.length / 10) + 1)
|
||||||
.filter(({cell}) => !occupied[cell])
|
.filter(({ cell }) => !occupied[cell])
|
||||||
.map(burg => burg.cell);
|
.map(burg => burg.cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPortal(id, cell) {
|
function addPortal(id, cell) {
|
||||||
const {cells, burgs} = pack;
|
const { cells, burgs } = pack;
|
||||||
|
|
||||||
if (!cells.burg[cell]) return;
|
if (!cells.burg[cell]) return;
|
||||||
const burgName = burgs[cells.burg[cell]].name;
|
const burgName = burgs[cells.burg[cell]].name;
|
||||||
|
|
||||||
const name = `${burgName} Portal`;
|
const name = `${burgName} Portal`;
|
||||||
const legend = `An element of the magic portal system connecting major cities. The portals were installed centuries ago, but still work fine.`;
|
const legend = `An element of the magic portal system connecting major cities. The portals were installed centuries ago, but still work fine.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listRifts({cells}) {
|
function listRifts({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && pack.cells.pop[i] <= 3 && biomesData.habitability[pack.cells.biome[i]]);
|
return cells.i.filter(i => !occupied[i] && pack.cells.pop[i] <= 3 && biomesData.habitability[pack.cells.biome[i]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1225,24 +1224,24 @@ window.Markers = (function () {
|
||||||
const riftType = ra(types);
|
const riftType = ra(types);
|
||||||
const name = `${riftType} Rift`;
|
const name = `${riftType} Rift`;
|
||||||
const legend = `A rumoured ${riftType.toLowerCase()} rift in this area is causing ${ra(descriptions)}.`;
|
const legend = `A rumoured ${riftType.toLowerCase()} rift in this area is causing ${ra(descriptions)}.`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listDisturbedBurial({cells}) {
|
function listDisturbedBurial({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] > 2);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] > 2);
|
||||||
}
|
}
|
||||||
function addDisturbedBurial(id, cell) {
|
function addDisturbedBurial(id, cell) {
|
||||||
const name = "Disturbed Burial";
|
const name = "Disturbed Burial";
|
||||||
const legend = "A burial site has been disturbed in this area, causing the dead to rise and attack the living.";
|
const legend = "A burial site has been disturbed in this area, causing the dead to rise and attack the living.";
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listNecropolis({cells}) {
|
function listNecropolis({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] < 2);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] < 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNecropolis(id, cell) {
|
function addNecropolis(id, cell) {
|
||||||
const {cells} = pack;
|
const { cells } = pack;
|
||||||
|
|
||||||
const toponym = Names.getCulture(cells.culture[cell]);
|
const toponym = Names.getCulture(cells.culture[cell]);
|
||||||
const type = rw({
|
const type = rw({
|
||||||
|
|
@ -1269,10 +1268,10 @@ window.Markers = (function () {
|
||||||
"A foreboding necropolis perched atop a jagged cliff, overlooking a desolate wasteland. Its towering walls harbor restless spirits, and the imposing gates bear the marks of countless battles and ancient curses."
|
"A foreboding necropolis perched atop a jagged cliff, overlooking a desolate wasteland. Its towering walls harbor restless spirits, and the imposing gates bear the marks of countless battles and ancient curses."
|
||||||
]);
|
]);
|
||||||
|
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
function listEncounters({cells}) {
|
function listEncounters({ cells }) {
|
||||||
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] > 1);
|
return cells.i.filter(i => !occupied[i] && cells.h[i] >= 20 && cells.pop[i] > 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1280,8 +1279,8 @@ window.Markers = (function () {
|
||||||
const name = "Random encounter";
|
const name = "Random encounter";
|
||||||
const encounterSeed = cell; // use just cell Id to not overwhelm the Vercel KV database
|
const encounterSeed = cell; // use just cell Id to not overwhelm the Vercel KV database
|
||||||
const legend = `<div>You have encountered a character.</div><iframe src="https://deorum.vercel.app/encounter/${encounterSeed}" width="375" height="600" sandbox="allow-scripts allow-same-origin allow-popups"></iframe>`;
|
const legend = `<div>You have encountered a character.</div><iframe src="https://deorum.vercel.app/encounter/${encounterSeed}" width="375" height="600" sandbox="allow-scripts allow-same-origin allow-popups"></iframe>`;
|
||||||
notes.push({id, name, legend});
|
notes.push({ id, name, legend });
|
||||||
}
|
}
|
||||||
|
|
||||||
return {add, generate, regenerate, getConfig, setConfig, deleteMarker};
|
return { add, generate, regenerate, getConfig, setConfig, deleteMarker };
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ function handleLayersPresetChange(preset) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function savePreset() {
|
function savePreset() {
|
||||||
prompt("Please provide a preset name", {default: ""}, preset => {
|
prompt("Please provide a preset name", { default: "" }, preset => {
|
||||||
presets[preset] = Array.from(byId("mapLayers").querySelectorAll("li:not(.buttonoff)"))
|
presets[preset] = Array.from(byId("mapLayers").querySelectorAll("li:not(.buttonoff)"))
|
||||||
.map(node => node.id)
|
.map(node => node.id)
|
||||||
.sort();
|
.sort();
|
||||||
|
|
@ -258,8 +258,8 @@ function drawBiomes() {
|
||||||
|
|
||||||
const cells = pack.cells;
|
const cells = pack.cells;
|
||||||
const bodyPaths = new Array(biomesData.i.length - 1);
|
const bodyPaths = new Array(biomesData.i.length - 1);
|
||||||
const isolines = getIsolines(pack, cellId => cells.biome[cellId], {fill: true, waterGap: true});
|
const isolines = getIsolines(pack, cellId => cells.biome[cellId], { fill: true, waterGap: true });
|
||||||
Object.entries(isolines).forEach(([index, {fill, waterGap}]) => {
|
Object.entries(isolines).forEach(([index, { fill, waterGap }]) => {
|
||||||
const color = biomesData.color[index];
|
const color = biomesData.color[index];
|
||||||
bodyPaths.push(getGappedFillPaths("biome", fill, waterGap, color, index));
|
bodyPaths.push(getGappedFillPaths("biome", fill, waterGap, color, index));
|
||||||
});
|
});
|
||||||
|
|
@ -288,7 +288,7 @@ function drawPrecipitation() {
|
||||||
TIME && console.time("drawPrecipitation");
|
TIME && console.time("drawPrecipitation");
|
||||||
|
|
||||||
prec.selectAll("circle").remove();
|
prec.selectAll("circle").remove();
|
||||||
const {cells, points} = grid;
|
const { cells, points } = grid;
|
||||||
|
|
||||||
const show = d3.transition().duration(800).ease(d3.easeSinIn);
|
const show = d3.transition().duration(800).ease(d3.easeSinIn);
|
||||||
prec.selectAll("text").attr("opacity", 0).transition(show).attr("opacity", 1);
|
prec.selectAll("text").attr("opacity", 0).transition(show).attr("opacity", 1);
|
||||||
|
|
@ -348,7 +348,7 @@ function togglePopulation(event) {
|
||||||
function drawPopulation() {
|
function drawPopulation() {
|
||||||
population.selectAll("line").remove();
|
population.selectAll("line").remove();
|
||||||
|
|
||||||
const {cells, burgs} = pack;
|
const { cells, burgs } = pack;
|
||||||
const show = d3.transition().duration(2000).ease(d3.easeSinIn);
|
const show = d3.transition().duration(2000).ease(d3.easeSinIn);
|
||||||
|
|
||||||
const rural = Array.from(
|
const rural = Array.from(
|
||||||
|
|
@ -420,8 +420,8 @@ function toggleIce(event) {
|
||||||
function drawIce() {
|
function drawIce() {
|
||||||
TIME && console.time("drawIce");
|
TIME && console.time("drawIce");
|
||||||
|
|
||||||
const {cells, features} = grid;
|
const { cells, features } = grid;
|
||||||
const {temp, h} = cells;
|
const { temp, h } = cells;
|
||||||
Math.random = aleaPRNG(seed);
|
Math.random = aleaPRNG(seed);
|
||||||
|
|
||||||
const ICEBERG_MAX_TEMP = 0;
|
const ICEBERG_MAX_TEMP = 0;
|
||||||
|
|
@ -432,7 +432,7 @@ function drawIce() {
|
||||||
{
|
{
|
||||||
const type = "iceShield";
|
const type = "iceShield";
|
||||||
const getType = cellId => (h[cellId] >= 20 && temp[cellId] <= GLACIER_MAX_TEMP ? type : null);
|
const getType = cellId => (h[cellId] >= 20 && temp[cellId] <= GLACIER_MAX_TEMP ? type : null);
|
||||||
const isolines = getIsolines(grid, getType, {polygons: true});
|
const isolines = getIsolines(grid, getType, { polygons: true });
|
||||||
isolines[type]?.polygons?.forEach(points => {
|
isolines[type]?.polygons?.forEach(points => {
|
||||||
const clipped = clipPoly(points);
|
const clipped = clipPoly(points);
|
||||||
ice.append("polygon").attr("points", clipped).attr("type", type);
|
ice.append("polygon").attr("points", clipped).attr("type", type);
|
||||||
|
|
@ -476,11 +476,11 @@ function toggleCultures(event) {
|
||||||
|
|
||||||
function drawCultures() {
|
function drawCultures() {
|
||||||
TIME && console.time("drawCultures");
|
TIME && console.time("drawCultures");
|
||||||
const {cells, cultures} = pack;
|
const { cells, cultures } = pack;
|
||||||
|
|
||||||
const bodyPaths = new Array(cultures.length - 1);
|
const bodyPaths = new Array(cultures.length - 1);
|
||||||
const isolines = getIsolines(pack, cellId => cells.culture[cellId], {fill: true, waterGap: true});
|
const isolines = getIsolines(pack, cellId => cells.culture[cellId], { fill: true, waterGap: true });
|
||||||
Object.entries(isolines).forEach(([index, {fill, waterGap}]) => {
|
Object.entries(isolines).forEach(([index, { fill, waterGap }]) => {
|
||||||
const color = cultures[index].color;
|
const color = cultures[index].color;
|
||||||
bodyPaths.push(getGappedFillPaths("culture", fill, waterGap, color, index));
|
bodyPaths.push(getGappedFillPaths("culture", fill, waterGap, color, index));
|
||||||
});
|
});
|
||||||
|
|
@ -505,11 +505,11 @@ function toggleReligions(event) {
|
||||||
|
|
||||||
function drawReligions() {
|
function drawReligions() {
|
||||||
TIME && console.time("drawReligions");
|
TIME && console.time("drawReligions");
|
||||||
const {cells, religions} = pack;
|
const { cells, religions } = pack;
|
||||||
|
|
||||||
const bodyPaths = new Array(religions.length - 1);
|
const bodyPaths = new Array(religions.length - 1);
|
||||||
const isolines = getIsolines(pack, cellId => cells.religion[cellId], {fill: true, waterGap: true});
|
const isolines = getIsolines(pack, cellId => cells.religion[cellId], { fill: true, waterGap: true });
|
||||||
Object.entries(isolines).forEach(([index, {fill, waterGap}]) => {
|
Object.entries(isolines).forEach(([index, { fill, waterGap }]) => {
|
||||||
const color = religions[index].color;
|
const color = religions[index].color;
|
||||||
bodyPaths.push(getGappedFillPaths("religion", fill, waterGap, color, index));
|
bodyPaths.push(getGappedFillPaths("religion", fill, waterGap, color, index));
|
||||||
});
|
});
|
||||||
|
|
@ -533,7 +533,7 @@ function toggleStates(event) {
|
||||||
|
|
||||||
function drawStates() {
|
function drawStates() {
|
||||||
TIME && console.time("drawStates");
|
TIME && console.time("drawStates");
|
||||||
const {cells, states} = pack;
|
const { cells, states } = pack;
|
||||||
|
|
||||||
const maxLength = states.length - 1;
|
const maxLength = states.length - 1;
|
||||||
const bodyPaths = new Array(maxLength);
|
const bodyPaths = new Array(maxLength);
|
||||||
|
|
@ -541,8 +541,8 @@ function drawStates() {
|
||||||
const haloPaths = new Array(maxLength);
|
const haloPaths = new Array(maxLength);
|
||||||
|
|
||||||
const renderHalo = shapeRendering.value === "geometricPrecision";
|
const renderHalo = shapeRendering.value === "geometricPrecision";
|
||||||
const isolines = getIsolines(pack, cellId => cells.state[cellId], {fill: true, waterGap: true, halo: renderHalo});
|
const isolines = getIsolines(pack, cellId => cells.state[cellId], { fill: true, waterGap: true, halo: renderHalo });
|
||||||
Object.entries(isolines).forEach(([index, {fill, waterGap, halo}]) => {
|
Object.entries(isolines).forEach(([index, { fill, waterGap, halo }]) => {
|
||||||
const color = states[index].color;
|
const color = states[index].color;
|
||||||
bodyPaths.push(getGappedFillPaths("state", fill, waterGap, color, index));
|
bodyPaths.push(getGappedFillPaths("state", fill, waterGap, color, index));
|
||||||
|
|
||||||
|
|
@ -588,11 +588,11 @@ function toggleProvinces(event) {
|
||||||
|
|
||||||
function drawProvinces() {
|
function drawProvinces() {
|
||||||
TIME && console.time("drawProvinces");
|
TIME && console.time("drawProvinces");
|
||||||
const {cells, provinces} = pack;
|
const { cells, provinces } = pack;
|
||||||
|
|
||||||
const bodyPaths = new Array(provinces.length - 1);
|
const bodyPaths = new Array(provinces.length - 1);
|
||||||
const isolines = getIsolines(pack, cellId => cells.province[cellId], {fill: true, waterGap: true});
|
const isolines = getIsolines(pack, cellId => cells.province[cellId], { fill: true, waterGap: true });
|
||||||
Object.entries(isolines).forEach(([index, {fill, waterGap}]) => {
|
Object.entries(isolines).forEach(([index, { fill, waterGap }]) => {
|
||||||
const color = provinces[index].color;
|
const color = provinces[index].color;
|
||||||
bodyPaths.push(getGappedFillPaths("province", fill, waterGap, color, index));
|
bodyPaths.push(getGappedFillPaths("province", fill, waterGap, color, index));
|
||||||
});
|
});
|
||||||
|
|
@ -653,8 +653,134 @@ function drawGrid() {
|
||||||
.attr("height", maxHeight)
|
.attr("height", maxHeight)
|
||||||
.attr("fill", "url(" + pattern + ")")
|
.attr("fill", "url(" + pattern + ")")
|
||||||
.attr("stroke", "none");
|
.attr("stroke", "none");
|
||||||
|
|
||||||
|
// Add grid numbering if enabled
|
||||||
|
const showNumbers = gridOverlay.attr("data-show-numbers") === "1";
|
||||||
|
if (showNumbers) {
|
||||||
|
drawGridNumbers(maxWidth, maxHeight, scale, dx, dy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawGridNumbers(maxWidth, maxHeight, scale, dx, dy) {
|
||||||
|
const gridType = gridOverlay.attr("type") || "pointyHex";
|
||||||
|
const fontSize = gridOverlay.attr("data-number-size") || 8;
|
||||||
|
const numberColor = gridOverlay.attr("data-number-color") || "#808080";
|
||||||
|
|
||||||
|
// Get cell dimensions based on grid type
|
||||||
|
const cellDimensions = getGridCellDimensions(gridType);
|
||||||
|
const cellWidth = cellDimensions.width * scale;
|
||||||
|
const cellHeight = cellDimensions.height * scale;
|
||||||
|
|
||||||
|
// Calculate grid dimensions based on ACTUAL spacing used
|
||||||
|
const rowSpacing = cellHeight * 0.5; // Same as used in getGridCellCenter
|
||||||
|
const cols = Math.ceil(maxWidth / cellWidth) + 2; // Add extra to cover edges
|
||||||
|
const rows = Math.ceil(maxHeight / rowSpacing) + 2; // Use rowSpacing, not cellHeight
|
||||||
|
|
||||||
|
// Create numbers group
|
||||||
|
const numbersGroup = gridOverlay.append("g").attr("id", "gridNumbers");
|
||||||
|
|
||||||
|
let counter = 1;
|
||||||
|
// Generate grid numbers for ALL cells (positioning is now perfect)
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const position = getGridCellCenter(gridType, col, row, cellWidth, cellHeight, dx, dy);
|
||||||
|
|
||||||
|
numbersGroup
|
||||||
|
.append("text")
|
||||||
|
.attr("x", position.x)
|
||||||
|
.attr("y", position.y)
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.attr("dominant-baseline", "middle")
|
||||||
|
.attr("font-size", fontSize)
|
||||||
|
.attr("fill", numberColor)
|
||||||
|
.attr("pointer-events", "none")
|
||||||
|
.text(String(counter).padStart(4, "0"));
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGridCellDimensions(gridType) {
|
||||||
|
// Base dimensions from pattern definitions
|
||||||
|
switch (gridType) {
|
||||||
|
case "square":
|
||||||
|
return { width: 25, height: 25 };
|
||||||
|
case "pointyHex":
|
||||||
|
return { width: 25, height: 43.4 };
|
||||||
|
case "flatHex":
|
||||||
|
return { width: 43.4, height: 25 };
|
||||||
|
case "square45deg":
|
||||||
|
return { width: 35.355, height: 35.355 };
|
||||||
|
case "squareTruncated":
|
||||||
|
case "squareTetrakis":
|
||||||
|
return { width: 25, height: 25 };
|
||||||
|
case "triangleHorizontal":
|
||||||
|
return { width: 41.76, height: 72.33 };
|
||||||
|
case "triangleVertical":
|
||||||
|
return { width: 72.33, height: 41.76 };
|
||||||
|
case "trihexagonal":
|
||||||
|
return { width: 25, height: 43.4 };
|
||||||
|
case "rhombille":
|
||||||
|
return { width: 82.5, height: 50 };
|
||||||
|
default:
|
||||||
|
return { width: 25, height: 43.4 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGridCellCenter(gridType, col, row, cellWidth, cellHeight, dx, dy) {
|
||||||
|
let x, y;
|
||||||
|
|
||||||
|
if (gridType === "pointyHex") {
|
||||||
|
// Pointy hex pattern: width=25, height=43.4
|
||||||
|
// Hexagons interlock: each row is spaced at 3/4 height
|
||||||
|
// Based on user's marker placement, need to adjust Y-center
|
||||||
|
|
||||||
|
// Vertical spacing adjusting based on visual alignment (not pure geometry)
|
||||||
|
const rowSpacing = cellHeight * 0.5;
|
||||||
|
|
||||||
|
x = col * cellWidth;
|
||||||
|
y = row * rowSpacing;
|
||||||
|
|
||||||
|
// Every other row (EVEN rows: 0, 2, 4...) is offset horizontally
|
||||||
|
if (row % 2 === 0) {
|
||||||
|
x += cellWidth / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center the number in the hexagon
|
||||||
|
x += cellWidth / 2;
|
||||||
|
// Top row is perfect at 0.35
|
||||||
|
y += cellHeight * 0.35;
|
||||||
|
|
||||||
|
} else if (gridType === "flatHex") {
|
||||||
|
// Flat hex grid: hexagons with flat sides up/down
|
||||||
|
// Columns are horizontally compressed (overlap by 25%)
|
||||||
|
x = col * (cellWidth * 0.75);
|
||||||
|
y = row * cellHeight;
|
||||||
|
|
||||||
|
// Every other column is offset vertically by half height
|
||||||
|
if (col % 2 === 1) {
|
||||||
|
y += cellHeight / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center the number in the hexagon
|
||||||
|
x += cellWidth / 2;
|
||||||
|
y += cellHeight / 2;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Square and other regular grids - simple grid
|
||||||
|
x = col * cellWidth + cellWidth / 2;
|
||||||
|
y = row * cellHeight + cellHeight / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply shift offsets
|
||||||
|
x += parseFloat(dx) || 0;
|
||||||
|
y += parseFloat(dy) || 0;
|
||||||
|
|
||||||
|
return { x: rn(x, 2), y: rn(y, 2) };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function toggleCoordinates(event) {
|
function toggleCoordinates(event) {
|
||||||
if (!coordinates.selectAll("*").size()) {
|
if (!coordinates.selectAll("*").size()) {
|
||||||
turnButtonOn("toggleCoordinates");
|
turnButtonOn("toggleCoordinates");
|
||||||
|
|
@ -711,7 +837,7 @@ function drawCoordinates() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {x, y, text};
|
return { x, y, text };
|
||||||
});
|
});
|
||||||
|
|
||||||
const path = round(d3.geoPath(projection)(graticule()));
|
const path = round(d3.geoPath(projection)(graticule()));
|
||||||
|
|
@ -795,7 +921,7 @@ function drawRivers() {
|
||||||
TIME && console.time("drawRivers");
|
TIME && console.time("drawRivers");
|
||||||
rivers.selectAll("*").remove();
|
rivers.selectAll("*").remove();
|
||||||
|
|
||||||
const riverPaths = pack.rivers.map(({cells, points, i, widthFactor, sourceWidth}) => {
|
const riverPaths = pack.rivers.map(({ cells, points, i, widthFactor, sourceWidth }) => {
|
||||||
if (!cells || cells.length < 2) return;
|
if (!cells || cells.length < 2) return;
|
||||||
|
|
||||||
if (points && points.length !== cells.length) {
|
if (points && points.length !== cells.length) {
|
||||||
|
|
@ -831,7 +957,7 @@ function drawRoutes() {
|
||||||
const routePaths = {};
|
const routePaths = {};
|
||||||
|
|
||||||
for (const route of pack.routes) {
|
for (const route of pack.routes) {
|
||||||
const {i, group, points} = route;
|
const { i, group, points } = route;
|
||||||
if (!points || points.length < 2) continue;
|
if (!points || points.length < 2) continue;
|
||||||
if (!routePaths[group]) routePaths[group] = [];
|
if (!routePaths[group]) routePaths[group] = [];
|
||||||
routePaths[group].push(`<path id="route${i}" d="${Routes.getPath(route)}"/>`);
|
routePaths[group].push(`<path id="route${i}" d="${Routes.getPath(route)}"/>`);
|
||||||
|
|
@ -943,12 +1069,12 @@ function drawZones() {
|
||||||
const filterBy = byId("zonesFilterType").value;
|
const filterBy = byId("zonesFilterType").value;
|
||||||
const isFiltered = filterBy && filterBy !== "all";
|
const isFiltered = filterBy && filterBy !== "all";
|
||||||
const visibleZones = pack.zones.filter(
|
const visibleZones = pack.zones.filter(
|
||||||
({hidden, cells, type}) => !hidden && cells.length && (!isFiltered || type === filterBy)
|
({ hidden, cells, type }) => !hidden && cells.length && (!isFiltered || type === filterBy)
|
||||||
);
|
);
|
||||||
zones.html(visibleZones.map(drawZone).join(""));
|
zones.html(visibleZones.map(drawZone).join(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawZone({i, cells, type, color}) {
|
function drawZone({ i, cells, type, color }) {
|
||||||
const path = getVertexPath(cells);
|
const path = getVertexPath(cells);
|
||||||
return `<path id="zone${i}" data-id="${i}" data-type="${type}" d="${path}" fill="${color}" />`;
|
return `<path id="zone${i}" data-id="${i}" data-type="${type}" d="${path}" fill="${color}" />`;
|
||||||
}
|
}
|
||||||
|
|
@ -1002,7 +1128,7 @@ function turnButtonOn(el) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// move layers on mapLayers dragging (jquery sortable)
|
// move layers on mapLayers dragging (jquery sortable)
|
||||||
$("#mapLayers").sortable({items: "li:not(.solid)", containment: "parent", cancel: ".solid", update: moveLayer});
|
$("#mapLayers").sortable({ items: "li:not(.solid)", containment: "parent", cancel: ".solid", update: moveLayer });
|
||||||
function moveLayer(event, ui) {
|
function moveLayer(event, ui) {
|
||||||
const el = getLayer(ui.item.attr("id"));
|
const el = getLayer(ui.item.attr("id"));
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
|
||||||
1100
modules/ui/layers.js.bak_sed
Normal file
1100
modules/ui/layers.js.bak_sed
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -209,6 +209,9 @@ function selectStyleElement() {
|
||||||
styleGridScale.value = el.attr("scale") || 1;
|
styleGridScale.value = el.attr("scale") || 1;
|
||||||
styleGridShiftX.value = el.attr("dx") || 0;
|
styleGridShiftX.value = el.attr("dx") || 0;
|
||||||
styleGridShiftY.value = el.attr("dy") || 0;
|
styleGridShiftY.value = el.attr("dy") || 0;
|
||||||
|
styleGridShowNumbers.checked = el.attr("data-show-numbers") === "1";
|
||||||
|
styleGridNumberSize.value = el.attr("data-number-size") || 8;
|
||||||
|
styleGridNumberColor.value = styleGridNumberColorOutput.value = el.attr("data-number-color") || "#808080";
|
||||||
calculateFriendlyGridSize();
|
calculateFriendlyGridSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -515,6 +518,12 @@ styleGridType.on("change", function () {
|
||||||
calculateFriendlyGridSize();
|
calculateFriendlyGridSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Grid numbering UI elements
|
||||||
|
const styleGridShowNumbers = byId("styleGridShowNumbers");
|
||||||
|
const styleGridNumberSize = byId("styleGridNumberSize");
|
||||||
|
const styleGridNumberColor = byId("styleGridNumberColor");
|
||||||
|
const styleGridNumberColorOutput = byId("styleGridNumberColorOutput");
|
||||||
|
|
||||||
styleGridScale.on("input", function () {
|
styleGridScale.on("input", function () {
|
||||||
getEl().attr("scale", this.value);
|
getEl().attr("scale", this.value);
|
||||||
if (layerIsOn("toggleGrid")) drawGrid();
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
|
|
@ -537,6 +546,23 @@ styleGridShiftY.on("input", function () {
|
||||||
if (layerIsOn("toggleGrid")) drawGrid();
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
styleGridShowNumbers.on("change", function () {
|
||||||
|
getEl().attr("data-show-numbers", this.checked ? "1" : "0");
|
||||||
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
|
});
|
||||||
|
|
||||||
|
styleGridNumberSize.on("input", function () {
|
||||||
|
getEl().attr("data-number-size", this.value);
|
||||||
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
|
});
|
||||||
|
|
||||||
|
styleGridNumberColor.on("input", function () {
|
||||||
|
styleGridNumberColorOutput.value = this.value;
|
||||||
|
getEl().attr("data-number-color", this.value);
|
||||||
|
if (layerIsOn("toggleGrid")) drawGrid();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
styleRescaleMarkers.on("change", function () {
|
styleRescaleMarkers.on("change", function () {
|
||||||
markers.attr("rescale", +this.checked);
|
markers.attr("rescale", +this.checked);
|
||||||
invokeActiveZooming();
|
invokeActiveZooming();
|
||||||
|
|
@ -626,36 +652,36 @@ openCreateHeightmapSchemeButton.on("click", function () {
|
||||||
|
|
||||||
Array.from(container.querySelectorAll("input.stop")).forEach(
|
Array.from(container.querySelectorAll("input.stop")).forEach(
|
||||||
(input, index) =>
|
(input, index) =>
|
||||||
(input.oninput = function () {
|
(input.oninput = function () {
|
||||||
stops[index] = this.value;
|
stops[index] = this.value;
|
||||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||||
renderPreview();
|
renderPreview();
|
||||||
renderGradient();
|
renderGradient();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
Array.from(container.querySelectorAll("button.remove")).forEach(
|
Array.from(container.querySelectorAll("button.remove")).forEach(
|
||||||
button =>
|
button =>
|
||||||
(button.onclick = function () {
|
(button.onclick = function () {
|
||||||
const index = +this.dataset.index;
|
const index = +this.dataset.index;
|
||||||
stops.splice(index, 1);
|
stops.splice(index, 1);
|
||||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||||
renderPreview();
|
renderPreview();
|
||||||
renderStops();
|
renderStops();
|
||||||
renderGradient();
|
renderGradient();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
Array.from(container.querySelectorAll("button.add")).forEach(
|
Array.from(container.querySelectorAll("button.add")).forEach(
|
||||||
(button, index) =>
|
(button, index) =>
|
||||||
(button.onclick = function () {
|
(button.onclick = function () {
|
||||||
const middleColor = d3.interpolateRgb(stops[index], stops[index + 1])(0.5);
|
const middleColor = d3.interpolateRgb(stops[index], stops[index + 1])(0.5);
|
||||||
stops.splice(index + 1, 0, toHEX(middleColor));
|
stops.splice(index + 1, 0, toHEX(middleColor));
|
||||||
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
openCreateHeightmapSchemeButton.dataset.stops = stops.join(",");
|
||||||
renderPreview();
|
renderPreview();
|
||||||
renderStops();
|
renderStops();
|
||||||
renderGradient();
|
renderGradient();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -687,7 +713,7 @@ openCreateHeightmapSchemeButton.on("click", function () {
|
||||||
Create: handleCreate,
|
Create: handleCreate,
|
||||||
Cancel: handleClose
|
Cancel: handleClose
|
||||||
},
|
},
|
||||||
position: {my: "center top+150", at: "center top", of: "svg"}
|
position: { my: "center top+150", at: "center top", of: "svg" }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -800,7 +826,7 @@ styleFontAdd.on("click", function () {
|
||||||
$("#addFontDialog").dialog({
|
$("#addFontDialog").dialog({
|
||||||
title: "Add custom font",
|
title: "Add custom font",
|
||||||
width: "26em",
|
width: "26em",
|
||||||
position: {my: "center", at: "center", of: "svg"},
|
position: { my: "center", at: "center", of: "svg" },
|
||||||
buttons: {
|
buttons: {
|
||||||
Add: function () {
|
Add: function () {
|
||||||
const family = addFontNameInput.value;
|
const family = addFontNameInput.value;
|
||||||
|
|
@ -1098,7 +1124,7 @@ styleScaleBar.on("input", function (event) {
|
||||||
const scaleBarBack = scaleBar.select("#scaleBarBack");
|
const scaleBarBack = scaleBar.select("#scaleBarBack");
|
||||||
if (!scaleBarBack.size()) return;
|
if (!scaleBarBack.size()) return;
|
||||||
|
|
||||||
const {id, value} = event.target;
|
const { id, value } = event.target;
|
||||||
|
|
||||||
if (id === "styleScaleBarSize") scaleBar.attr("data-bar-size", value);
|
if (id === "styleScaleBarSize") scaleBar.attr("data-bar-size", value);
|
||||||
else if (id === "styleScaleBarFontSize") scaleBar.attr("font-size", value);
|
else if (id === "styleScaleBarFontSize") scaleBar.attr("font-size", value);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue