mirror of
https://github.com/louis-e/arnis.git
synced 2026-02-02 18:34:12 -05:00
Compare commits
29 Commits
v2.1.1-twe
...
v2.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96cbf2ac96 | ||
|
|
feab8cea98 | ||
|
|
8128270a57 | ||
|
|
8ab5745efa | ||
|
|
8291a94e13 | ||
|
|
8d5d96f88a | ||
|
|
5008ddd988 | ||
|
|
4a200e01f8 | ||
|
|
eb9e349cdb | ||
|
|
4ccb8a492b | ||
|
|
a7a74fecdb | ||
|
|
364e4933ac | ||
|
|
b7db71209e | ||
|
|
ae4c8371ae | ||
|
|
14aa8e94d5 | ||
|
|
8f739e0189 | ||
|
|
c9b2899c83 | ||
|
|
155e3e5a9b | ||
|
|
d364ed1361 | ||
|
|
2f2d1c79bb | ||
|
|
54313019af | ||
|
|
1bd3fb6802 | ||
|
|
5cbeeb0e01 | ||
|
|
55b522764d | ||
|
|
e2fd24ba08 | ||
|
|
b095c40285 | ||
|
|
7c211938f7 | ||
|
|
3aa66407be | ||
|
|
18f1239215 |
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -11,13 +11,13 @@ assignees: ''
|
||||
A clear and concise description of what the bug is and what you expected to happen.
|
||||
|
||||
**Used bbox area**
|
||||
Please provide your input parameters so we can reproduce the issue.
|
||||
Please provide your input parameters so we can reproduce the issue. *(For example: 48.133444 11.569462 48.142609 11.584740)*
|
||||
|
||||
**Used Minecraft version**
|
||||
Please provide the Minecraft version you used.
|
||||
**Arnis and Minecraft version**
|
||||
Please tell us what version of Arnis and Minecraft you used.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here. If you used any more custom parameters, please provide them here too.
|
||||
Add any other context about the problem here. If you used any more custom settings, please provide them here too.
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,9 +7,6 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
|
||||
# Lock files
|
||||
Cargo.lock
|
||||
|
||||
# IDE/editor files
|
||||
.idea/
|
||||
/.vscode/
|
||||
|
||||
6149
Cargo.lock
generated
Normal file
6149
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "arnis"
|
||||
version = "2.1.1"
|
||||
version = "2.1.2"
|
||||
edition = "2021"
|
||||
description = "Arnis - Generate real life cities in Minecraft"
|
||||
homepage = "https://github.com/louis-e/arnis"
|
||||
@@ -17,15 +17,15 @@ tauri-build = { version = "2", features = [] }
|
||||
[dependencies]
|
||||
clap = { version = "4.1", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
dirs = "4.0.0"
|
||||
dirs = "5.0.1"
|
||||
fastanvil = "0.31.0"
|
||||
fastnbt = "2.5.0"
|
||||
flate2 = "1.0"
|
||||
fnv = "1.0.7"
|
||||
fs2 = "0.4"
|
||||
geo = "0.28.0"
|
||||
geo = "0.29.3"
|
||||
indicatif = "0.17.8"
|
||||
itertools = "0.13.0"
|
||||
itertools = "0.14.0"
|
||||
nalgebra = "0.33.0"
|
||||
once_cell = "1.19.0"
|
||||
rand = "0.8.5"
|
||||
|
||||
28
README.md
28
README.md
@@ -18,7 +18,7 @@ Arnis is designed to handle large-scale data and generate rich, immersive enviro
|
||||
Download the [latest release](https://github.com/louis-e/arnis/releases/) or [compile](#trophy-open-source) the project on your own.
|
||||
|
||||
Choose your area in Arnis using the rectangle tool and select your Minecraft world - then simply click on 'Start Generation'!
|
||||
The world will always be generated starting from the Minecraft coordinates 0 0 0 (/tp 0 0 0).
|
||||
The world will always be generated starting from the Minecraft coordinates 0 0 0 (/tp 0 0 0). This is the top left of your selected area.
|
||||
|
||||
If you choose to select an own world, make sure to generate a new flat world in advance in Minecraft.
|
||||
|
||||
@@ -48,6 +48,8 @@ The script uses the [fastnbt](https://github.com/owengage/fastnbt) cargo package
|
||||
The project is named after the smallest city in Germany, Arnis[^2]. The city's small size made it an ideal test case for developing and debugging the algorithm efficiently.
|
||||
- *I don't have Minecraft installed but want to generate a world for my kids. How?*<br>
|
||||
When selecting a world, click on 'Select existing world' and choose a directory. The world will be generated there.
|
||||
- *Arnis instantly closes again or the window is empty!*<br>
|
||||
If you're on Windows, please install the [Evergreen Bootstrapper from Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/?form=MA13LH#download).
|
||||
- *What Minecraft version should I use?*<br>
|
||||
Please use Minecraft version 1.21.4 for the best results. Minecraft version 1.16.5 and below is currently not supported, but we are working on it!
|
||||
- *The generation did finish, but there's nothing in the world!*<br>
|
||||
@@ -56,23 +58,26 @@ Make sure to teleport to the generation starting point (/tp 0 0 0). If there is
|
||||
## :memo: ToDo and Known Bugs
|
||||
Feel free to choose an item from the To-Do or Known Bugs list, or bring your own idea to the table. Bug reports shall be raised as a Github issue. Contributions are highly welcome and appreciated!
|
||||
- [ ] Fix compilation for Linux
|
||||
- [ ] Support multipolygons
|
||||
- [ ] Add street names as signs
|
||||
- [ ] Add support for older Minecraft versions (<=1.16.5)
|
||||
- [ ] Mapping real coordinates to Minecraft coordinates (https://github.com/louis-e/arnis/issues/29)
|
||||
- [ ] Rotate maps (https://github.com/louis-e/arnis/issues/97)
|
||||
- [ ] Add street names as signs
|
||||
- [ ] Add support for older Minecraft versions (<=1.16.5) (https://github.com/louis-e/arnis/issues/124, https://github.com/louis-e/arnis/issues/137)
|
||||
- [ ] Mapping real coordinates to Minecraft coordinates (https://github.com/louis-e/arnis/issues/29)
|
||||
- [ ] Add interior to buildings
|
||||
- [ ] Implement house roof types
|
||||
- [ ] Evaluate and implement elevation (https://github.com/louis-e/arnis/issues/66)
|
||||
- [ ] Add support for inner attribute in multipolygons and multipolygon elements other than buildings
|
||||
- [ ] Fix Github Action Workflow for releasing Linux & MacOS Binary
|
||||
- [ ] Evaluate and implement faster region saving
|
||||
- [ ] Automatic new world creation instead of using an existing world
|
||||
- [ ] Implement house roof types
|
||||
- [ ] Refactor bridges implementation
|
||||
- [ ] Refactor railway implementation
|
||||
- [ ] Better code documentation
|
||||
- [ ] Refactor fountain structure implementation
|
||||
- [ ] Add interior to buildings
|
||||
- [ ] Luanti Support (https://github.com/louis-e/arnis/issues/120)
|
||||
- [ ] Minecraft Bedrock Edition Support (https://github.com/louis-e/arnis/issues/148)
|
||||
- [x] Support multipolygons (https://github.com/louis-e/arnis/issues/112, https://github.com/louis-e/arnis/issues/114)
|
||||
- [x] Memory optimization
|
||||
- [x] Design and implement a GUI
|
||||
- [x] Automatic new world creation instead of using an existing world
|
||||
- [x] Fix faulty empty chunks ([https://github.com/owengage/fastnbt/issues/120](https://github.com/owengage/fastnbt/issues/120)) (workaround found)
|
||||
- [x] Setup fork of [https://github.com/aaronr/bboxfinder.com](https://github.com/aaronr/bboxfinder.com) for easy bbox picking
|
||||
|
||||
@@ -92,13 +97,6 @@ For the GUI: ```cargo run --release```<br>
|
||||
|
||||
After your pull request was merged, I will take care of regularly creating update releases which will include your changes.
|
||||
|
||||
#### Contributors:
|
||||
This section is dedicated to recognizing and celebrating the outstanding contributions of individuals who have significantly enhanced this project. Your work and dedication are deeply appreciated!
|
||||
- louis-e
|
||||
- scd31
|
||||
- vfosnar
|
||||
|
||||
|
||||
## :star: Star History
|
||||
|
||||
<a href="https://star-history.com/#louis-e/arnis&Date">
|
||||
|
||||
2
gui-src/index.html
vendored
2
gui-src/index.html
vendored
@@ -97,7 +97,7 @@
|
||||
<!-- World Scale Slider -->
|
||||
<div class="scale-slider-container">
|
||||
<label for="scale-value-slider">World Scale:</label>
|
||||
<input type="range" id="scale-value-slider" name="scale-value-slider" min="0.50" max="2.5" step="0.25" value="1">
|
||||
<input type="range" id="scale-value-slider" name="scale-value-slider" min="0.30" max="2.5" step="0.1" value="1">
|
||||
<span id="slider-value">1.00</span>
|
||||
</div>
|
||||
|
||||
|
||||
3
gui-src/js/main.js
vendored
3
gui-src/js/main.js
vendored
@@ -270,6 +270,7 @@ async function selectWorld(generate_new_world) {
|
||||
document.getElementById('selected-world').style.color = "#fecc44";
|
||||
}
|
||||
} catch (error) {
|
||||
worldPath = error;
|
||||
console.error(error);
|
||||
document.getElementById('selected-world').textContent = error;
|
||||
document.getElementById('selected-world').style.color = "#fa7878";
|
||||
@@ -295,7 +296,7 @@ async function startGeneration() {
|
||||
worldPath === "No world selected" ||
|
||||
worldPath == "Invalid Minecraft world" ||
|
||||
worldPath == "The selected world is currently in use" ||
|
||||
worldPath == "Minecraft directory not found." ||
|
||||
worldPath == "Minecraft directory not found" ||
|
||||
worldPath === ""
|
||||
) {
|
||||
document.getElementById('selected-world').textContent = "Select a Minecraft world first!";
|
||||
|
||||
@@ -105,7 +105,14 @@ pub fn generate_world(
|
||||
}
|
||||
}
|
||||
ProcessedElement::Relation(rel) => {
|
||||
if rel.tags.contains_key("water") {
|
||||
if rel.tags.contains_key("building") || rel.tags.contains_key("building:part") {
|
||||
buildings::generate_building_from_relation(
|
||||
&mut editor,
|
||||
rel,
|
||||
ground_level,
|
||||
args,
|
||||
);
|
||||
} else if rel.tags.contains_key("water") {
|
||||
water_areas::generate_water_areas(&mut editor, rel, ground_level);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
use crate::colors::{color_text_to_rgb_tuple, rgb_distance, RGBTuple};
|
||||
use crate::floodfill::flood_fill_area;
|
||||
use crate::osm_parser::ProcessedWay;
|
||||
use crate::osm_parser::{ProcessedMemberRole, ProcessedRelation, ProcessedWay};
|
||||
use crate::world_editor::WorldEditor;
|
||||
use rand::Rng;
|
||||
use std::collections::HashSet;
|
||||
@@ -80,6 +80,39 @@ pub fn generate_buildings(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(amenity_type) = element.tags.get("amenity") {
|
||||
if amenity_type == "shelter" {
|
||||
let roof_block: Block = STONE_BRICK_SLAB;
|
||||
|
||||
let polygon_coords: Vec<(i32, i32)> = element
|
||||
.nodes
|
||||
.iter()
|
||||
.map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z))
|
||||
.collect();
|
||||
let roof_area: Vec<(i32, i32)> =
|
||||
flood_fill_area(&polygon_coords, args.timeout.as_ref());
|
||||
|
||||
// Place fences and roof slabs at each corner node directly
|
||||
for node in &element.nodes {
|
||||
let x: i32 = node.x;
|
||||
let z: i32 = node.z;
|
||||
|
||||
for y in 1..=4 {
|
||||
editor.set_block(OAK_FENCE, x, ground_level + y, z, None, None);
|
||||
}
|
||||
editor.set_block(roof_block, x, ground_level + 5, z, None, None);
|
||||
}
|
||||
|
||||
// Flood fill the roof area
|
||||
let roof_height: i32 = ground_level + 5;
|
||||
for (x, z) in roof_area.iter() {
|
||||
editor.set_block(roof_block, *x, roof_height, *z, None, None);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(building_type) = element.tags.get("building") {
|
||||
if building_type == "garage" {
|
||||
building_height = 2;
|
||||
@@ -284,6 +317,35 @@ pub fn generate_buildings(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_building_from_relation(
|
||||
editor: &mut WorldEditor,
|
||||
relation: &ProcessedRelation,
|
||||
ground_level: i32,
|
||||
args: &Args,
|
||||
) {
|
||||
// Process the outer way to create the building walls
|
||||
for member in &relation.members {
|
||||
if member.role == ProcessedMemberRole::Outer {
|
||||
generate_buildings(editor, &member.way, ground_level, args);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle inner ways (holes, courtyards, etc.)
|
||||
for member in &relation.members {
|
||||
if member.role == ProcessedMemberRole::Inner {
|
||||
let polygon_coords: Vec<(i32, i32)> =
|
||||
member.way.nodes.iter().map(|n| (n.x, n.z)).collect();
|
||||
let hole_area: Vec<(i32, i32)> =
|
||||
flood_fill_area(&polygon_coords, args.timeout.as_ref());
|
||||
|
||||
for (x, z) in hole_area {
|
||||
// Remove blocks in the inner area to create a hole
|
||||
editor.set_block(AIR, x, ground_level, z, None, Some(&[SPONGE]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_nearest_block_in_color_map(
|
||||
rgb: &RGBTuple,
|
||||
color_map: Vec<(RGBTuple, Block)>,
|
||||
|
||||
114
src/main.rs
114
src/main.rs
@@ -161,27 +161,13 @@ fn gui_select_world(generate_new: bool) -> Result<String, String> {
|
||||
// Handle new world generation
|
||||
if let Some(default_path) = &default_dir {
|
||||
if default_path.exists() {
|
||||
// Generate a unique world name
|
||||
let mut counter: i32 = 1;
|
||||
let unique_name: String = loop {
|
||||
let candidate_name: String = format!("Arnis World {}", counter);
|
||||
let candidate_path: PathBuf = default_path.join(&candidate_name);
|
||||
if !candidate_path.exists() {
|
||||
break candidate_name;
|
||||
}
|
||||
counter += 1;
|
||||
};
|
||||
|
||||
let new_world_path: PathBuf = default_path.join(&unique_name);
|
||||
|
||||
// Create the new world structure
|
||||
create_new_world(&new_world_path, &unique_name)?;
|
||||
Ok(new_world_path.display().to_string())
|
||||
// Call create_new_world and return the result
|
||||
create_new_world(default_path)
|
||||
} else {
|
||||
Err("Minecraft directory not found.".to_string())
|
||||
Err("Minecraft directory not found".to_string())
|
||||
}
|
||||
} else {
|
||||
Err("Minecraft directory not found.".to_string())
|
||||
Err("Minecraft directory not found".to_string())
|
||||
}
|
||||
} else {
|
||||
// Handle existing world selection
|
||||
@@ -212,24 +198,8 @@ fn gui_select_world(generate_new: bool) -> Result<String, String> {
|
||||
|
||||
return Ok(path.display().to_string());
|
||||
} else {
|
||||
// No Minecraft directory found, generating world in custom user selected directory
|
||||
|
||||
// Generate a unique world name
|
||||
let mut counter: i32 = 1;
|
||||
let unique_name: String = loop {
|
||||
let candidate_name: String = format!("Arnis World {}", counter);
|
||||
let candidate_path: PathBuf = path.join(&candidate_name);
|
||||
if !candidate_path.exists() {
|
||||
break candidate_name;
|
||||
}
|
||||
counter += 1;
|
||||
};
|
||||
|
||||
let new_world_path: PathBuf = path.join(&unique_name);
|
||||
|
||||
// Create the new world structure
|
||||
create_new_world(&new_world_path, &unique_name)?;
|
||||
return Ok(new_world_path.display().to_string());
|
||||
// No Minecraft directory found, generating new world in custom user selected directory
|
||||
return create_new_world(&path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,76 +208,82 @@ fn gui_select_world(generate_new: bool) -> Result<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_new_world(world_path: &Path, world_name: &str) -> Result<(), String> {
|
||||
fn create_new_world(base_path: &Path) -> Result<String, String> {
|
||||
// Generate a unique world name
|
||||
let mut counter: i32 = 1;
|
||||
let unique_name: String = loop {
|
||||
let candidate_name: String = format!("Arnis World {}", counter);
|
||||
let candidate_path: PathBuf = base_path.join(&candidate_name);
|
||||
if !candidate_path.exists() {
|
||||
break candidate_name;
|
||||
}
|
||||
counter += 1;
|
||||
};
|
||||
|
||||
let new_world_path: PathBuf = base_path.join(&unique_name);
|
||||
|
||||
// Create the new world directory structure
|
||||
fs::create_dir_all(world_path.join("region"))
|
||||
.map_err(|e: std::io::Error| format!("Failed to create world directory: {}", e))?;
|
||||
fs::create_dir_all(new_world_path.join("region"))
|
||||
.map_err(|e| format!("Failed to create world directory: {}", e))?;
|
||||
|
||||
// Copy the region template file
|
||||
const REGION_TEMPLATE: &[u8] = include_bytes!("../mcassets/region.template");
|
||||
let region_path = world_path.join("region").join("r.0.0.mca");
|
||||
let region_path = new_world_path.join("region").join("r.0.0.mca");
|
||||
fs::write(®ion_path, REGION_TEMPLATE)
|
||||
.map_err(|e: std::io::Error| format!("Failed to create region file: {}", e))?;
|
||||
.map_err(|e| format!("Failed to create region file: {}", e))?;
|
||||
|
||||
// Add the level.dat file
|
||||
const LEVEL_TEMPLATE: &[u8] = include_bytes!("../mcassets/level.dat");
|
||||
|
||||
// Decompress the gzipped level.template
|
||||
let mut decoder: GzDecoder<&[u8]> = GzDecoder::new(LEVEL_TEMPLATE);
|
||||
let mut decompressed_data: Vec<u8> = Vec::new();
|
||||
let mut decoder = GzDecoder::new(LEVEL_TEMPLATE);
|
||||
let mut decompressed_data = Vec::new();
|
||||
decoder
|
||||
.read_to_end(&mut decompressed_data)
|
||||
.map_err(|e: std::io::Error| format!("Failed to decompress level.template: {}", e))?;
|
||||
.map_err(|e| format!("Failed to decompress level.template: {}", e))?;
|
||||
|
||||
// Parse the decompressed NBT data
|
||||
let mut level_data: Value = fastnbt::from_bytes(&decompressed_data)
|
||||
.map_err(|e: fastnbt::error::Error| format!("Failed to parse level.dat template: {}", e))?;
|
||||
.map_err(|e| format!("Failed to parse level.dat template: {}", e))?;
|
||||
|
||||
// Modify the LevelName and LastPlayed fields
|
||||
if let Value::Compound(ref mut root) = level_data {
|
||||
if let Some(Value::Compound(ref mut data)) = root.get_mut("Data") {
|
||||
// Update LevelName
|
||||
data.insert(
|
||||
"LevelName".to_string(),
|
||||
Value::String(world_name.to_string()),
|
||||
);
|
||||
data.insert("LevelName".to_string(), Value::String(unique_name.clone()));
|
||||
|
||||
// Update LastPlayed to the current Unix time in milliseconds
|
||||
let current_time: std::time::Duration = std::time::SystemTime::now()
|
||||
let current_time = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map_err(|e: std::time::SystemTimeError| {
|
||||
format!("Failed to get current time: {}", e)
|
||||
})?;
|
||||
let current_time_millis: i64 = current_time.as_millis() as i64;
|
||||
.map_err(|e| format!("Failed to get current time: {}", e))?;
|
||||
let current_time_millis = current_time.as_millis() as i64;
|
||||
data.insert("LastPlayed".to_string(), Value::Long(current_time_millis));
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the updated NBT data back to bytes
|
||||
let serialized_level_data: Vec<u8> =
|
||||
fastnbt::to_bytes(&level_data).map_err(|e: fastnbt::error::Error| {
|
||||
format!("Failed to serialize updated level.dat: {}", e)
|
||||
})?;
|
||||
let serialized_level_data: Vec<u8> = fastnbt::to_bytes(&level_data)
|
||||
.map_err(|e| format!("Failed to serialize updated level.dat: {}", e))?;
|
||||
|
||||
// Compress the serialized data back to gzip
|
||||
let mut encoder: flate2::write::GzEncoder<Vec<u8>> =
|
||||
flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
|
||||
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
|
||||
encoder
|
||||
.write_all(&serialized_level_data)
|
||||
.map_err(|e: std::io::Error| format!("Failed to compress updated level.dat: {}", e))?;
|
||||
let compressed_level_data: Vec<u8> = encoder.finish().map_err(|e: std::io::Error| {
|
||||
format!("Failed to finalize compression for level.dat: {}", e)
|
||||
})?;
|
||||
.map_err(|e| format!("Failed to compress updated level.dat: {}", e))?;
|
||||
let compressed_level_data = encoder
|
||||
.finish()
|
||||
.map_err(|e| format!("Failed to finalize compression for level.dat: {}", e))?;
|
||||
|
||||
fs::write(world_path.join("level.dat"), compressed_level_data)
|
||||
.map_err(|e: std::io::Error| format!("Failed to create level.dat file: {}", e))?;
|
||||
// Write the level.dat file
|
||||
fs::write(new_world_path.join("level.dat"), compressed_level_data)
|
||||
.map_err(|e| format!("Failed to create level.dat file: {}", e))?;
|
||||
|
||||
// Add the icon.png file
|
||||
const ICON_TEMPLATE: &[u8] = include_bytes!("../mcassets/icon.png");
|
||||
fs::write(world_path.join("icon.png"), ICON_TEMPLATE)
|
||||
.map_err(|e: std::io::Error| format!("Failed to create icon.png file: {}", e))?;
|
||||
fs::write(new_world_path.join("icon.png"), ICON_TEMPLATE)
|
||||
.map_err(|e| format!("Failed to create icon.png file: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
Ok(new_world_path.display().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -51,7 +51,7 @@ pub struct ProcessedWay {
|
||||
pub tags: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ProcessedMemberRole {
|
||||
Outer,
|
||||
Inner,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Arnis",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.2",
|
||||
"identifier": "com.louisdev.arnis",
|
||||
"build": {
|
||||
"frontendDist": "gui-src"
|
||||
@@ -32,6 +32,11 @@
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
],
|
||||
"windows": {
|
||||
"webviewInstallMode": {
|
||||
"type": "embedBootstrapper"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user