├── .clippy.toml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── LICENSE ├── animals │ ├── ant.animal.ron │ ├── ant.png │ ├── elephant.animal.ron │ ├── elephant.png │ ├── fox.animal.ron │ ├── fox.png │ ├── raccoon-dog.animal.ron │ └── raccoon-dog.png ├── biomes │ ├── boreal-forest.png │ ├── desert.png │ ├── grassland.png │ ├── ice-sheet.png │ ├── list.biomes.ron │ ├── ocean.png │ ├── rock.png │ ├── sea-ice.png │ ├── temperate-forest.png │ ├── tropical-rainforest.png │ └── tundra.png ├── default.conf.toml ├── fonts │ ├── Mplus2-SemiBold.otf.gz │ └── OFL.txt ├── list.credits.ron ├── music │ └── list.music.ron ├── planet.params.ron ├── se │ ├── achivement.ogg │ ├── aerosol-injection.ogg │ ├── black-dust.ogg │ ├── build-space.ogg │ ├── build.ogg │ ├── civilize.ogg │ ├── demolish.ogg │ ├── fire.ogg │ ├── plague.ogg │ ├── report.ogg │ ├── select-item.ogg │ ├── slider.ogg │ ├── spawn-animal.ogg │ ├── window-close.ogg │ └── window-open.ogg ├── start_planets │ ├── continental.png │ ├── continental.start_planet.ron │ ├── desert.png │ ├── desert.start_planet.ron │ ├── ice.png │ ├── ice.start_planet.ron │ └── tutorial.start_planet.ron ├── structures │ ├── carbon-capturer.png │ ├── carbon-dioxide-sprayer.png │ ├── fertilization-plant.png │ ├── gift-tower.png │ ├── heater.png │ ├── list.structures.ron │ ├── oxygen-generator.png │ ├── rainmaker.png │ └── settlement.png ├── text │ ├── en │ │ ├── achivements.toml │ │ ├── animals.toml │ │ ├── base.toml │ │ ├── help.toml │ │ ├── planets.toml │ │ ├── report.toml │ │ └── tutorial.toml │ └── ja │ │ ├── achivements.toml │ │ ├── animals.toml │ │ ├── base.toml │ │ ├── help.toml │ │ ├── planets.toml │ │ ├── report.toml │ │ └── tutorial.toml ├── tile_animations │ ├── aerosol-injection.png │ ├── black-dust.png │ ├── decadence.png │ ├── fire.png │ ├── nuclear-explosion.png │ ├── plague.png │ ├── troop.png │ ├── vehicle.png │ └── war.png └── ui │ ├── achivement-abundant-power.png │ ├── achivement-animals.png │ ├── achivement-civilize.png │ ├── achivement-desert-greening.png │ ├── achivement-destroy-planet.png │ ├── achivement-forests.png │ ├── achivement-giant-mirror.png │ ├── achivement-grasslands.png │ ├── achivement-green-planet.png │ ├── achivement-industrial-revolution.png │ ├── achivement-inter-species-war.png │ ├── achivement-locked.png │ ├── achivement-low-carbon-dioxide.png │ ├── achivement-magical-energy.png │ ├── achivement-melted-ice.png │ ├── achivement-pandemic.png │ ├── achivement-step-toward-ecumenopolis.png │ ├── background-building-planet-left.png │ ├── background-building-planet.png │ ├── background-building-space.png │ ├── background-building-star.png │ ├── building-asteroid-mining-station.png │ ├── building-carbon-importer.png │ ├── building-dyson-swarm-unit.png │ ├── building-fusion-reactor.png │ ├── building-ice-importer.png │ ├── building-ion-irradiator.png │ ├── building-nitrogen-importer.png │ ├── building-orbital-mirror.png │ ├── icon-achivements.png │ ├── icon-action.png │ ├── icon-age-atomic.png │ ├── icon-age-bronze.png │ ├── icon-age-early-space.png │ ├── icon-age-industrial.png │ ├── icon-age-iron.png │ ├── icon-age-stone.png │ ├── icon-air-temperature.png │ ├── icon-animal.png │ ├── icon-argon.png │ ├── icon-biomass.png │ ├── icon-build.png │ ├── icon-carbon-dioxide.png │ ├── icon-carbon.png │ ├── icon-check.png │ ├── icon-city.png │ ├── icon-civilization.png │ ├── icon-cloud-albedo.png │ ├── icon-control.png │ ├── icon-coordinates.png │ ├── icon-cross.png │ ├── icon-cycles.png │ ├── icon-energy-source-biomass.png │ ├── icon-energy-source-fossil-fuel.png │ ├── icon-energy-source-gift.png │ ├── icon-energy-source-hydro-geothermal.png │ ├── icon-energy-source-nuclear.png │ ├── icon-energy-source-solar-wind.png │ ├── icon-fertility.png │ ├── icon-game-menu.png │ ├── icon-gene.png │ ├── icon-height.png │ ├── icon-help.png │ ├── icon-layers.png │ ├── icon-map.png │ ├── icon-material.png │ ├── icon-nitrogen.png │ ├── icon-oxygen.png │ ├── icon-planet.png │ ├── icon-population.png │ ├── icon-power.png │ ├── icon-radius.png │ ├── icon-rainfall.png │ ├── icon-reports.png │ ├── icon-sea-temperature.png │ ├── icon-solar-constant.png │ ├── icon-space-buildings.png │ ├── icon-speed-fast-selected.png │ ├── icon-speed-fast.png │ ├── icon-speed-medium-selected.png │ ├── icon-speed-medium.png │ ├── icon-speed-paused-selected.png │ ├── icon-speed-paused.png │ ├── icon-speed-slow-selected.png │ ├── icon-speed-slow.png │ ├── icon-stat.png │ ├── tile-colored.png │ ├── tile-cursor-bold.png │ ├── tile-cursor.png │ ├── tutorial-animal-habitat.png │ ├── tutorial-carbon-capturer-example.png │ ├── tutorial-icon.png │ ├── tutorial-mouse.png │ ├── tutorial-move-keys.png │ ├── tutorial-oxygen-generator-example.png │ └── tutorial-soil-fertilize-example.png ├── gaia-maker.desktop ├── icon.png └── src ├── achivement_save.rs ├── action.rs ├── assets.rs ├── audio.rs ├── conf.rs ├── draw.rs ├── gz.rs ├── image_assets.rs ├── main.rs ├── manage_planet.rs ├── overlay.rs ├── planet ├── achivement.rs ├── action.rs ├── animal.rs ├── atmo.rs ├── biome.rs ├── buildings.rs ├── civ.rs ├── civ_energy.rs ├── debug.rs ├── decadence.rs ├── defs.rs ├── event.rs ├── heat_transfer.rs ├── initial_conditions.rs ├── map_generator.rs ├── misc.rs ├── mod.rs ├── monitoring.rs ├── new.rs ├── plague.rs ├── report.rs ├── requirement.rs ├── resources.rs ├── serde_with_types.rs ├── sim.rs ├── stat.rs ├── tile_event.rs ├── war.rs └── water.rs ├── platform ├── client.rs ├── mod.rs ├── native.rs └── wasm.rs ├── saveload.rs ├── screen.rs ├── text.rs ├── text_assets.rs ├── title_screen.rs ├── tools.rs ├── tutorial.rs └── ui ├── achivements.rs ├── animals.rs ├── control.rs ├── debug_tools.rs ├── dialogs.rs ├── help.rs ├── hover_tile_tooltip.rs ├── indicators.rs ├── main_menu.rs ├── map.rs ├── misc.rs ├── mod.rs ├── new_planet.rs ├── preferences.rs ├── reports.rs ├── saveload.rs ├── space_buildings.rs ├── stat.rs ├── toolbar.rs ├── tools_expander.rs └── tutorial.rs /.clippy.toml: -------------------------------------------------------------------------------- 1 | single-char-binding-names-threshold = 10 2 | too-many-arguments-threshold = 12 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check-test: 7 | name: Ci 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - run: rustup component add rustfmt 17 | - run: rustup component add clippy 18 | - run: sudo apt-get update && sudo apt-get upgrade 19 | - run: sudo apt-get install -y libasound2-dev libudev-dev 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: check 23 | - uses: actions-rs/cargo@v1 24 | with: 25 | command: test 26 | - uses: actions-rs/cargo@v1 27 | with: 28 | command: fmt 29 | args: --all -- --check 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: clippy 33 | args: -- -D warnings 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.js 3 | *.wasm 4 | assets.tar.gz 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | attr_fn_like_width = 92 2 | chain_width = 64 3 | merge_derives = false 4 | newline_style = "Unix" 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gaia-maker" 3 | version = "0.2.0" 4 | edition = "2024" 5 | authors = ["T. Okubo "] 6 | license = "GPL-3.0" 7 | description = "Planet and terraforming simulation game" 8 | 9 | [features] 10 | default = [] 11 | asset_tar = ["dep:bevy_asset_tar"] 12 | deb = ["asset_tar"] 13 | 14 | [dependencies] 15 | anyhow = "1" 16 | arrayvec = { version = "0.7", features = ["serde"] } 17 | base64 = "0.22" 18 | bevy = { version = "0.15", default-features = false, features = ["bevy_asset", "bevy_core_pipeline", "bevy_image", "bevy_render", "bevy_sprite", "bevy_window", "bevy_winit", "multi_threaded", "x11", "webgl2", "webp"] } 19 | bevy_asset_loader = "0.22" 20 | bevy_common_assets = { version = "0.12", features = ["ron", "toml"] } 21 | bevy_egui = "0.33" 22 | bevy_kira_audio = "0.22" 23 | byteorder = "1.5.0" 24 | bytes = "1.9.0" 25 | chrono = "0.4.39" 26 | clap = { version = "4", features = ["derive"] } 27 | compact_str = { version = "0.8.0", features = ["serde"] } 28 | crossbeam = "0.8" 29 | dirs = "6" 30 | egui_extras = "0.31" 31 | egui_plot = "0.31" 32 | flate2 = "1" 33 | fnv = "1" 34 | image = { version = "0.25.5", default-features = false, features = ["png", "rayon"] } 35 | log = "0.4" 36 | noise = "0.9" 37 | num-derive = "0.4.2" 38 | num-traits = "0.2.19" 39 | ordered-float = { version = "5.0.0", default-features = false } 40 | rand = { version = "0.9.0", features = ["small_rng"] } 41 | rayon = "1.10.0" 42 | regex = "1" 43 | rmp-serde = "1.3.0" 44 | ron = "0.8" 45 | sanitize-filename = "0.6.0" 46 | serde = { version = "1", features = ["derive"] } 47 | serde_json = "1.0.138" 48 | serde_repr = "0.1.19" 49 | serde_with = "3" 50 | smallvec = { version = "1.13.2", features = ["serde"] } 51 | strum = { version = "0.27.1", features = ["derive"] } 52 | toml = "0.8.19" 53 | winit = { version = "0.30.9", default-features = false } 54 | 55 | bevy_asset_tar = { git = "https://github.com/garkimasera/bevy_asset_tar.git", rev = "f0b7266", optional = true } 56 | tile-geom = { git = "https://github.com/garkimasera/tile-geom.git" } 57 | 58 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 59 | zstd = "0.13.2" 60 | 61 | [target.'cfg(target_arch = "wasm32")'.dependencies] 62 | wasm-bindgen = "0.2" 63 | web-sys = { version = "0.3", features = ["Window", "Storage", "Location"] } 64 | getrandom = { version = "0.3", features = ["wasm_js"] } # For rand v0.9 65 | 66 | [profile.dev] 67 | opt-level = 1 68 | 69 | [profile.release] 70 | strip = true 71 | 72 | [profile.profiling] 73 | inherits = "release" 74 | debug = true 75 | strip = false 76 | 77 | [package.metadata.deb] 78 | depends = "$auto" 79 | extended-description = "" 80 | section = "games" 81 | priority = "optional" 82 | assets = [ 83 | ["target/release/gaia-maker", "usr/games/", "755"], 84 | ["assets.tar.gz", "usr/share/games/gaia-maker/assets.tar.gz", "644"], 85 | ["gaia-maker.desktop", "usr/share/applications/", "644"], 86 | ["icon.png", "usr/share/pixmaps/gaia-maker.png", "644"], 87 | ] 88 | features = ["deb"] 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Gaia Maker](https://garkimasera.github.io/games/images/gaia-maker-logo2.png) 2 | 3 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/garkimasera/gaia-maker/blob/main/LICENSE) 4 | [![CI](https://github.com/garkimasera/gaia-maker/actions/workflows/ci.yml/badge.svg)](https://github.com/garkimasera/gaia-maker/actions/workflows/ci.yml) 5 | 6 | Planet and terraforming simulation game written in Rust 7 | 8 | ## Features 9 | 10 | * Simulation of the entire planet based on real physics and geology. 11 | * Detailed simulation of temperature, biomass, carbon cycle, insolation, and atmospheric composition. 12 | * Dynamic gameplay where player actions impact the entire planet. 13 | * Resource acquisition via fusion reactors, Dyson swarms, and more. 14 | * Various facilities and methods for terraforming (e.g., oxygen generators, aerosol injection). 15 | * Animal breeding and habitat expansion. 16 | * Simulation of civilizations' evolution and their planetary impact. 17 | * Various dynamic events affecting civilizations, such as plagues and wars. 18 | * Multiple distinct planet types to terraform. 19 | 20 | ## Downloads 21 | 22 | Pre-built packages and wasm version is available at [garkimasera.itch.io/gaia-maker](https://garkimasera.itch.io/gaia-maker) 23 | 24 | [Github Releases](https://github.com/garkimasera/gaia-maker/releases) 25 | 26 | ## Screenshots 27 | 28 | ![Biome](https://garkimasera.github.io/games/images/screenshots/gaia-maker-01.webp) 29 | ![Temperature](https://garkimasera.github.io/games/images/screenshots/gaia-maker-02.webp) 30 | ![Civilization](https://garkimasera.github.io/games/images/screenshots/gaia-maker-03.webp) 31 | 32 | ## License 33 | 34 | GPL v3 35 | 36 | Files under `assets` are licensed as CC BY 4.0 (except for font files) 37 | 38 | The pre-built packages contain non-free assets that are not included in this repository. Redistribution of these assets is NOT permitted. 39 | -------------------------------------------------------------------------------- /assets/animals/ant.animal.ron: -------------------------------------------------------------------------------- 1 | ( 2 | size: small, 3 | cost: 2, 4 | habitat: land, 5 | growth_speed: 140, 6 | temp: (5, 50), 7 | settlement_effect: 0.9, 8 | ) 9 | -------------------------------------------------------------------------------- /assets/animals/ant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/animals/ant.png -------------------------------------------------------------------------------- /assets/animals/elephant.animal.ron: -------------------------------------------------------------------------------- 1 | ( 2 | size: large, 3 | cost: 30, 4 | habitat: land, 5 | temp: (12, 32), 6 | settlement_effect: 0.0, 7 | ) 8 | -------------------------------------------------------------------------------- /assets/animals/elephant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/animals/elephant.png -------------------------------------------------------------------------------- /assets/animals/fox.animal.ron: -------------------------------------------------------------------------------- 1 | ( 2 | size: medium, 3 | cost: 20, 4 | habitat: land, 5 | temp: (8, 28), 6 | settlement_effect: 0.5, 7 | civ: ( 8 | color: (0xff, 0xd4, 0x58), 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /assets/animals/fox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/animals/fox.png -------------------------------------------------------------------------------- /assets/animals/raccoon-dog.animal.ron: -------------------------------------------------------------------------------- 1 | ( 2 | size: medium, 3 | cost: 20, 4 | habitat: land, 5 | temp: (8, 28), 6 | settlement_effect: 0.2, 7 | civ: ( 8 | color: (0xa5, 0x50, 0x00), 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /assets/animals/raccoon-dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/animals/raccoon-dog.png -------------------------------------------------------------------------------- /assets/biomes/boreal-forest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/boreal-forest.png -------------------------------------------------------------------------------- /assets/biomes/desert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/desert.png -------------------------------------------------------------------------------- /assets/biomes/grassland.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/grassland.png -------------------------------------------------------------------------------- /assets/biomes/ice-sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/ice-sheet.png -------------------------------------------------------------------------------- /assets/biomes/list.biomes.ron: -------------------------------------------------------------------------------- 1 | { 2 | "rock": ( 3 | z: 80.0, 4 | albedo: 0.3, 5 | revaporization_ratio: 0.1, 6 | priority: 1, 7 | mean_transition_time: 200, 8 | requirements: ( 9 | temp: (-273.15, 10000.0), 10 | rainfall: (0.0, 100000.0), 11 | fertility: 0.0, 12 | biomass: 0.0, 13 | ), 14 | color: (198, 118, 37), 15 | ), 16 | "ocean": ( 17 | z: 90.0, 18 | albedo: 0.2, 19 | revaporization_ratio: 0.0, 20 | priority: 0, 21 | mean_transition_time: 100, 22 | requirements: ( 23 | temp: (-10.0, 100.0), 24 | rainfall: (0.0, 100000.0), 25 | fertility: 0.0, 26 | biomass: 0.0, 27 | ), 28 | color: (43, 82, 187), 29 | ), 30 | "sea-ice": ( 31 | z: 95.0, 32 | albedo: 0.7, 33 | revaporization_ratio: 0.0, 34 | priority: 0, 35 | mean_transition_time: 100, 36 | requirements: ( 37 | temp: (-273.15, -20.0), 38 | rainfall: (0.0, 100000.0), 39 | fertility: 0.0, 40 | biomass: 0.0, 41 | ), 42 | color: (234, 238, 241), 43 | ), 44 | "desert": ( 45 | z: 50.0, 46 | albedo: 0.35, 47 | revaporization_ratio: 0.1, 48 | priority: 2, 49 | mean_transition_time: 80, 50 | requirements: ( 51 | temp: (0.0, 100.0), 52 | rainfall: (0.0, 100000.0), 53 | fertility: 0.0, 54 | biomass: 0.0, 55 | ), 56 | color: (211, 141, 66), 57 | ), 58 | "ice-sheet": ( 59 | z: 70.0, 60 | albedo: 0.7, 61 | revaporization_ratio: 0.1, 62 | priority: 2, 63 | mean_transition_time: 80, 64 | requirements: ( 65 | temp: (-273.15, -15.0), 66 | rainfall: (0.0, 100000.0), 67 | fertility: 0.0, 68 | biomass: 0.0, 69 | ), 70 | color: (234, 238, 241), 71 | ), 72 | "tundra": ( 73 | z: 45.0, 74 | albedo: 0.6, 75 | revaporization_ratio: 0.2, 76 | priority: 5, 77 | mean_transition_time: 100, 78 | requirements: ( 79 | temp: (-15.0, 0.0), 80 | rainfall: (50.0, 3000.0), 81 | fertility: 10.0, 82 | biomass: 1.0, 83 | ), 84 | color: (171, 171, 171), 85 | ), 86 | "grassland": ( 87 | z: 40.0, 88 | albedo: 0.2, 89 | revaporization_ratio: 0.3, 90 | priority: 10, 91 | mean_transition_time: 120, 92 | requirements: ( 93 | temp: (0.0, 40.0), 94 | rainfall: (50.0, 5000.0), 95 | fertility: 20.0, 96 | biomass: 1.0, 97 | ), 98 | color: (94, 138, 64), 99 | ), 100 | "boreal-forest": ( 101 | z: 35.0, 102 | albedo: 0.2, 103 | revaporization_ratio: 0.7, 104 | priority: 15, 105 | mean_transition_time: 150, 106 | requirements: ( 107 | temp: (2.0, 12.0), 108 | rainfall: (300.0, 10000.0), 109 | fertility: 40.0, 110 | biomass: 5.5, 111 | ), 112 | color: (24, 80, 22), 113 | ), 114 | "temperate-forest": ( 115 | z: 34.0, 116 | albedo: 0.2, 117 | revaporization_ratio: 0.7, 118 | priority: 20, 119 | mean_transition_time: 180, 120 | requirements: ( 121 | temp: (12.0, 25.0), 122 | rainfall: (500.0, 10000.0), 123 | fertility: 40.0, 124 | biomass: 6.0, 125 | ), 126 | color: (37, 117, 19), 127 | ), 128 | "tropical-rainforest": ( 129 | z: 33.0, 130 | albedo: 0.2, 131 | revaporization_ratio: 0.7, 132 | priority: 25, 133 | mean_transition_time: 180, 134 | requirements: ( 135 | temp: (25.0, 40.0), 136 | rainfall: (800.0, 10000.0), 137 | fertility: 40.0, 138 | biomass: 7.0, 139 | ), 140 | color: (44, 192, 1), 141 | ), 142 | } 143 | -------------------------------------------------------------------------------- /assets/biomes/ocean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/ocean.png -------------------------------------------------------------------------------- /assets/biomes/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/rock.png -------------------------------------------------------------------------------- /assets/biomes/sea-ice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/sea-ice.png -------------------------------------------------------------------------------- /assets/biomes/temperate-forest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/temperate-forest.png -------------------------------------------------------------------------------- /assets/biomes/tropical-rainforest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/tropical-rainforest.png -------------------------------------------------------------------------------- /assets/biomes/tundra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/biomes/tundra.png -------------------------------------------------------------------------------- /assets/default.conf.toml: -------------------------------------------------------------------------------- 1 | lang = "English" 2 | 3 | camera_move_speed = 48.0 4 | autosave_enabled = true 5 | autosave_cycle_duration = 1000 6 | autosave_max_files = 5 7 | manual_max_files = 5 8 | report_lifespan = 10000 9 | screen_refresh_rate = "medium" 10 | show_fps = false 11 | sound_effect_volume = 70 12 | bgm_volume = 70 13 | slow_speed_sim_duration_ms = 250 14 | medium_speed_sim_duration_ms = 100 15 | 16 | [ui] 17 | scale_factor = 1.0 18 | font_scale = 1.2 19 | reports_in_list = 10 20 | min_sidebar_width = 200 21 | -------------------------------------------------------------------------------- /assets/fonts/Mplus2-SemiBold.otf.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/fonts/Mplus2-SemiBold.otf.gz -------------------------------------------------------------------------------- /assets/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2021 The M+ FONTS Project Authors (https://github.com/coz-m/MPLUS_FONTS) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /assets/list.credits.ron: -------------------------------------------------------------------------------- 1 | { 2 | Developer: [ 3 | "garkimasera", 4 | ], 5 | } 6 | -------------------------------------------------------------------------------- /assets/music/list.music.ron: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /assets/se/achivement.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/achivement.ogg -------------------------------------------------------------------------------- /assets/se/aerosol-injection.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/aerosol-injection.ogg -------------------------------------------------------------------------------- /assets/se/black-dust.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/black-dust.ogg -------------------------------------------------------------------------------- /assets/se/build-space.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/build-space.ogg -------------------------------------------------------------------------------- /assets/se/build.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/build.ogg -------------------------------------------------------------------------------- /assets/se/civilize.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/civilize.ogg -------------------------------------------------------------------------------- /assets/se/demolish.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/demolish.ogg -------------------------------------------------------------------------------- /assets/se/fire.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/fire.ogg -------------------------------------------------------------------------------- /assets/se/plague.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/plague.ogg -------------------------------------------------------------------------------- /assets/se/report.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/report.ogg -------------------------------------------------------------------------------- /assets/se/select-item.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/select-item.ogg -------------------------------------------------------------------------------- /assets/se/slider.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/slider.ogg -------------------------------------------------------------------------------- /assets/se/spawn-animal.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/spawn-animal.ogg -------------------------------------------------------------------------------- /assets/se/window-close.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/window-close.ogg -------------------------------------------------------------------------------- /assets/se/window-open.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/se/window-open.ogg -------------------------------------------------------------------------------- /assets/start_planets/continental.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/start_planets/continental.png -------------------------------------------------------------------------------- /assets/start_planets/continental.start_planet.ron: -------------------------------------------------------------------------------- 1 | ( 2 | id: "continental", 3 | habitability: ideal, 4 | radius: (6200, 300), 5 | solar_constant: (1350, 120), 6 | elevation: (7000.0, 1000.0), 7 | water_volume: (2.0e+17, 0.2e+17), 8 | nitrogen: (0.75, 0.08), 9 | carbon_dioxide: (0.05, 0.01), 10 | argon: (1.0e-2, 0.2e-2), 11 | initial_conditions: [], 12 | height_table: [(0.0, 0.0), (0.2, 0.05), (0.4, 0.5), (0.8, 0.6), (1.0, 1.0)], 13 | target_sea_level: 0.525, 14 | initial_buried_carbon: ( 15 | n_spot: (4, 7), 16 | mass: (1.0e+4, 1.0e+6), 17 | radius: (1, 3), 18 | scattering: 0.4, 19 | ), 20 | ) 21 | -------------------------------------------------------------------------------- /assets/start_planets/desert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/start_planets/desert.png -------------------------------------------------------------------------------- /assets/start_planets/desert.start_planet.ron: -------------------------------------------------------------------------------- 1 | ( 2 | id: "desert", 3 | habitability: poor, 4 | radius: (6500, 200), 5 | solar_constant: (1200, 200), 6 | elevation: (7000.0, 1000.0), 7 | water_volume: (0.0, 0.0), 8 | nitrogen: (0.75, 0.08), 9 | oxygen: (8.0e-6, 2.0e-6), 10 | carbon_dioxide: (0.08, 0.02), 11 | argon: (1.0e-2, 0.2e-2), 12 | initial_conditions: [], 13 | height_table: [(0.0, 0.0), (0.3, 0.05), (0.8, 0.4), (1.0, 1.0)], 14 | initial_buried_carbon: ( 15 | n_spot: (3, 10), 16 | mass: (1.0e+1, 1.0e+4), 17 | radius: (2, 3), 18 | scattering: 0.4, 19 | ), 20 | ) 21 | -------------------------------------------------------------------------------- /assets/start_planets/ice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/start_planets/ice.png -------------------------------------------------------------------------------- /assets/start_planets/ice.start_planet.ron: -------------------------------------------------------------------------------- 1 | ( 2 | id: "ice", 3 | habitability: adequate, 4 | radius: (5500, 100), 5 | solar_constant: (850, 30), 6 | elevation: (7000.0, 1000.0), 7 | water_volume: (1.6e+17, 0.2e+17), 8 | nitrogen: (0.75, 0.08), 9 | oxygen: (8.0e-6, 2.0e-6), 10 | carbon_dioxide: (0.07, 0.01), 11 | argon: (1.0e-2, 0.2e-2), 12 | initial_conditions: [ 13 | Snowball ( thickness: (250.0, 10.0) ) 14 | ], 15 | height_table: [(0.0, 0.0), (0.2, 0.05), (0.4, 0.5), (0.8, 0.7), (1.0, 1.0)], 16 | initial_buried_carbon: ( 17 | n_spot: (8, 16), 18 | mass: (1.0e+4, 1.0e+5), 19 | radius: (1, 2), 20 | scattering: 0.4, 21 | ), 22 | ) 23 | -------------------------------------------------------------------------------- /assets/start_planets/tutorial.start_planet.ron: -------------------------------------------------------------------------------- 1 | ( 2 | id: "tutorial", 3 | habitability: ideal, 4 | radius: (6000, 0), 5 | solar_constant: (1300, 0), 6 | elevation: (7000.0, 0.0), 7 | water_volume: (2.0e+17, 0.2e+17), 8 | nitrogen: (0.75, 0.00), 9 | oxygen: (0.06, 0.00), 10 | carbon_dioxide: (0.007, 0.00), 11 | argon: (0.01, 0.00), 12 | initial_conditions: [], 13 | height_table: [(0.0, 0.0), (0.2, 0.05), (0.4, 0.48), (0.8, 0.6), (1.0, 1.0)], 14 | target_sea_area: 0.52, 15 | initial_buried_carbon: ( 16 | n_spot: (4, 7), 17 | mass: (1.0e+4, 1.0e+6), 18 | radius: (1, 3), 19 | scattering: 0.4, 20 | ), 21 | ) 22 | -------------------------------------------------------------------------------- /assets/structures/carbon-capturer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/structures/carbon-capturer.png -------------------------------------------------------------------------------- /assets/structures/carbon-dioxide-sprayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/structures/carbon-dioxide-sprayer.png -------------------------------------------------------------------------------- /assets/structures/fertilization-plant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/structures/fertilization-plant.png -------------------------------------------------------------------------------- /assets/structures/gift-tower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/structures/gift-tower.png -------------------------------------------------------------------------------- /assets/structures/heater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/structures/heater.png -------------------------------------------------------------------------------- /assets/structures/list.structures.ron: -------------------------------------------------------------------------------- 1 | { 2 | oxygen_generator: ( 3 | width: 48, 4 | height: 48, 5 | columns: 1, 6 | rows: 2, 7 | building: ( 8 | power: -400, 9 | cost: 500, 10 | effect: SprayToAtmo( kind: oxygen, mass: 4000, limit_atm: 0.21 ), 11 | ), 12 | ), 13 | rainmaker: ( 14 | width: 48, 15 | height: 48, 16 | columns: 1, 17 | rows: 2, 18 | building: ( 19 | power: -10, 20 | cost: 100, 21 | effect: Vapor ( value: 5000 ), 22 | ), 23 | ), 24 | fertilization_plant: ( 25 | width: 48, 26 | height: 48, 27 | columns: 1, 28 | rows: 2, 29 | building: ( 30 | power: -0.2, 31 | cost: 50, 32 | effect: Fertilize ( increment: 0.1, max: 40.0, range: 2 ), 33 | ), 34 | ), 35 | heater: ( 36 | width: 48, 37 | height: 48, 38 | columns: 1, 39 | rows: 2, 40 | building: ( 41 | power: -10, 42 | cost: 100, 43 | effect: Heater ( heat: 1.0e+18 ), 44 | ), 45 | ), 46 | carbon_capturer: ( 47 | width: 48, 48 | height: 48, 49 | columns: 1, 50 | rows: 2, 51 | building: ( 52 | power: -550, 53 | cost: 200, 54 | effect: CaptureCarbonDioxide ( mass: 2.0e+4, limit_atm: 0.3 ), 55 | ), 56 | ), 57 | gift_tower: ( 58 | width: 48, 59 | height: 48, 60 | columns: 1, 61 | rows: 2, 62 | building: ( 63 | power: -10, 64 | cost: 150, 65 | effect: SupplyEnergy ( value: 8.0e+8 ), 66 | ), 67 | ), 68 | settlement: ( 69 | width: 48, 70 | height: 48, 71 | columns: 6, 72 | rows: 2, 73 | building: (), 74 | ), 75 | } 76 | -------------------------------------------------------------------------------- /assets/structures/oxygen-generator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/structures/oxygen-generator.png -------------------------------------------------------------------------------- /assets/structures/rainmaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/structures/rainmaker.png -------------------------------------------------------------------------------- /assets/structures/settlement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/structures/settlement.png -------------------------------------------------------------------------------- /assets/text/en/achivements.toml: -------------------------------------------------------------------------------- 1 | [achivement] 2 | grasslands = "First Green" 3 | forests = "Forester" 4 | animals = "Animal Keeper" 5 | civilize = "Monolith" 6 | green-planet = "Green Planet" 7 | melted-ice = "Melted Ice" 8 | desert-greening = "Desert Greening" 9 | industrial-revolution = "Industrial Revolution" 10 | inter-species-war = "Interspecies War" 11 | pandemic = "Pandemic" 12 | step-toward-ecumenopolis = "The First Step Toward Ecumenopolis" 13 | magical-energy = "Magical Energy" 14 | abundant-power = "Abundant Power" 15 | giant-mirror = "Giant Mirror" 16 | low-carbon-dioxide = "Cannot Photosynthesize" 17 | destroy-planet = "I don't need this" 18 | 19 | [achivement.desc] 20 | grasslands = "Spread grasslands to cover more than 10 tiles." 21 | forests = "Spread forests to cover more than 50 tiles." 22 | animals = "Introduce animal life onto the planet." 23 | civilize = "Evolve animals into a civilization." 24 | green-planet = "Reach a total biomass of 2000 Gt." 25 | melted-ice = "Melt all the ice on an ice planet." 26 | desert-greening = "Achieve 600 Gt of biomass on a desert planet." 27 | industrial-revolution = "Advance a civilization to the Industrial Age." 28 | inter-species-war = "Witness an interspecies war break out." 29 | pandemic = "Have a plague infect more than 1000 cities." 30 | step-toward-ecumenopolis = "Cover more than 50% of the planet surface with cities and reach a population over 7,500,000." 31 | magical-energy = "Supply Gift Energy to an Iron Age civilization." 32 | abundant-power = "Generate 30,000 TW of power." 33 | giant-mirror = "Build an orbital mirror." 34 | low-carbon-dioxide = "Lower the atmospheric CO2 concentration below 0.00003 atm." 35 | destroy-planet = "Make a thriving planet (500+ Gt biomass, 1000+ pop) barren within 3000 cycles." 36 | -------------------------------------------------------------------------------- /assets/text/en/animals.toml: -------------------------------------------------------------------------------- 1 | [animal] 2 | ant = "Ant" 3 | elephant = "Elephant" 4 | fox = "Fox" 5 | raccoon-dog = "Raccoon Dog" 6 | 7 | [civ] 8 | fox = "Fox Civilization" 9 | raccoon-dog = "Raccoon Dog Civilization" 10 | -------------------------------------------------------------------------------- /assets/text/en/planets.toml: -------------------------------------------------------------------------------- 1 | [habitability] 2 | ideal = "Ideal" 3 | adequate = "Adequate" 4 | poor = "Poor" 5 | hostile = "Hostile" 6 | 7 | [planet] 8 | custom = "Custom" 9 | continental = "Continental Planet" 10 | ice = "Ice Planet" 11 | desert = "Desert Planet" 12 | 13 | [planet.desc] 14 | continental = "This is a planet that has both an ocean and a continent. Although it does not have oxygen, it is easy to sustain life due to the presence of an ocean. It is the most suitable celestial body for terraforming." 15 | ice = "This is a planet covered in ice. Due to its low temperature, no life exists there, but if the temperature could be raised somehow, water, which is essential for life, could be obtained." 16 | desert = "This is a planet with almost no water, and its surface is covered in dry desert. Without supplying water, life would not be possible." 17 | -------------------------------------------------------------------------------- /assets/text/en/report.toml: -------------------------------------------------------------------------------- 1 | [report] 2 | warn-high-temp = "The temperature is too high. An effective way to lower the temperature is to operate the Orbital Mirror to block stellar light from reaching the ground surface." 3 | warn-low-carbon-dioxide = "There is not enough carbon dioxide in the atmosphere. This can inhibit plant growth and may even cause a cooling effect. It is necessary to either start up Carbon Importers or burn buried carbon." 4 | warn-low-oxygen = "There is not enough oxygen in the air. Build Oxygen Generators or Carbon Capturers." 5 | warn-low-temp = "The temperature is too low. An effective way to raise the temperature is to operate the Orbital Mirror to increase the amount of stellar light reaching the surface." 6 | civilized = "The process of civilizing {$animal} has been completed." 7 | civ-advance = "{$civ} advanced into the {$age} Age." 8 | civ-extinct = "{$civ} has become extinct." 9 | civ-decadence = "The decadence of {$civ} began." 10 | inter-species-war = "{$civ_a} and {$civ_b} started a war." 11 | inter-species-war-ceased = "The war between {$civ_a} and {$civ_b} ended." 12 | nuclear-war = "A nuclear war began." 13 | -------------------------------------------------------------------------------- /assets/text/ja/achivements.toml: -------------------------------------------------------------------------------- 1 | [achivement] 2 | grasslands = "まずは草を育てる" 3 | forests = "森林管理人" 4 | animals = "動物飼育" 5 | civilize = "モノリス" 6 | green-planet = "緑の惑星" 7 | melted-ice = "溶けた氷" 8 | desert-greening = "砂漠緑化" 9 | industrial-revolution = "産業革命" 10 | inter-species-war = "種族間戦争" 11 | pandemic = "パンデミック" 12 | step-toward-ecumenopolis = "エキュメノポリスへの第一歩" 13 | magical-energy = "魔法みたいなエネルギー" 14 | abundant-power = "みなぎるパワー" 15 | giant-mirror = "巨大な鏡" 16 | low-carbon-dioxide = "光合成できない" 17 | destroy-planet = "もうこんな星いらない" 18 | 19 | [achivement.desc] 20 | grasslands = "草原が10タイル以上に広がる" 21 | forests = "森林が50タイル以上に広がる" 22 | animals = "惑星上に動物が存在する" 23 | civilize = "動物を文明化する" 24 | green-planet = "バイオマス量が2000Gtに達する" 25 | melted-ice = "氷惑星の氷を全て溶かす" 26 | desert-greening = "砂漠惑星でバイオマス量が600Gtに達する" 27 | industrial-revolution = "産業化時代に文明が進化する" 28 | inter-species-war = "異種族間での戦争が起きる" 29 | pandemic = "1000以上の都市に疫病が伝染する" 30 | step-toward-ecumenopolis = "地上の50%以上が都市で、人口が7500000以上" 31 | magical-energy = "鉄器時代の文明にギフトエネルギーを供給する" 32 | abundant-power = "30000TWの電力を得る" 33 | giant-mirror = "軌道ミラーを建設する" 34 | low-carbon-dioxide = "大気中の二酸化炭素濃度が0.00003 atm以下になる" 35 | destroy-planet = "バイオマス500Gt、人口1000以上の惑星を3000サイクル以内に不毛な星にする" 36 | -------------------------------------------------------------------------------- /assets/text/ja/animals.toml: -------------------------------------------------------------------------------- 1 | [animal] 2 | ant = "アリ" 3 | elephant = "ゾウ" 4 | fox = "キツネ" 5 | raccoon-dog = "タヌキ" 6 | 7 | [civ] 8 | fox = "キツネ文明" 9 | raccoon-dog = "タヌキ文明" 10 | -------------------------------------------------------------------------------- /assets/text/ja/base.toml: -------------------------------------------------------------------------------- 1 | # UI 2 | achivements = "実績" 3 | aggressiveness = "攻撃性" 4 | air-temperature = "気温" 5 | animal = "動物" 6 | animals = "動物" 7 | argon = "アルゴン" 8 | atmosphere = "大気" 9 | atmosphere-pressure = "気圧" 10 | average-air-temperature = "平均気温" 11 | average-rainfall = "平均降水量" 12 | average-sea-temperature = "平均海水温" 13 | back = "前へ" 14 | biomass = "生物量" 15 | biome = "バイオーム" 16 | biomes = "バイオーム" 17 | build = "建設" 18 | building-limit-reached = "建設数限界" 19 | buried-carbon = "埋没炭素" 20 | cancel = "キャンセル" 21 | carbon = "炭素" 22 | carbon-dioxide = "二酸化炭素" 23 | civilizable = "文明化可能" 24 | cities = "都市" 25 | city = "都市" 26 | city-icons = "都市アイコン" 27 | civilization = "文明" 28 | civilization-ages = "文明の時代" 29 | civilizations = "文明" 30 | civilize = "文明化" 31 | civilize-cost = "文明化コスト" 32 | civilized = "文明化完了" 33 | civilizing-in-progress = "文明化中" 34 | close = "閉じる" 35 | control = "コントロール" 36 | coordinates = "座標" 37 | cost = "コスト" 38 | credits = "クレジット" 39 | cycles = "サイクル" 40 | date-saved = "保存日時" 41 | delete = "消去" 42 | demolition = "撤去" 43 | density = "密度" 44 | details = "詳細" 45 | difference-in-elevation = "高低差" 46 | enabled = "有効" 47 | energy = "エネルギー" 48 | energy-consumption = "エネルギー消費量" 49 | energy-source-weight = "エネルギー源配分" 50 | energy-sources = "エネルギー源" 51 | exit = "終了" 52 | facilities = "施設" 53 | fertility = "肥沃度" 54 | focus = "注目" 55 | forestation-speed = "植林速度" 56 | gene-points = "遺伝子ポイント" 57 | habitability = "居住性" 58 | habitat = "生息環境" 59 | height = "高さ" 60 | help = "ヘルプ" 61 | high = "高" 62 | history = "歴史" 63 | latitude = "緯度" 64 | large = "大" 65 | layers = "レイヤー" 66 | livable-temperature = "生存可能温度" 67 | load = "ロード" 68 | longitude = "経度" 69 | low = "低" 70 | main-menu = "メインメニュー" 71 | map = "マップ" 72 | material = "素材" 73 | medium = "中" 74 | menu = "メニュー" 75 | messages = "メッセージ" 76 | new = "新規" 77 | new-achivement = "実績獲得!" 78 | new-planet = "新しい惑星" 79 | next = "次へ" 80 | next-tutorial = "次のチュートリアル" 81 | nitrogen = "窒素" 82 | no-civilization = "文明なし" 83 | not-enough = "不足" 84 | none = "なし" 85 | ok = "OK" 86 | orbit = "軌道" 87 | oxygen = "酸素" 88 | planet = "惑星" 89 | planet-name = "惑星名" 90 | population = "人口" 91 | population-growth = "人口増加" 92 | power = "電力" 93 | preferences = "設定" 94 | produce = "産出" 95 | project = "プロジェクト" 96 | radius = "半径" 97 | rainfall = "降水量" 98 | random-name = "ランダム名" 99 | reports = "レポート" 100 | resume = "再開" 101 | save = "セーブ" 102 | save-as = "別名でセーブ" 103 | search-new-planet = "新しい惑星" 104 | size = "サイズ" 105 | small = "小" 106 | solar-constant = "太陽定数" 107 | spawn = "配置" 108 | speed-fast = "高速" 109 | speed-medium = "中速" 110 | speed-paused = "停止" 111 | speed-slow = "低速" 112 | star-system = "星系" 113 | start = "開始" 114 | statistics = "統計" 115 | structures = "構造物" 116 | technology-development = "技術開発" 117 | tile-event = "タイルイベント" 118 | tile-events = "タイルイベント" 119 | tutorial = "チュートリアル" 120 | upkeep = "維持" 121 | water = "水" 122 | 123 | # Biomes 124 | land = "陸" 125 | sea = "海" 126 | rock = "岩石" 127 | ocean = "海" 128 | sea-ice = "海氷" 129 | desert = "砂漠" 130 | ice-sheet = "氷床" 131 | tundra = "ツンドラ" 132 | grassland = "草原" 133 | boreal-forest = "針葉樹林" 134 | temperate-forest = "温帯樹林" 135 | tropical-rainforest = "熱帯雨林" 136 | 137 | # Structures 138 | oxygen-generator = "酸素発生機" 139 | rainmaker = "降雨装置" 140 | fertilization-plant = "肥沃化工場" 141 | heater = "ヒーター" 142 | carbon-capturer = "炭素還元機" 143 | gift-tower = "ギフトタワー" 144 | settlement = "居住地" 145 | 146 | # Space Buildings 147 | fusion-reactor = "核融合炉" 148 | asteroid-mining-station = "小惑星採掘ステーション" 149 | dyson-swarm-unit = "ダイソンスウォームユニット" 150 | orbital-mirror = "軌道ミラー" 151 | ice-importer = "氷輸送機" 152 | nitrogen-importer = "窒素輸送機" 153 | carbon-importer = "炭素輸送機" 154 | ion-irradiator = "イオン放射機" 155 | 156 | # Tile Events 157 | aerosol-injection = "エアロゾル注入" 158 | black-dust = "黒色塵" 159 | decadence = "退廃" 160 | fire = "火災" 161 | plague = "疫病" 162 | vehicle = "乗り物" 163 | war = "戦争" 164 | nuclear-explosion = "核爆発" 165 | troop = "軍隊" 166 | 167 | # Civilization Ages 168 | [age] 169 | stone = "石器" 170 | bronze = "青銅器" 171 | iron = "鉄器" 172 | industrial = "工業化" 173 | atomic = "原子力" 174 | early-space = "初期宇宙" 175 | 176 | # Energy sources 177 | [energy_source] 178 | biomass = "バイオマス" 179 | solar-wind = "太陽 & 風力" 180 | hydro-geothermal = "水力 & 地熱" 181 | fossil-fuel = "化石燃料" 182 | nuclear = "原子力" 183 | gift = "ギフト" 184 | 185 | # Preferences 186 | [preference] 187 | autosave-enabled = "オートセーブ" 188 | screen-refresh-rate = "画面更新頻度" 189 | show-fps = "FPS表示" 190 | bgm-volume = "BGM音量" 191 | se-volume = "効果音音量" 192 | 193 | # Messages 194 | [msg] 195 | animal-insufficient-population = "不十分な個体数" 196 | lack-of-gene-points = "遺伝子ポイント不足" 197 | loading-failed = "ロード失敗" 198 | loading-failed-desc-decode-error = "ロードに失敗しました。セーブデータが互換のないバージョンで作成されたか、破損しています。" 199 | loading-failed-desc-not-found = "ロードに失敗しました。セーブデータを見つけられません。" 200 | delete-save-data = "以下のセーブデータを削除しますか?" 201 | save-limit = "ストレージの制限により、Webブラウザ上で動作している場合は複数のセーブデータを作成できません。" 202 | save-limit-strong = "以下のセーブデータを削除し、新規のゲームをはじめますか? この動作は取り消せません。" 203 | save-limit-file = "消去: {$name}" 204 | save-limit-supplement = "この制限無しにプレイしたい場合はダウンロード版をご使用下さい。" 205 | web-limit-warning = "Gaia MakerはWebブラウザ上で実行されています。Webブラウザ上で実行する場合、セーブデータの数やパフォーマンスが制限されます。制限無しにプレイしたい場合はダウンロード版をご使用下さい。" 206 | control-need-orbital-mirror = "軌道ミラーが建設されていない" 207 | control-need-fertilization-plant = "肥沃化工場が建設されていない" 208 | civilize-animal = "以下の動物を文明化しますか?" 209 | 210 | # Stat items 211 | [stat_item] 212 | planet-name = "惑星の名称" 213 | cycles = "テラフォーミング開始からの経過時間" 214 | radius = "惑星の半径" 215 | population = "文明の人口" 216 | -------------------------------------------------------------------------------- /assets/text/ja/help.toml: -------------------------------------------------------------------------------- 1 | basics = "基本" 2 | concept = "コンセプト" 3 | player = "プレイヤー" 4 | terraforming = "テラフォーミング" 5 | space-buildings = "建造物" 6 | glossary = "用語" 7 | 8 | [help] 9 | concept = "Gaia Maker では、プレイヤーはある未開の惑星とそれを含む星系の管理者となり、テラフォーミングと施設運営を行います。テラフォーミングとは、不毛な惑星を生命が住める環境に改善するプロセスであり、人類が地球以外に大規模な居住地を設けるにはテラフォーミングが不可欠です。各種の施設を建設し、惑星上の大気や気温、植生を調整しましょう。そうすれば、生命にとって理想的な環境を整えることができます。" 10 | player = "管理者であるプレイヤーは、テラフォーミング候補惑星のあるこの星系へと恒星間宇宙船を用いて長い年月をかけやって来ました。管理者は、タイプII文明に相当する技術を持ち、地球型惑星のテラフォーミング技術も所有します。また、母星からテラフォーミングのために様々な動植物の遺伝情報を持ち込んでいます。\n\nタイプII文明とは、カルダシェフ・スケールにおいて、ある星系の全エネルギーを利用できる技術水準を持つ文明を指します。恒星のエネルギーをダイソンスウォームにより集め、その豊富なエネルギーを恒星間航行やテラフォーミングに利用します。" 11 | terraforming = "テラフォーミングの基本は、惑星上の気温、降水量、土壌を調整し、植物を生育することです。そのためには以下の施設が助けになります。\n\n軌道ミラー\n惑星軌道上に建設できるこの施設は、地表に届く恒星の光を増減させ、気温を変化させます。\n\n降雨装置\n海がある惑星であればその周辺に降雨がありますが、そうでなければこの施設で降雨を発生させます。\n\n肥沃化工場\n周辺の肥沃度を増加させ、植物を生育します。" 12 | 13 | oxygen-generator = "酸素発生機は、ケイ素といった元素と結合している地殻中の酸素を単離し、大気中に放出します。その過程には十分な電力が必要です。" 14 | rainmaker = "降雨装置は、雨雲を生成し周囲の降雨量を増加させます。" 15 | fertilization-plant = "肥沃化工場は、周辺を植物の育成に適した土壌にゆっくりと変化させます。植物の生育には十分な養分を含む土壌が必要であり、この工場は肥料と土壌改良ドローンを展開することによりその形成を促進します。" 16 | heater = "ヒーターは電力を大気中に熱として放出し、周辺の気温を上昇させます。" 17 | carbon-capturer = "炭素還元機は、大気中の二酸化炭素を収集・還元します。還元により得られた炭素を地中に保存し、酸素は大気中に解放します。" 18 | gift-tower = "ギフトタワーは、周囲の文明にエネルギーを供給します。その文明の技術レベルに応じて、最も利用しやすい形態でのエネルギーを提供します。文明の成長を促進させますが、努力無しに得られるエネルギーは文明に悪影響を与える可能性もあります。" 19 | 20 | fusion-reactor = "核融合炉は施設の稼働に必要となる電力を供給します。核融合は宇宙文明にとって不可欠な高効率のエネルギー源ですが、それ以上のエネルギーが必要なのであれば、巨大な核融合炉、恒星の利用が必要でしょう。" 21 | asteroid-mining-station = "小惑星採掘ステーションは、小惑星から鉱石を採掘し、建築などに利用可能な素材に加工します。" 22 | dyson-swarm-unit = "ダイソンスウォームは、恒星の発するエネルギーを余すことなく利用するための巨大建造物であり、ダイソンスフィアの1つの形態です。ユニットひとつひとつは光を反射して需要地まで届ける巨大な鏡に過ぎませんが、数が揃えば惑星改造にも十分なほどのエネルギーを供給します。" 23 | orbital-mirror = "軌道ミラーは、恒星の光を反射し惑星表面に到達する光エネルギーを増減させます。その比率は調整可能です。" 24 | ice-importer = "氷輸送機は、星系内の他の天体から氷を輸送します。惑星にある水の量を増加させます。氷の抽出と輸送には大量の電力が必要です。" 25 | nitrogen-importer = "窒素輸送機はアンモニアを大気として持つ星系内の他の天体から窒素を輸送し、惑星の大気に散布します。窒素の抽出と輸送には大量の電力が必要です。" 26 | carbon-importer = "炭素輸送機は、星系内の他の天体から炭素を輸送し、惑星の大気に二酸化炭素として散布します。炭素の抽出と輸送には大量の電力が必要です。" 27 | ion-irradiator = "イオン放射機は、惑星の大気に加速したイオンを衝突させることにより、その分子を宇宙空間へと弾き飛ばします。大気質量を減らすことにより気圧を下げる効果をもちます。" 28 | 29 | aerosol-injection = "惑星の大気圏にエアロゾルを注入します。これにより雲の形成が促進され、多くの場合は恒星からの光を多く反射するようになり気温が低下します。その効果は時間経過とともに減少します。" 30 | black-dust = "地表に黒色の塵を散布します。地表面が恒星の光をより多く吸収し、周囲の気温を上昇させます。この塵は時間経過で消滅します。" 31 | fire = "指定したタイルに火災を起こします。地表のバイオマスを燃やし、文明や動物も焼失させます。二酸化炭素とエアロゾルを増やす効果があります。" 32 | plague = "都市に疫病を広めます。疫病は時間経過で他都市にも感染し、文明の人口を減少させます。" 33 | vehicle = "ある程度発達した文明は船や飛行機で移動します。" 34 | decadence = "ある程度繁栄した文明は、退廃することがあります。退廃した文明では、人々は豊かさの基盤である科学技術、文化、インフラストラクチャーの維持を怠るか、もしくは積極的に破壊するようになります。人口と技術レベルが低下します。" 35 | war = "都市は戦争に巻き込まれることがあります。戦争中の都市は人口が減少します。" 36 | troop = "目標の都市へと進軍する軍隊です。" 37 | 38 | rock = "岩石がむき出しの不毛な地域です。生命はほとんど、もしくは全く存在しません。" 39 | ice-sheet = "氷で覆われた地域です。生命は存続できません。恒星の光を効率よく反射するため、周囲をさらに冷やす効果があります。" 40 | desert = "乾燥により植物が育たない不毛な地域です。たとえ生命が存在してもその量はわずかです。" 41 | ocean = "ある程度の面積と深さのある水域です。惑星にとっての重要な水源であり、海洋生物が生息できます。" 42 | sea-ice = "低温のため海面が凍った海域です。" 43 | grassland = "草地の広がる肥沃な地域です。森林が発達するほどの降雨がない場合、草原になります。森林に比べると生物は少ないです。" 44 | tundra = "地下に永久凍土の存在する地域です。寒冷なため森林は発達しませんが、ある程度の生命が存在します。" 45 | boreal-forest = "気温の低い地域に形成される森林です。" 46 | temperate-forest = "温暖な地域に形成される森林です。多くの生命が存在します。" 47 | tropical-rainforest = "温帯樹林よりさらに温暖で湿潤な地域に形成される森林です。多くの生命が存在します。" 48 | 49 | oxygen = "生命が存在するためには酸素が不可欠です。大気に20%含まれる状態が理想的で、少なすぎても多すぎても問題になります。" 50 | nitrogen = "窒素は大気圧を保つために不可欠です。また生命の重要な構成元素の1つでもあり、大気中の窒素の一部は生命に取り込まれます。" 51 | carbon-dioxide = "植物が生長するためには二酸化炭素が不可欠です。またその温室効果により、惑星の気温を調整する役割を果たします。少なすぎると寒冷化の原因に、多すぎると温暖化の原因になります。そのため、その濃度変化は惑星全体の環境に大きな影響を与えます。" 52 | argon = "アルゴンは化学反応をほとんど起こさないため、その濃度はほとんど変化しません。惑星の大気圧を保つのにわずかながら寄与します。" 53 | 54 | biomass = "その土地、もしくは惑星全体において存在する生物の量を炭素の質量で表します。植物が育つと大気中の二酸化炭素が生命の中に炭素として蓄えられます。逆に、火災などで土地のバイオマスが減少すると、大気に二酸化炭素として放出されます。バイオマスが多い土地であるほど動物は繁殖しやすくなります。" 55 | civilization = "ある程度の知能を持ち、道具を使用するのに適した体格を持つ動物は、遺伝子操作によって知性化を行い、文明をもたせることができます。彼らは惑星から外に出ることができない、プレイヤーから見れば原始的な文明を築きます。文明は石器時代の技術からスタートし、時間経過によってより高度な技術を獲得していきます。拡大した文明の存在は、惑星の環境にも影響を及ぼします。" 56 | cloud-albedo = "雲によって反射される恒星の光の割合を表します。" 57 | fertility = "陸上であれば土壌の肥沃さ、海であれば海水の栄養量を表します。肥沃な土地であれば森林が育ち、また文明が繁栄しやすくなります。土地が肥沃になるためには適切な気温と降水が必要です。" 58 | solar-constant = "惑星が恒星から受け取るエネルギーを表します。これが大きいほど惑星の気温が高くなります。建造物や自然現象によって増減します。" 59 | 60 | civilize = "動物の遺伝子を改造することで知性化し、文明を与えます。選択したタイルに文明化に適した動物が必要です。" 61 | 62 | [help.age] 63 | stone = "原始的な道具として石器を用い、原始的な農業を始めた文明です。人口は少なくエネルギー消費も少ないので生態系への影響は少ないですが、わずかながらエネルギー源としてバイオマスを消費します。" 64 | bronze = "石器より洗練された道具を用い、初歩的な金属加工の技術を持つ文明です。都市への定住や社会の形成が行われています。また、文字の発明により技術や文化を後世に伝えるようになりました。主なエネルギー源はバイオマスです。" 65 | iron = "鉄を道具の製作に使用し、集約的な農業を行い複雑な社会を形成している文明です。鉄の製造には大きなエネルギーを消費し、その源はバイオマスであるため、森林破壊などの生態系への悪影響をもたらします。僅かながら水力や風力、化石燃料をエネルギー源として使用します。" 66 | industrial = "工場による製品の大量生産を行うようになった文明です。人口とエネルギー消費は飛躍的に増大します。化石燃料かそれ以上に高効率なエネルギーが無ければ社会の維持は不可能です。主なエネルギー源は化石燃料と水力です。" 67 | atomic = "核分裂エネルギーを利用する技術を持つ文明です。化石燃料も可能であれば利用します。高効率なエネルギーから発展した社会を築いていますが、核戦争が起こる可能性があります。" 68 | early-space = "核融合エネルギーを利用する技術を持つ文明です。これは、もはや自然界の資源を収奪することなく文明を存続させるほどのエネルギーを得られていることを意味します。また、その余剰エネルギーは、惑星の重力から脱出するのに十分なほどです。" 69 | 70 | [help.energy_source] 71 | biomass = "薪や動物の油などを燃焼させたり、家畜を労働させて得るエネルギーです。文明の技術レベルに関係なく利用することができますが、自然界のバイオマスを直接消費するため、文明の消費エネルギーが多い場合にこれに頼ると著しい環境破壊をもたらします。技術が進歩し、他のエネルギーが利用可能になると、消費量は減少していきます。" 72 | solar-wind = "恒星からの光や風をエネルギー源とします。エネルギーの密度が希薄であるという特性があるため、大量のエネルギーを得るには広い面積が必要であり、土地の確保で環境破壊をもたらします。またその不安定さから、このエネルギー源の割合が増えるほど効率が減少します。しかしながら、技術の進歩した文明の場合、補助的に使用するのであれば高効率のエネルギー源になりえます。" 73 | hydro-geothermal = "川の流れや地熱からエネルギーを取り出します。工業化した文明にとって高効率なエネルギー源として重要です。しかし利用可能な量は限られます。わずかながら環境破壊をもたらします。" 74 | fossil-fuel = "地中に埋まった炭素を燃焼させエネルギーを取り出します。工業化した文明にとって重要なエネルギー源です。利用するには、その文明が炭素の埋まっている土地を確保している必要があり、惑星によっては利用不可能なこともあります。また文明が化石燃料を消費すると二酸化炭素が排出されます。採掘や消費においてわずかながら環境破壊をもたらします。" 75 | nuclear = "ウランやトリウムなどから得られる核分裂エネルギーや、重水素による核融合エネルギーです。惑星上で知的生命体が手にし得る最も高効率なエネルギー源で、環境への負荷もほとんどありません。物理学的には理想的なエネルギー源ですが、利用には高い技術レベルを維持する必要があります。また、原子力エネルギーを利用可能な文明は、核戦争ができる技術も有していることは覚えておくべきでしょう。" 76 | gift = "管理者がもつエネルギーを、地上の原始文明が利用できるようにしたものです。その文明の技術レベルに応じて、最も利用しやすい形態で供給されます。本来なら原始文明が手にすることができないような高効率のエネルギーですが、それゆえ文明の進化に大きな影響を与えるでしょう。" 77 | 78 | [help.control] 79 | orbital-mirror = "軌道ミラーは、恒星の光を反射し惑星表面に到達する光エネルギーを増減させます。" 80 | forestation-speed = "植林速度を変更すると、タイルのバイオマスの増加速度も変化します。" 81 | population-growth = "文明の人口増加速度を調節します" 82 | technology-development = "文明の技術開発速度を調節します。" 83 | aggressiveness = "この文明が問題解決の手段としてどれだけ戦争を好むのかを調整します。内戦、種族間戦争、核戦争の頻度が変わります。" 84 | energy-source-weight = "文明が消費するエネルギー源の割合を指定した値に誘導します。実際の配分は文明の技術レベルとエネルギー効率に依存し、他のエネルギー源の使用ができない場合はバイオマスを消費します。" 85 | -------------------------------------------------------------------------------- /assets/text/ja/planets.toml: -------------------------------------------------------------------------------- 1 | [habitability] 2 | ideal = "理想的" 3 | adequate = "適切" 4 | poor = "貧弱" 5 | hostile = "過酷" 6 | 7 | [planet] 8 | custom = "カスタム" 9 | continental = "大陸惑星" 10 | ice = "氷惑星" 11 | desert = "砂漠惑星" 12 | 13 | [planet.desc] 14 | continental = "海洋と大陸の両方が形成された惑星です。酸素こそ存在しないものの、海洋によって生命の維持は容易です。最もテラフォーミングに向いた天体です。" 15 | ice = "氷に覆われた惑星です。その低い温度のため生命は存在しませんが、なんらかの方法で気温を上げれば、生命に欠かせない水を得ることができます。" 16 | desert = "水がほとんど存在しない惑星で、その表面は乾燥した砂漠です。水を供給しなければ生命は存在し得ないでしょう。" 17 | -------------------------------------------------------------------------------- /assets/text/ja/report.toml: -------------------------------------------------------------------------------- 1 | [report] 2 | warn-high-temp = "気温が高すぎます。気温を下げるには、軌道上建造物である軌道ミラーを稼働させ、地表に届く太陽光を遮蔽することが効果的です。" 3 | warn-low-carbon-dioxide = "大気中に十分な二酸化炭素がありません。植物の生長が阻害され、寒冷化をもたらす場合もあります。炭素輸送機を稼働させるか、埋没炭素を燃焼させる必要があります。" 4 | warn-low-oxygen = "大気中に十分な酸素がありません。酸素発生機や炭素還元機を設置しましょう。" 5 | warn-low-temp = "気温が低すぎます。気温を上げるには、軌道上建造物である軌道ミラーを稼働させ、地表に届く太陽光を増やすことが効果的です。" 6 | civilized = "{$animal}の文明化が完了しました" 7 | civ-advance = "{$civ} が {$age}時代 に進歩しました" 8 | civ-extinct = "{$civ} が滅亡しました" 9 | civ-decadence = "{$civ} の退廃が始まりました" 10 | inter-species-war = "{$civ_a}と{$civ_b}が戦争状態に入りました" 11 | inter-species-war-ceased = "{$civ_a}と{$civ_b}の戦争が終結しました" 12 | nuclear-war = "核戦争が始まった" 13 | -------------------------------------------------------------------------------- /assets/text/ja/tutorial.toml: -------------------------------------------------------------------------------- 1 | [tutorial] 2 | start-0 = "あなたは長い旅路の末、テラフォーミングに適した惑星へ到着しました。この惑星の管理者となり、テラフォーミングにより生命に適した環境へと作り変えましょう。" 3 | start-1-1 = "惑星上で視点を移動するには、WASDもしくは方向キーを使います。" 4 | start-1-2 = "マウスのミドルクリックでカーソルのあるタイルに視点を移動し、ホイールでズームイン・ズームアウトができます。" 5 | start-1-3 = "また、画面右下のマップでは、惑星全体の状態を把握できます。マップを閉じている場合は画面右下の以下のアイコンをクリックして下さい。" 6 | 7 | power-0-1 = "テラフォーミングに用いる施設を稼働させるには、十分な電力が必要です。現在どれだけの電力が消費・生産されているかは、画面上部に以下のように示されています。" 8 | power-0-2 = "施設の建設には素材が必要です。現在どれだけの素材が貯蔵・生産されているかは、画面上部で以下のように示されています。" 9 | power-1-1 = "電力や素材の生産量を増やすには、画面左上にある以下のアイコンをクリックして建造物ウィンドウを開き、核融合炉と小惑星採掘ステーションを建設します。" 10 | power-1-2 = "「+1」と書かれたボタンをクリックして、指定された数だけ建物を建造しましょう。" 11 | power-checklist1 = "核融合炉を5つ以上保有する" 12 | power-checklist2 = "小惑星採掘ステーションを5つ以上保有する" 13 | 14 | fertilize-0-1 = "ある程度の電力が確保できたら、惑星の緑化を始めましょう。\n植物が育つためには、陸上で、さらにほどよい気温と降水という条件が必要です。カーソル下のタイルの気温と降水量を画面右上のインジケーターで確認できるので、それで植物に適したタイルを探して下さい。気温と降水量は以下のアイコンで表されています。" 15 | fertilize-0-2 = "気温の目安は10〜30°C、降水量の目安は500mm以上です。" 16 | fertilize-1-1 = "良い条件のタイルを見つけたら、画面上部のツールバーの以下のアイコンをクリックしてメニューを開き、肥沃化工場を選択します。そして、目的のタイルをクリックして建設して下さい。" 17 | fertilize-1-2 = "肥沃化工場は距離を離していくつか建設しましょう。建設し終わったら、ツールバーの以下のアイコンをクリックして時間を進めましょう。" 18 | fertilize-1-3 = "時間が経つと、以下の例のように草原が広がっていきます。いつまで経っても草原が生成されない場合、気温と降水量を再度確認しましょう。" 19 | fertilize-checklist1 = "地上に肥沃化工場を3つ以上建設する" 20 | fertilize-checklist2 = "草原が10タイル以上に広がる" 21 | 22 | build-oxygen-0-1 = "おめでとうございます。惑星に生命が根付き始めました。次は、大気の状態を確認しましょう。画面左下にある以下のアイコンをクリックして統計ウィンドウを開き、「大気」のタブをクリックしましょう。" 23 | build-oxygen-0-2 = "森林や動物といった高度な生態系が形成されるには、惑星の大気にある程度の酸素が必要です。酸素が少ない場合は、酸素発生機を建設しましょう。" 24 | build-oxygen-1-1 = "酸素発生機の稼働には、潤沢な電力が必要です。画面左上にある以下のアイコンをクリックして建造物ウィンドウを開き、ダイソンスウォームユニットを建設して電力を確保します。" 25 | build-oxygen-1-2 = "建設に必要な素材が足りない場合は、小惑星採掘ステーションを建設して時間を進めて下さい。\n電力を確保したら、ツールバーの以下のアイコンをクリックしてメニューを開き、酸素発生機を選択します。そして、空いているタイルをクリックして建設して下さい。" 26 | build-oxygen-checklist1 = "ダイソンスウォームユニットを5つ以上保有する" 27 | build-oxygen-checklist2 = "地上に酸素発生機を8つ以上建設する" 28 | 29 | wait-oxygen-0-1 = "酸素発生機を建設したら、酸素濃度が上がり、森林が形成されるまで待ちましょう。肥沃度の高いタイルは、植物が成長するにつれバイオマスが増え、やがて森林へと変化します。\nこの過程は時間がかかるので、ツールバーの以下のアイコンをクリックして速く時間を進めましょう。" 30 | wait-oxygen-0-2 = "この過程を早く終わらせたい場合、素材が貯まり次第、ダイソンスウォームユニットと酸素発生機を追加しましょう。" 31 | wait-oxygen-checklist1 = "酸素の分圧が0.12 atmに達する" 32 | wait-oxygen-checklist2 = "森林が50タイル以上に広がる" 33 | 34 | carbon-0-1 = "この惑星の大気は、少し二酸化炭素が多いようです。二酸化炭素はやがて植物が吸収してくれますが、炭素還元機を使えばより速く二酸化炭素を取り除くことができます。取り除かれた炭素は地下に埋没炭素として蓄えられ、後の時代に文明が化石燃料として利用されます。\nツールバーの以下のアイコンをクリックしてメニューを開き、炭素還元機を選択します。そして、空いているタイルをクリックして建設して下さい。" 35 | carbon-0-2 = "建設する電力が足りない場合は、ダイソンスウォームユニットを追加するか、酸素発生機を撤去します。撤去は建設と同じメニューから選択できます。" 36 | carbon-checklist1 = "地上に炭素還元機を2つ以上建設する" 37 | 38 | animal-0-1 = "もし現状、十分な森林が形成されているなら、好みの動物を繁殖させるために必要な条件は整っていると言えるでしょう。動物の配置には遺伝子ポイントが必要です。現在どれだけの遺伝子ポイントが貯められているかは、画面上部に以下で以下のように示されています。" 39 | animal-0-2 = "遺伝子ポイントは時間経過で少しずつ増加します。その増加量は惑星上の生物が増えると大きくなります。" 40 | animal-1-1 = "動物を配置するには、画面左上にある以下のアイコンをクリックして動物ウィンドウを表示します。" 41 | animal-1-2 = "動物のリストから「キツネ」を選択し、配置ボタンを押してからタイルをクリックすることでキツネを配置することができます。\n動物を配置するとき、配置するタイルはその動物に適した環境である必要があります。キツネの場合は温帯樹林です。適した環境に配置すれば、時間経過で周りのタイルにも繁殖していきます。" 42 | animal-checklist1 = "キツネが30タイル以上に居住" 43 | 44 | civilize-0-1 = "適正のある動物には、文明化のプロセスにより文明をもたせることができます。画面上部のツールバーの以下のアイコンをクリックしてメニューを開き、文明化を選択します。" 45 | civilize-0-2 = "そこでキツネのような文明化に適した動物のいるタイルをクリックすると、文明化された動物の都市が出現します。" 46 | civilize-checklist1 = "惑星上に文明の都市が存在する" 47 | 48 | control-civ-0-1 = "惑星上の文明に干渉し、その方向性を変化させることが可能です。画面左上にある以下のアイコンをクリックしてコントロールウィンドウを表示し、「文明」のタブをクリックします。" 49 | control-civ-0-2 = "試しに、人口増加のスライダーを150%以上に調節してみましょう。これで文明の人口がより増えやすくなります。\n戦争の頻度やエネルギー源も調節できます。" 50 | control-civ-checklist1 = "文明の人口増加を150%以上に調節する" 51 | 52 | orbital-mirror-0-1 = "惑星上の生命や文明のために、その気温を即座に細かく調整する場合、軌道ミラーが最も適した方法です。画面左上にある以下のアイコンをクリックして建造物ウィンドウを開き、軌道ミラーの項目で「+1」と書かれたボタンをクリックし建設しましょう。" 53 | orbital-mirror-0-2 = "軌道ミラーは、恒星からの光を直接調節する建造物です。軌道ミラーの項目にあるスライドバーから調節できます。\n光量の変化はわずかな割合でも惑星に多大な影響をあたえるので、まずは-10〜-5%の範囲に動かして、少し温度を下げてみましょう。\nそして惑星全体の平均温度や生物量がどう変化したかは、統計ウィンドウにある「歴史」のタブからグラフで確認できます。統計ウィンドウは画面左下の以下のアイコンをクリックすると開けます。" 54 | orbital-mirror-checklist1 = "軌道ミラーを保有する" 55 | orbital-mirror-checklist2 = "軌道ミラーを-10〜-5%の範囲に調節する" 56 | 57 | complete-0-1 = "おめでとうございます。文明が発展できるほど惑星の環境を整えることができました。このまま惑星の環境をさらに改善するのも、動物や文明を見守るのも、または滅ぼしてしまうのもあなたの自由です。\nチュートリアルはこれで終わりですが、このまま続けてもできます。他の惑星から開始する場合は、メインメニューから「新規」を選ぶことができます。" 58 | -------------------------------------------------------------------------------- /assets/tile_animations/aerosol-injection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/tile_animations/aerosol-injection.png -------------------------------------------------------------------------------- /assets/tile_animations/black-dust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/tile_animations/black-dust.png -------------------------------------------------------------------------------- /assets/tile_animations/decadence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/tile_animations/decadence.png -------------------------------------------------------------------------------- /assets/tile_animations/fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/tile_animations/fire.png -------------------------------------------------------------------------------- /assets/tile_animations/nuclear-explosion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/tile_animations/nuclear-explosion.png -------------------------------------------------------------------------------- /assets/tile_animations/plague.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/tile_animations/plague.png -------------------------------------------------------------------------------- /assets/tile_animations/troop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/tile_animations/troop.png -------------------------------------------------------------------------------- /assets/tile_animations/vehicle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/tile_animations/vehicle.png -------------------------------------------------------------------------------- /assets/tile_animations/war.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/tile_animations/war.png -------------------------------------------------------------------------------- /assets/ui/achivement-abundant-power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-abundant-power.png -------------------------------------------------------------------------------- /assets/ui/achivement-animals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-animals.png -------------------------------------------------------------------------------- /assets/ui/achivement-civilize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-civilize.png -------------------------------------------------------------------------------- /assets/ui/achivement-desert-greening.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-desert-greening.png -------------------------------------------------------------------------------- /assets/ui/achivement-destroy-planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-destroy-planet.png -------------------------------------------------------------------------------- /assets/ui/achivement-forests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-forests.png -------------------------------------------------------------------------------- /assets/ui/achivement-giant-mirror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-giant-mirror.png -------------------------------------------------------------------------------- /assets/ui/achivement-grasslands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-grasslands.png -------------------------------------------------------------------------------- /assets/ui/achivement-green-planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-green-planet.png -------------------------------------------------------------------------------- /assets/ui/achivement-industrial-revolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-industrial-revolution.png -------------------------------------------------------------------------------- /assets/ui/achivement-inter-species-war.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-inter-species-war.png -------------------------------------------------------------------------------- /assets/ui/achivement-locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-locked.png -------------------------------------------------------------------------------- /assets/ui/achivement-low-carbon-dioxide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-low-carbon-dioxide.png -------------------------------------------------------------------------------- /assets/ui/achivement-magical-energy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-magical-energy.png -------------------------------------------------------------------------------- /assets/ui/achivement-melted-ice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-melted-ice.png -------------------------------------------------------------------------------- /assets/ui/achivement-pandemic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-pandemic.png -------------------------------------------------------------------------------- /assets/ui/achivement-step-toward-ecumenopolis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/achivement-step-toward-ecumenopolis.png -------------------------------------------------------------------------------- /assets/ui/background-building-planet-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/background-building-planet-left.png -------------------------------------------------------------------------------- /assets/ui/background-building-planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/background-building-planet.png -------------------------------------------------------------------------------- /assets/ui/background-building-space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/background-building-space.png -------------------------------------------------------------------------------- /assets/ui/background-building-star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/background-building-star.png -------------------------------------------------------------------------------- /assets/ui/building-asteroid-mining-station.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/building-asteroid-mining-station.png -------------------------------------------------------------------------------- /assets/ui/building-carbon-importer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/building-carbon-importer.png -------------------------------------------------------------------------------- /assets/ui/building-dyson-swarm-unit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/building-dyson-swarm-unit.png -------------------------------------------------------------------------------- /assets/ui/building-fusion-reactor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/building-fusion-reactor.png -------------------------------------------------------------------------------- /assets/ui/building-ice-importer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/building-ice-importer.png -------------------------------------------------------------------------------- /assets/ui/building-ion-irradiator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/building-ion-irradiator.png -------------------------------------------------------------------------------- /assets/ui/building-nitrogen-importer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/building-nitrogen-importer.png -------------------------------------------------------------------------------- /assets/ui/building-orbital-mirror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/building-orbital-mirror.png -------------------------------------------------------------------------------- /assets/ui/icon-achivements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-achivements.png -------------------------------------------------------------------------------- /assets/ui/icon-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-action.png -------------------------------------------------------------------------------- /assets/ui/icon-age-atomic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-age-atomic.png -------------------------------------------------------------------------------- /assets/ui/icon-age-bronze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-age-bronze.png -------------------------------------------------------------------------------- /assets/ui/icon-age-early-space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-age-early-space.png -------------------------------------------------------------------------------- /assets/ui/icon-age-industrial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-age-industrial.png -------------------------------------------------------------------------------- /assets/ui/icon-age-iron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-age-iron.png -------------------------------------------------------------------------------- /assets/ui/icon-age-stone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-age-stone.png -------------------------------------------------------------------------------- /assets/ui/icon-air-temperature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-air-temperature.png -------------------------------------------------------------------------------- /assets/ui/icon-animal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-animal.png -------------------------------------------------------------------------------- /assets/ui/icon-argon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-argon.png -------------------------------------------------------------------------------- /assets/ui/icon-biomass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-biomass.png -------------------------------------------------------------------------------- /assets/ui/icon-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-build.png -------------------------------------------------------------------------------- /assets/ui/icon-carbon-dioxide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-carbon-dioxide.png -------------------------------------------------------------------------------- /assets/ui/icon-carbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-carbon.png -------------------------------------------------------------------------------- /assets/ui/icon-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-check.png -------------------------------------------------------------------------------- /assets/ui/icon-city.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-city.png -------------------------------------------------------------------------------- /assets/ui/icon-civilization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-civilization.png -------------------------------------------------------------------------------- /assets/ui/icon-cloud-albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-cloud-albedo.png -------------------------------------------------------------------------------- /assets/ui/icon-control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-control.png -------------------------------------------------------------------------------- /assets/ui/icon-coordinates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-coordinates.png -------------------------------------------------------------------------------- /assets/ui/icon-cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-cross.png -------------------------------------------------------------------------------- /assets/ui/icon-cycles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-cycles.png -------------------------------------------------------------------------------- /assets/ui/icon-energy-source-biomass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-energy-source-biomass.png -------------------------------------------------------------------------------- /assets/ui/icon-energy-source-fossil-fuel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-energy-source-fossil-fuel.png -------------------------------------------------------------------------------- /assets/ui/icon-energy-source-gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-energy-source-gift.png -------------------------------------------------------------------------------- /assets/ui/icon-energy-source-hydro-geothermal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-energy-source-hydro-geothermal.png -------------------------------------------------------------------------------- /assets/ui/icon-energy-source-nuclear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-energy-source-nuclear.png -------------------------------------------------------------------------------- /assets/ui/icon-energy-source-solar-wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-energy-source-solar-wind.png -------------------------------------------------------------------------------- /assets/ui/icon-fertility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-fertility.png -------------------------------------------------------------------------------- /assets/ui/icon-game-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-game-menu.png -------------------------------------------------------------------------------- /assets/ui/icon-gene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-gene.png -------------------------------------------------------------------------------- /assets/ui/icon-height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-height.png -------------------------------------------------------------------------------- /assets/ui/icon-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-help.png -------------------------------------------------------------------------------- /assets/ui/icon-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-layers.png -------------------------------------------------------------------------------- /assets/ui/icon-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-map.png -------------------------------------------------------------------------------- /assets/ui/icon-material.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-material.png -------------------------------------------------------------------------------- /assets/ui/icon-nitrogen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-nitrogen.png -------------------------------------------------------------------------------- /assets/ui/icon-oxygen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-oxygen.png -------------------------------------------------------------------------------- /assets/ui/icon-planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-planet.png -------------------------------------------------------------------------------- /assets/ui/icon-population.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-population.png -------------------------------------------------------------------------------- /assets/ui/icon-power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-power.png -------------------------------------------------------------------------------- /assets/ui/icon-radius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-radius.png -------------------------------------------------------------------------------- /assets/ui/icon-rainfall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-rainfall.png -------------------------------------------------------------------------------- /assets/ui/icon-reports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-reports.png -------------------------------------------------------------------------------- /assets/ui/icon-sea-temperature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-sea-temperature.png -------------------------------------------------------------------------------- /assets/ui/icon-solar-constant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-solar-constant.png -------------------------------------------------------------------------------- /assets/ui/icon-space-buildings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-space-buildings.png -------------------------------------------------------------------------------- /assets/ui/icon-speed-fast-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-speed-fast-selected.png -------------------------------------------------------------------------------- /assets/ui/icon-speed-fast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-speed-fast.png -------------------------------------------------------------------------------- /assets/ui/icon-speed-medium-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-speed-medium-selected.png -------------------------------------------------------------------------------- /assets/ui/icon-speed-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-speed-medium.png -------------------------------------------------------------------------------- /assets/ui/icon-speed-paused-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-speed-paused-selected.png -------------------------------------------------------------------------------- /assets/ui/icon-speed-paused.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-speed-paused.png -------------------------------------------------------------------------------- /assets/ui/icon-speed-slow-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-speed-slow-selected.png -------------------------------------------------------------------------------- /assets/ui/icon-speed-slow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-speed-slow.png -------------------------------------------------------------------------------- /assets/ui/icon-stat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/icon-stat.png -------------------------------------------------------------------------------- /assets/ui/tile-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tile-colored.png -------------------------------------------------------------------------------- /assets/ui/tile-cursor-bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tile-cursor-bold.png -------------------------------------------------------------------------------- /assets/ui/tile-cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tile-cursor.png -------------------------------------------------------------------------------- /assets/ui/tutorial-animal-habitat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tutorial-animal-habitat.png -------------------------------------------------------------------------------- /assets/ui/tutorial-carbon-capturer-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tutorial-carbon-capturer-example.png -------------------------------------------------------------------------------- /assets/ui/tutorial-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tutorial-icon.png -------------------------------------------------------------------------------- /assets/ui/tutorial-mouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tutorial-mouse.png -------------------------------------------------------------------------------- /assets/ui/tutorial-move-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tutorial-move-keys.png -------------------------------------------------------------------------------- /assets/ui/tutorial-oxygen-generator-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tutorial-oxygen-generator-example.png -------------------------------------------------------------------------------- /assets/ui/tutorial-soil-fertilize-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/assets/ui/tutorial-soil-fertilize-example.png -------------------------------------------------------------------------------- /gaia-maker.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Name=Gaia Maker 4 | Comment=Planet and terraforming simulation game 5 | Exec=gaia-maker 6 | TryExec=gaia-maker 7 | Icon=gaia-maker 8 | StartupNotify=false 9 | Terminal=false 10 | Type=Application 11 | Categories=Game 12 | SingleMainWindow=true 13 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garkimasera/gaia-maker/e2f4c9c74379dfedbd7826820ccaf1ff67e5462f/icon.png -------------------------------------------------------------------------------- /src/achivement_save.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use anyhow::Context; 4 | use base64::Engine; 5 | use bevy::prelude::*; 6 | use fnv::FnvHashSet; 7 | use num_traits::FromPrimitive; 8 | 9 | use crate::{ 10 | GameState, 11 | audio::SoundEffectPlayer, 12 | planet::{Achivement, Params, Planet, Sim, check_achivements}, 13 | }; 14 | 15 | #[derive(Debug, Resource)] 16 | pub struct UnlockedAchivements(pub FnvHashSet); 17 | 18 | #[derive(Default, Debug, Resource)] 19 | pub struct AchivementNotification { 20 | pub achivement: Option, 21 | timer: Option, 22 | } 23 | 24 | const ACHIVEMENT_FILE_NAME: &str = "saves/achivements"; 25 | 26 | const CHECK_ACHIVEMENT_INTERVAL_CYCLES: u64 = 10; 27 | 28 | const ACHIVEMENT_NOTIFICATION_DURATION: Duration = Duration::from_secs(5); 29 | 30 | #[derive(Debug)] 31 | pub struct AchivementPlugin; 32 | 33 | impl Plugin for AchivementPlugin { 34 | fn build(&self, app: &mut App) { 35 | app.init_resource::() 36 | .add_systems(OnExit(GameState::AssetLoading), load_unlocked_achivement) 37 | .add_systems( 38 | FixedUpdate, 39 | check_periodic.run_if(in_state(GameState::Running)), 40 | ); 41 | } 42 | } 43 | 44 | fn load_unlocked_achivement(mut command: Commands) { 45 | let mut achivements = FnvHashSet::default(); 46 | 47 | match crate::platform::read_data_file(ACHIVEMENT_FILE_NAME) 48 | .and_then(|data| { 49 | base64::prelude::BASE64_STANDARD 50 | .decode(data) 51 | .context("invalid achivement data") 52 | }) 53 | .and_then(|data| { 54 | rmp_serde::from_slice::>(&data).context("deserialize achivement data") 55 | }) { 56 | Ok(achivement_data) => { 57 | for achivement_number in achivement_data { 58 | if let Some(achivement) = Achivement::from_u16(achivement_number) { 59 | achivements.insert(achivement); 60 | } else { 61 | log::warn!("unknown achivement in file: {}", achivement_number); 62 | } 63 | } 64 | } 65 | Err(e) => { 66 | log::warn!("cannot load achivement data: {:?}", e); 67 | } 68 | } 69 | command.insert_resource(UnlockedAchivements(achivements)); 70 | } 71 | 72 | fn check_periodic( 73 | planet: Res, 74 | params: Res, 75 | mut unlocked_achivements: ResMut, 76 | mut achivement_notification: ResMut, 77 | mut sim: ResMut, 78 | se_player: SoundEffectPlayer, 79 | time: Res>, 80 | ) { 81 | if let Some(timer) = &mut achivement_notification.timer { 82 | timer.tick(time.delta()); 83 | if timer.finished() { 84 | *achivement_notification = AchivementNotification::default(); 85 | } 86 | } 87 | 88 | if planet.cycles % CHECK_ACHIVEMENT_INTERVAL_CYCLES != 0 { 89 | return; 90 | } 91 | 92 | check_achivements( 93 | &planet, 94 | &unlocked_achivements.0, 95 | &mut sim.new_achievements, 96 | ¶ms, 97 | ); 98 | 99 | let mut unlocked = false; 100 | 101 | for new_achivement in sim.new_achievements.drain() { 102 | if unlocked_achivements.0.contains(&new_achivement) { 103 | continue; 104 | } 105 | 106 | log::info!("get achivement {:?}", new_achivement); 107 | unlocked_achivements.0.insert(new_achivement); 108 | unlocked = true; 109 | achivement_notification.achivement = Some(new_achivement); 110 | achivement_notification.timer = Some(Timer::new( 111 | ACHIVEMENT_NOTIFICATION_DURATION, 112 | TimerMode::Once, 113 | )); 114 | } 115 | 116 | if unlocked { 117 | se_player.play("achivement"); 118 | 119 | let list: Vec = unlocked_achivements 120 | .0 121 | .iter() 122 | .map(|achivement| *achivement as u16) 123 | .collect(); 124 | let data = rmp_serde::to_vec(&list).expect("serialize achivement data"); 125 | let data = base64::prelude::BASE64_STANDARD.encode(data); 126 | if let Err(e) = crate::platform::write_data_file(ACHIVEMENT_FILE_NAME, &data) { 127 | log::warn!("cannot write achivement data: {}", e); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/conf.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use bevy::prelude::*; 3 | use serde::{Deserialize, Serialize}; 4 | use strum::{AsRefStr, EnumIter}; 5 | 6 | use crate::GameState; 7 | use crate::{assets::UiAssets, text_assets::Lang}; 8 | 9 | pub const CONF_FILE_NAME: &str = "conf.toml"; 10 | 11 | #[derive(Clone, Copy, Debug)] 12 | pub struct ConfPlugin; 13 | 14 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, SystemSet)] 15 | pub struct ConfLoadSystemSet; 16 | 17 | impl Plugin for ConfPlugin { 18 | fn build(&self, app: &mut App) { 19 | app.add_event::() 20 | .add_plugins(bevy_common_assets::toml::TomlAssetPlugin::::new(&[ 21 | "conf.toml", 22 | ])) 23 | .add_systems(Update, on_change) 24 | .add_systems( 25 | OnExit(GameState::AssetLoading), 26 | load_conf.in_set(ConfLoadSystemSet), 27 | ); 28 | } 29 | } 30 | 31 | fn on_change(mut er_conf_change: EventReader, conf: Option>) { 32 | if let Some(conf) = conf { 33 | if er_conf_change.read().last().is_some() { 34 | let conf = toml::to_string(&*conf).unwrap(); 35 | if let Err(e) = crate::platform::write_data_file(CONF_FILE_NAME, &conf) { 36 | log::error!("cannot save conf: {}", e); 37 | } 38 | log::info!("conf saved"); 39 | } 40 | } 41 | } 42 | 43 | fn load_conf(mut command: Commands, ui_assets: Res, conf: Res>) { 44 | let conf = match crate::platform::read_data_file(CONF_FILE_NAME) 45 | .and_then(|data| toml::from_str(&data).context("deserialize conf")) 46 | { 47 | Ok(conf) => conf, 48 | Err(e) => { 49 | log::info!("cannot load config: {}", e); 50 | conf.get(&ui_assets.default_conf).unwrap().clone() 51 | } 52 | }; 53 | let conf = crate::platform::modify_conf(conf); 54 | crate::text_assets::set_lang(conf.lang); 55 | command.insert_resource(conf); 56 | } 57 | 58 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Asset, Resource, TypePath)] 59 | pub struct Conf { 60 | pub lang: Lang, 61 | pub camera_move_speed: f32, 62 | pub ui: UiConf, 63 | pub autosave_enabled: bool, 64 | pub autosave_cycle_duration: u64, 65 | pub autosave_max_files: usize, 66 | pub manual_max_files: usize, 67 | pub report_lifespan: u64, 68 | pub screen_refresh_rate: HighLow3, 69 | pub show_fps: bool, 70 | pub sound_effect_volume: u8, 71 | pub bgm_volume: u8, 72 | pub slow_speed_sim_duration_ms: u64, 73 | pub medium_speed_sim_duration_ms: u64, 74 | pub window: Option, 75 | } 76 | 77 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] 78 | pub struct UiConf { 79 | pub scale_factor: f32, 80 | pub font_scale: f32, 81 | pub reports_in_list: usize, 82 | pub min_sidebar_width: f32, 83 | } 84 | 85 | #[derive(Clone, PartialEq, Debug)] 86 | #[derive(Serialize, Deserialize, Asset, Resource, TypePath)] 87 | pub struct WindowConf { 88 | pub size: (u32, u32), 89 | #[serde(default)] 90 | pub maximized: bool, 91 | } 92 | 93 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] 94 | #[derive(Serialize, Deserialize, AsRefStr, EnumIter)] 95 | #[serde(rename_all = "snake_case")] 96 | #[strum(serialize_all = "kebab-case")] 97 | pub enum HighLow3 { 98 | Low, 99 | Medium, 100 | High, 101 | } 102 | 103 | #[derive(Clone, Copy, Debug, Event)] 104 | pub struct ConfChange; 105 | 106 | impl Default for ConfChange { 107 | fn default() -> Self { 108 | ConfChange 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/gz.rs: -------------------------------------------------------------------------------- 1 | use bevy::asset::io::Reader; 2 | use bevy::asset::{AssetLoader, LoadContext}; 3 | use bevy::prelude::*; 4 | use flate2::read::GzDecoder; 5 | use std::io::Read; 6 | 7 | pub struct GzPlugin; 8 | 9 | #[derive(Clone, PartialEq, Eq, Debug, Asset, TypePath)] 10 | pub struct GunzipBin(pub Vec); 11 | 12 | impl Plugin for GzPlugin { 13 | fn build(&self, app: &mut App) { 14 | app.init_asset::().register_asset_loader(GzLoader); 15 | } 16 | } 17 | 18 | struct GzLoader; 19 | 20 | impl AssetLoader for GzLoader { 21 | type Asset = GunzipBin; 22 | type Settings = (); 23 | type Error = anyhow::Error; 24 | 25 | async fn load( 26 | &self, 27 | reader: &mut dyn Reader, 28 | _settings: &Self::Settings, 29 | _load_context: &mut LoadContext<'_>, 30 | ) -> Result { 31 | let mut bytes = Vec::new(); 32 | reader.read_to_end(&mut bytes).await?; 33 | let mut gz = GzDecoder::new(&bytes[..]); 34 | let mut decoded = Vec::new(); 35 | gz.read_to_end(&mut decoded)?; 36 | 37 | Ok(GunzipBin(decoded)) 38 | } 39 | 40 | fn extensions(&self) -> &[&str] { 41 | &["gz"] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/image_assets.rs: -------------------------------------------------------------------------------- 1 | use bevy::asset::io::Reader; 2 | use bevy::asset::{AssetLoader, LoadContext}; 3 | use bevy::prelude::*; 4 | use image::GenericImage; 5 | 6 | const MONOCHROME_IMAGE_DIRS: &[&str] = &["animals", "biomes", "structures", "tile_animations"]; 7 | 8 | #[derive(Clone, Copy, Debug)] 9 | pub struct ImageAssetsPlugin; 10 | 11 | impl Plugin for ImageAssetsPlugin { 12 | fn build(&self, app: &mut App) { 13 | app.register_asset_loader(ImageLoader); 14 | } 15 | } 16 | 17 | struct ImageLoader; 18 | 19 | impl AssetLoader for ImageLoader { 20 | type Asset = Image; 21 | type Settings = (); 22 | type Error = anyhow::Error; 23 | 24 | async fn load( 25 | &self, 26 | reader: &mut dyn Reader, 27 | _settings: &Self::Settings, 28 | load_context: &mut LoadContext<'_>, 29 | ) -> Result { 30 | let mut bytes = Vec::new(); 31 | reader.read_to_end(&mut bytes).await?; 32 | 33 | let image = 34 | image::load_from_memory_with_format(&bytes, image::ImageFormat::Png)?.into_rgba8(); 35 | 36 | let path = load_context.path(); 37 | let image = if MONOCHROME_IMAGE_DIRS.iter().any(|dir| path.starts_with(dir)) { 38 | let width = image.width(); 39 | let height = image.height(); 40 | let mut new_image = image::RgbaImage::new(width, height * 2); 41 | new_image.copy_from(&image, 0, 0)?; 42 | 43 | for y in 0..height { 44 | for x in 0..width { 45 | let pixel = image.get_pixel(x, y); 46 | let v = ((pixel.0[0] as u32 + pixel.0[1] as u32 + pixel.0[2] as u32) / 3) as u8; 47 | new_image.put_pixel(x, height + y, image::Rgba([v, v, v, pixel.0[3]])); 48 | } 49 | } 50 | new_image 51 | } else { 52 | image 53 | }; 54 | 55 | let width = image.width(); 56 | let height = image.height(); 57 | 58 | Ok(Image::new( 59 | bevy::render::render_resource::Extent3d { 60 | width, 61 | height, 62 | depth_or_array_layers: 1, 63 | }, 64 | bevy::render::render_resource::TextureDimension::D2, 65 | image.into_raw(), 66 | bevy::render::render_resource::TextureFormat::Rgba8UnormSrgb, 67 | bevy::asset::RenderAssetUsages::default(), 68 | )) 69 | } 70 | 71 | fn extensions(&self) -> &[&str] { 72 | &["png"] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity, clippy::comparison_chain)] 2 | 3 | extern crate tile_geom as geom; 4 | 5 | #[macro_use] 6 | mod tools; 7 | #[macro_use] 8 | mod text_assets; 9 | 10 | mod achivement_save; 11 | mod action; 12 | mod assets; 13 | mod audio; 14 | mod conf; 15 | mod draw; 16 | mod gz; 17 | mod image_assets; 18 | mod manage_planet; 19 | mod overlay; 20 | mod planet; 21 | mod platform; 22 | mod saveload; 23 | mod screen; 24 | mod text; 25 | mod title_screen; 26 | mod tutorial; 27 | mod ui; 28 | 29 | use std::path::PathBuf; 30 | 31 | use bevy::{ 32 | prelude::*, 33 | window::{PresentMode, WindowResolution}, 34 | winit::WinitSettings, 35 | }; 36 | use clap::Parser; 37 | 38 | const APP_NAME: &str = concat!("Gaia Maker v", env!("CARGO_PKG_VERSION")); 39 | 40 | #[derive(Clone, Parser, Debug)] 41 | #[clap(author, about, version)] 42 | struct Args { 43 | /// Specify the number of threads used to run this game 44 | #[arg(long, default_value_t = 4)] 45 | num_threads: u8, 46 | /// Log file path 47 | #[arg(long)] 48 | log_file: Option, 49 | #[arg(long)] 50 | launcher_port: Option, 51 | } 52 | 53 | fn main() { 54 | let args = Args::parse(); 55 | crate::platform::init_rayon(args.num_threads as usize); 56 | if let Some(log_file) = args.log_file { 57 | crate::platform::init_log_file(log_file); 58 | } 59 | if let Some(port) = args.launcher_port { 60 | crate::platform::client::run_client(port); 61 | } 62 | crate::platform::window_open(); 63 | 64 | let mut window = Window { 65 | title: APP_NAME.into(), 66 | present_mode: PresentMode::Fifo, 67 | canvas: Some("#game-screen".into()), 68 | ..default() 69 | }; 70 | match crate::platform::PreferredWindowResolution::get() { 71 | platform::PreferredWindowResolution::Size(w, h) => { 72 | window.resolution = WindowResolution::new(w as f32, h as f32); 73 | } 74 | platform::PreferredWindowResolution::Maximized => { 75 | window.set_maximized(true); 76 | } 77 | } 78 | 79 | App::new() 80 | .add_plugins(AssetPlugin) 81 | .add_plugins( 82 | DefaultPlugins 83 | .set(bevy::log::LogPlugin { 84 | custom_layer: crate::platform::log_plugin_custom_layer, 85 | ..default() 86 | }) 87 | .set(TaskPoolPlugin { 88 | task_pool_options: TaskPoolOptions::with_num_threads(args.num_threads as usize), 89 | }) 90 | .set(WindowPlugin { 91 | primary_window: Some(window), 92 | ..default() 93 | }), 94 | ) 95 | .add_plugins(bevy::diagnostic::FrameTimeDiagnosticsPlugin) 96 | .add_plugins(gz::GzPlugin) 97 | .add_plugins(text_assets::TextAssetsPlugin) 98 | .add_plugins(image_assets::ImageAssetsPlugin) 99 | .init_state::() 100 | .add_plugins(conf::ConfPlugin) 101 | .add_plugins(assets::AssetsPlugin) 102 | .add_plugins(overlay::OverlayPlugin) 103 | .add_plugins(screen::ScreenPlugin) 104 | .add_plugins(ui::UiPlugin) 105 | .add_plugins(audio::GameAudioPlugin) 106 | .add_plugins(title_screen::TitleScreenPlugin) 107 | .add_plugins(draw::DrawPlugin) 108 | .add_plugins(action::ActionPlugin) 109 | .add_plugins(manage_planet::ManagePlanetPlugin) 110 | .add_plugins(achivement_save::AchivementPlugin) 111 | .insert_resource(WinitSettings::game()) 112 | .init_resource::() 113 | .run(); 114 | } 115 | 116 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Hash, States)] 117 | pub enum GameState { 118 | #[default] 119 | AssetLoading, 120 | MainMenu, 121 | Running, 122 | } 123 | 124 | #[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Resource)] 125 | pub enum GameSpeed { 126 | #[default] 127 | Paused, 128 | Slow, 129 | Medium, 130 | Fast, 131 | } 132 | 133 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, SystemSet)] 134 | pub enum GameSystemSet { 135 | Draw, 136 | StartSim, 137 | UpdateHoverTile, 138 | } 139 | 140 | struct AssetPlugin; 141 | 142 | impl Plugin for AssetPlugin { 143 | #[cfg(feature = "asset_tar")] 144 | fn build(&self, app: &mut App) { 145 | use std::path::PathBuf; 146 | #[cfg(feature = "deb")] 147 | fn asset_file_path() -> PathBuf { 148 | let current_exe = std::env::current_exe().expect("cannot get current exe path"); 149 | let usr_dir = current_exe 150 | .parent() 151 | .expect("cannot get usr directory path") 152 | .parent() 153 | .expect("cannot get usr directory path"); 154 | usr_dir.join("share/games/gaia-maker/assets.tar.gz") 155 | } 156 | #[cfg(all(not(feature = "deb"), not(target_arch = "wasm32")))] 157 | fn asset_file_path() -> PathBuf { 158 | let current_exe = std::env::current_exe().expect("cannot get current exe path"); 159 | let dir = current_exe.parent().expect("invalid current exe path"); 160 | dir.join("assets.tar.gz") 161 | } 162 | #[cfg(all(not(feature = "deb"), target_arch = "wasm32"))] 163 | fn asset_file_path() -> PathBuf { 164 | "assets.tar".into() 165 | } 166 | 167 | app.add_plugins(bevy_asset_tar::AssetTarPlugin { 168 | archive_files: vec![asset_file_path()], 169 | addon_directories: platform::addon_directory(), 170 | ..Default::default() 171 | }); 172 | } 173 | 174 | #[cfg(not(feature = "asset_tar"))] 175 | fn build(&self, _app: &mut App) {} 176 | } 177 | -------------------------------------------------------------------------------- /src/planet/achivement.rs: -------------------------------------------------------------------------------- 1 | use fnv::FnvHashSet; 2 | use strum::{AsRefStr, EnumIter, EnumString}; 3 | 4 | use super::*; 5 | 6 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] 7 | #[derive(AsRefStr, EnumString, EnumIter, num_derive::FromPrimitive)] 8 | #[strum(serialize_all = "kebab-case")] 9 | #[repr(u16)] 10 | pub enum Achivement { 11 | Grasslands = 1, 12 | Forests, 13 | Animals, 14 | Civilize, 15 | GiantMirror, 16 | GreenPlanet, 17 | MeltedIce = 101, 18 | DesertGreening, 19 | IndustrialRevolution = 201, 20 | InterSpeciesWar, 21 | Pandemic, 22 | StepTowardEcumenopolis, 23 | MagicalEnergy, 24 | AbundantPower = 301, 25 | LowCarbonDioxide, 26 | DestroyPlanet, 27 | } 28 | 29 | pub static ACHIVEMENTS: std::sync::LazyLock> = 30 | std::sync::LazyLock::new(|| Achivement::iter().collect()); 31 | 32 | pub fn check_achivements( 33 | planet: &Planet, 34 | unlocked_achivements: &FnvHashSet, 35 | new_achivements: &mut FnvHashSet, 36 | params: &Params, 37 | ) { 38 | for achivement in Achivement::iter() { 39 | if !unlocked_achivements.contains(&achivement) && achivement.check(planet, params) { 40 | new_achivements.insert(achivement); 41 | } 42 | } 43 | } 44 | 45 | impl Achivement { 46 | fn check(self, planet: &Planet, params: &Params) -> bool { 47 | match self { 48 | Achivement::Grasslands => Requirement::BiomeTiles { 49 | biomes: vec![Biome::Grassland], 50 | n: 10, 51 | } 52 | .check(planet), 53 | Achivement::Forests => Requirement::BiomeTiles { 54 | biomes: vec![ 55 | Biome::BorealForest, 56 | Biome::TemperateForest, 57 | Biome::TropicalRainforest, 58 | ], 59 | n: 50, 60 | } 61 | .check(planet), 62 | Achivement::Animals => planet 63 | .map 64 | .iter() 65 | .flat_map(|tile| &tile.animal) 66 | .any(|animal| animal.is_some()), 67 | Achivement::Civilize => planet 68 | .map 69 | .iter() 70 | .any(|tile| matches!(tile.structure, Some(Structure::Settlement(_)))), 71 | Achivement::GreenPlanet => planet.stat.sum_biomass > 2_000_000.0, 72 | Achivement::MeltedIce => { 73 | planet.basics.origin == "ice" 74 | && planet 75 | .map 76 | .iter() 77 | .filter(|tile| matches!(tile.biome, Biome::IceSheet | Biome::SeaIce)) 78 | .count() 79 | == 0 80 | } 81 | Achivement::DesertGreening => { 82 | planet.basics.origin == "desert" && planet.stat.sum_biomass > 600_000.0 83 | } 84 | Achivement::IndustrialRevolution => planet.map.iter().any(|tile| { 85 | if let Some(Structure::Settlement(settlement)) = &tile.structure { 86 | settlement.age >= CivilizationAge::Industrial 87 | } else { 88 | false 89 | } 90 | }), 91 | Achivement::Pandemic => { 92 | planet 93 | .map 94 | .iter() 95 | .filter(|tile| tile.tile_events.get(TileEventKind::Plague).is_some()) 96 | .count() 97 | > 1000 98 | } 99 | Achivement::StepTowardEcumenopolis => { 100 | planet 101 | .map 102 | .iter() 103 | .filter(|tile| matches!(tile.structure, Some(Structure::Settlement(_)))) 104 | .count() 105 | > (planet.map.size().0 * planet.map.size().1 / 2) as usize 106 | && planet.civs.iter().map(|civ| civ.1.total_pop).sum::() > 7500000.0 107 | } 108 | Achivement::MagicalEnergy => planet.civs.iter().any(|(_, civ)| { 109 | civ.current_age() == CivilizationAge::Iron 110 | && civ.total_energy_consumption[EnergySource::Gift as usize] > 1.0 111 | }), 112 | Achivement::AbundantPower => planet.res.power >= 30000.0, 113 | Achivement::GiantMirror => Requirement::SpaceBuildingBuilt { 114 | kind: SpaceBuildingKind::OrbitalMirror, 115 | n: 1, 116 | } 117 | .check(planet), 118 | Achivement::LowCarbonDioxide => { 119 | planet.atmo.partial_pressure(GasKind::CarbonDioxide) < 30.0 * 1.0e-6 120 | } 121 | Achivement::DestroyPlanet => { 122 | if planet.stat.sum_biomass < 1.0 && planet.civs.is_empty() { 123 | if let Some(record) = planet.stat.record(3000, params) { 124 | record.biomass > 500_000.0 && record.pop.values().sum::() > 1000.0 125 | } else { 126 | false 127 | } 128 | } else { 129 | false 130 | } 131 | } 132 | _ => false, 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/planet/action.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl Planet { 4 | pub fn buildable(&self, building: &BuildingAttrs) -> Result<(), Cost> { 5 | if self.res.surplus_power() < -building.power { 6 | return Err(Cost::Power(building.power, 0)); 7 | } 8 | if building.cost > self.res.material { 9 | return Err(Cost::Material(building.cost)); 10 | } 11 | Ok(()) 12 | } 13 | 14 | pub fn placeable(&self, p: Coords) -> bool { 15 | if !self.map.in_range(p) { 16 | return false; 17 | } 18 | self.map[p].structure.is_none() 19 | } 20 | 21 | pub fn place(&mut self, p: Coords, structure: Structure, sim: &mut Sim, params: &Params) { 22 | assert!(self.placeable(p)); 23 | 24 | let kind = structure.kind(); 25 | self.map[p].structure = Some(structure); 26 | 27 | self.res.material -= params.structures[&kind].building.cost; 28 | self.update(sim, params); 29 | } 30 | 31 | pub fn demolition(&mut self, p: Coords, sim: &mut Sim, params: &Params) -> bool { 32 | let structure = &mut self.map[p].structure; 33 | if structure.is_some() && !matches!(structure, Some(Structure::Settlement(_))) { 34 | *structure = None; 35 | self.update(sim, params); 36 | true 37 | } else { 38 | false 39 | } 40 | } 41 | 42 | pub fn build_space_building( 43 | &mut self, 44 | kind: impl Into, 45 | sim: &mut Sim, 46 | params: &Params, 47 | ) { 48 | let kind = kind.into(); 49 | let cost = ¶ms.building_attrs(BuildingKind::Space(kind)).cost; 50 | self.res.material -= cost; 51 | let building = self.space_building_mut(kind); 52 | building.n += 1; 53 | 54 | if building.n == 1 { 55 | // Set initial control value at the first build 56 | match params.building_attrs(kind).control { 57 | BuildingControl::AlwaysEnabled => (), 58 | BuildingControl::IncreaseRate => { 59 | building.control = BuildingControlValue::IncreaseRate(0); 60 | } 61 | } 62 | } 63 | self.update(sim, params); 64 | } 65 | 66 | pub fn demolish_space_building( 67 | &mut self, 68 | kind: SpaceBuildingKind, 69 | n: u32, 70 | sim: &mut Sim, 71 | params: &Params, 72 | ) { 73 | let building = self.space_building_mut(kind); 74 | if building.n > n { 75 | building.n -= n; 76 | } else { 77 | building.n = 0; 78 | } 79 | 80 | if building.n == 0 { 81 | building.control = Default::default(); 82 | } 83 | 84 | self.update(sim, params); 85 | } 86 | 87 | pub fn cause_tile_event( 88 | &mut self, 89 | p: Coords, 90 | kind: TileEventKind, 91 | sim: &mut Sim, 92 | params: &Params, 93 | ) -> bool { 94 | let cost = params.event.tile_event_costs[&kind]; 95 | if self.res.enough_to_consume(cost) { 96 | self.res.consume(cost); 97 | super::tile_event::cause_tile_event(self, p, kind, sim, params); 98 | self.update(sim, params); 99 | true 100 | } else { 101 | false 102 | } 103 | } 104 | 105 | pub fn animal_spawnable(&self, p: Coords, animal_id: AnimalId, params: &Params) -> bool { 106 | let attr = ¶ms.animals[&animal_id]; 107 | 108 | self.map[p].animal[attr.size as usize].is_none() && attr.cost <= self.res.gene_point 109 | } 110 | 111 | pub fn spawn_animal(&mut self, p: Coords, animal_id: AnimalId, params: &Params) { 112 | assert!(self.animal_spawnable(p, animal_id, params)); 113 | 114 | let attr = ¶ms.animals[&animal_id]; 115 | self.res.gene_point -= attr.cost; 116 | self.map[p].animal[attr.size as usize] = Some(Animal { 117 | id: animal_id, 118 | n: 0.1, 119 | }); 120 | } 121 | 122 | pub fn civilize_animal(&mut self, p: Coords, animal_id: AnimalId, params: &Params) { 123 | self.res.consume(Cost::GenePoint(params.event.civilize_cost)); 124 | super::civ::civilize_animal(self, params, p, animal_id); 125 | } 126 | 127 | pub fn get_civilizable_animal(&mut self, p: Coords, params: &Params) -> Option { 128 | self.map[p] 129 | .animal 130 | .iter() 131 | .filter_map(|animal| animal.map(|animal| animal.id)) 132 | .find(|&animal| params.animals[&animal].civ.is_some()) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/planet/atmo.rs: -------------------------------------------------------------------------------- 1 | use std::sync::LazyLock; 2 | 3 | use fnv::FnvHashMap; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use super::misc::linear_interpolation; 7 | use super::*; 8 | 9 | const MOLECULAR_WEIGHT_N2: f32 = 28.0; 10 | const MOLECULAR_WEIGHT_O2: f32 = 32.0; 11 | const MOLECULAR_WEIGHT_CO2: f32 = 44.0; 12 | const MOLECULAR_WEIGHT_ARGON: f32 = 40.0; 13 | 14 | pub const CO2_CARBON_WEIGHT_RATIO: f32 = MOLECULAR_WEIGHT_CO2 / 12.0; 15 | pub const CO2_OXYGEN_WEIGHT_RATIO: f32 = MOLECULAR_WEIGHT_CO2 / MOLECULAR_WEIGHT_O2; 16 | 17 | static GAS_MOLECULAR_WEIGHT: LazyLock> = LazyLock::new(|| { 18 | let mut map = FnvHashMap::default(); 19 | map.insert(GasKind::Nitrogen, MOLECULAR_WEIGHT_N2); 20 | map.insert(GasKind::Oxygen, MOLECULAR_WEIGHT_O2); 21 | map.insert(GasKind::CarbonDioxide, MOLECULAR_WEIGHT_CO2); 22 | map.insert(GasKind::Argon, MOLECULAR_WEIGHT_ARGON); 23 | map 24 | }); 25 | 26 | const AEROSOL_EQUILIBRIUM_TARGET: f32 = 1.0; 27 | 28 | #[derive(Clone, Debug, Serialize, Deserialize)] 29 | pub struct Atmosphere { 30 | atm: f32, 31 | /// Gases mass [Mt] 32 | mass: FnvHashMap, 33 | /// Gass mole ratio 34 | pub mole_ratio: FnvHashMap, 35 | /// Cloud amount [%]. The average is 50% 36 | pub cloud_amount: f32, 37 | /// Aerosol amount 38 | pub aerosol: f32, 39 | } 40 | 41 | impl Atmosphere { 42 | pub fn new(start_params: &StartParams, params: &Params) -> Self { 43 | let mass = start_params 44 | .atmo 45 | .iter() 46 | .map(|(gas_kind, atm)| { 47 | ( 48 | *gas_kind, 49 | atm * params.sim.mol_per_atm as f64 * GAS_MOLECULAR_WEIGHT[gas_kind] as f64, 50 | ) 51 | }) 52 | .collect(); 53 | let mole_ratio = GasKind::iter().map(|gas_kind| (gas_kind, 0.0)).collect(); 54 | 55 | Atmosphere { 56 | atm: 0.0, 57 | mass, 58 | mole_ratio, 59 | cloud_amount: 50.0, 60 | aerosol: AEROSOL_EQUILIBRIUM_TARGET, 61 | } 62 | } 63 | 64 | pub fn total_mass(&self) -> f32 { 65 | self.mass.values().sum::() as f32 66 | } 67 | 68 | pub fn atm(&self) -> f32 { 69 | self.atm 70 | } 71 | 72 | pub fn partial_pressure(&self, kind: GasKind) -> f32 { 73 | self.atm * self.mole_ratio[&kind] 74 | } 75 | 76 | pub fn mass(&self, kind: GasKind) -> f32 { 77 | *self.mass.get(&kind).unwrap() as f32 78 | } 79 | 80 | pub fn set_mass(&mut self, kind: GasKind, value: f32) { 81 | *self.mass.get_mut(&kind).unwrap() = value as f64; 82 | } 83 | 84 | pub fn add(&mut self, kind: GasKind, value: impl Into) { 85 | let mass = self.mass.get_mut(&kind).unwrap(); 86 | *mass = (*mass + value.into()).max(0.0); 87 | } 88 | 89 | pub fn remove_carbon(&mut self, value: f32) -> bool { 90 | debug_assert!(value >= 0.0); 91 | let co2_mass = value * CO2_CARBON_WEIGHT_RATIO; 92 | let co2_mass_in_atmo = self.mass.get_mut(&GasKind::CarbonDioxide).unwrap(); 93 | if *co2_mass_in_atmo > co2_mass as f64 { 94 | *co2_mass_in_atmo -= co2_mass as f64; 95 | self.add(GasKind::Oxygen, value * CO2_OXYGEN_WEIGHT_RATIO); 96 | true 97 | } else { 98 | false 99 | } 100 | } 101 | 102 | pub fn release_carbon(&mut self, value: f32) { 103 | self.add(GasKind::Oxygen, -value * CO2_OXYGEN_WEIGHT_RATIO); 104 | self.add(GasKind::CarbonDioxide, value * CO2_CARBON_WEIGHT_RATIO); 105 | } 106 | 107 | pub fn remove_atmo(&mut self, value: impl Into) { 108 | let value = value.into(); 109 | let total_mass = self.total_mass() as f64; 110 | for kind in GasKind::iter() { 111 | let mass = self.mass.get_mut(&kind).unwrap(); 112 | let v = value * *mass / total_mass; 113 | *mass = (*mass - v).max(0.0); 114 | } 115 | } 116 | } 117 | 118 | pub fn sim_atmosphere(planet: &mut Planet, _sim: &mut Sim, params: &Params) { 119 | let mut atmo_mole = FnvHashMap::default(); 120 | let mut sum_mole = 0.0; 121 | for gas_kind in GasKind::iter() { 122 | let mole = planet.atmo.mass[&gas_kind] as f32 / GAS_MOLECULAR_WEIGHT[&gas_kind]; 123 | atmo_mole.insert(gas_kind, mole); 124 | sum_mole += mole; 125 | } 126 | for gas_kind in GasKind::iter() { 127 | planet 128 | .atmo 129 | .mole_ratio 130 | .insert(gas_kind, atmo_mole[&gas_kind] / sum_mole); 131 | } 132 | planet.atmo.atm = sum_mole / params.sim.mol_per_atm; 133 | 134 | // Aerosol 135 | let base_supply = (1.0 - params.sim.aerosol_remaining_rate) * AEROSOL_EQUILIBRIUM_TARGET; 136 | planet.atmo.aerosol += base_supply; 137 | planet.atmo.aerosol *= params.sim.aerosol_remaining_rate; 138 | 139 | planet.atmo.cloud_amount = 140 | linear_interpolation(¶ms.sim.aerosol_cloud_table, planet.atmo.aerosol); 141 | } 142 | -------------------------------------------------------------------------------- /src/planet/buildings.rs: -------------------------------------------------------------------------------- 1 | use super::{atmo::CO2_CARBON_WEIGHT_RATIO, misc::linear_interpolation, *}; 2 | use fnv::FnvHashMap; 3 | 4 | impl Planet { 5 | pub fn space_building(&self, kind: SpaceBuildingKind) -> &Building { 6 | self.space_buildings.get(&kind).unwrap() 7 | } 8 | 9 | pub fn space_building_mut(&mut self, kind: SpaceBuildingKind) -> &mut Building { 10 | self.space_buildings.get_mut(&kind).unwrap() 11 | } 12 | 13 | pub fn working_building_effect<'a>( 14 | &self, 15 | p: Coords, 16 | params: &'a Params, 17 | ) -> Option<&'a BuildingEffect> { 18 | if let Some(structure) = &self.map[p].structure { 19 | params 20 | .structures 21 | .get(&structure.kind()) 22 | .and_then(|s| s.building.effect.as_ref()) 23 | } else { 24 | None 25 | } 26 | } 27 | } 28 | 29 | pub fn update(planet: &mut Planet, sim: &mut Sim, params: &Params) { 30 | update_working_buildings(planet, &mut sim.working_buildings); 31 | 32 | for (&kind, &n) in &sim.working_buildings { 33 | if n == 0 { 34 | continue; 35 | } 36 | let attrs = params.building_attrs(kind); 37 | let control_value = if let BuildingKind::Space(kind) = kind { 38 | Some(planet.space_building(kind).control) 39 | } else { 40 | None 41 | }; 42 | if attrs.power > 0.0 { 43 | planet.res.power += attrs.power * n as f32; 44 | } else { 45 | planet.res.used_power += -attrs.power * n as f32; 46 | } 47 | 48 | match &attrs.effect { 49 | Some(BuildingEffect::ProduceMaterial { mass }) => { 50 | planet.res.diff_material += mass * n as f32; 51 | } 52 | Some(BuildingEffect::AdjustSolarPower) => { 53 | if let Some(BuildingControlValue::IncreaseRate(rate)) = control_value { 54 | planet.state.solar_power_multiplier += (rate as f32) / 100.0; 55 | } 56 | } 57 | _ => (), 58 | } 59 | } 60 | } 61 | 62 | pub fn advance(planet: &mut Planet, sim: &mut Sim, params: &Params) { 63 | for (&kind, &n) in &sim.working_buildings { 64 | if n == 0 { 65 | continue; 66 | } 67 | let Some(effect) = ¶ms.building_attrs(kind).effect.as_ref() else { 68 | continue; 69 | }; 70 | match effect { 71 | BuildingEffect::RemoveAtmo { 72 | mass, 73 | efficiency_table, 74 | } => { 75 | let efficiency = linear_interpolation(efficiency_table, planet.atmo.atm()); 76 | planet 77 | .atmo 78 | .remove_atmo(*mass as f64 * n as f64 * efficiency as f64); 79 | } 80 | BuildingEffect::SprayToAtmo { 81 | kind, 82 | mass, 83 | limit_atm, 84 | } => { 85 | if planet.atmo.partial_pressure(*kind) < *limit_atm { 86 | planet.atmo.add(*kind, mass * n as f32); 87 | } 88 | } 89 | BuildingEffect::AddWater { value } => { 90 | planet.water.water_volume += value; 91 | } 92 | _ => (), 93 | } 94 | } 95 | 96 | for p in planet.map.iter_idx() { 97 | process_building_on_tile(planet, p, sim, params); 98 | } 99 | } 100 | 101 | fn process_building_on_tile(planet: &mut Planet, p: Coords, sim: &mut Sim, params: &Params) { 102 | let Some(effect) = planet.map[p] 103 | .structure 104 | .as_ref() 105 | .map(|structure| structure.kind()) 106 | .and_then(|kind| params.building_attrs(kind).effect.as_ref()) 107 | else { 108 | return; 109 | }; 110 | 111 | if let BuildingEffect::CaptureCarbonDioxide { mass, limit_atm } = effect { 112 | // Remove co2 from atmosphere, and add buried carbon to one near tile. 113 | let co2_mass_to_remove = 114 | (planet.atmo.mass(GasKind::CarbonDioxide) / planet.atmo.total_mass()) * mass; 115 | let carbon_mass = co2_mass_to_remove / CO2_CARBON_WEIGHT_RATIO; 116 | planet.atmo.remove_carbon(carbon_mass); 117 | 118 | if planet.atmo.partial_pressure(GasKind::Oxygen) < *limit_atm { 119 | planet 120 | .atmo 121 | .add(GasKind::Oxygen, co2_mass_to_remove - carbon_mass); 122 | } 123 | 124 | loop { 125 | let p_target = p + (sim.rng.random_range(-3..=3), sim.rng.random_range(-3..=3)); 126 | if planet.map.in_range(p_target) { 127 | planet.map[p_target].buried_carbon += carbon_mass; 128 | break; 129 | } 130 | } 131 | } 132 | } 133 | 134 | fn update_working_buildings( 135 | planet: &Planet, 136 | working_buildings: &mut FnvHashMap, 137 | ) { 138 | working_buildings.clear(); 139 | 140 | for kind in SpaceBuildingKind::iter() { 141 | let n = planet.space_building(kind).enabled(); 142 | working_buildings.insert(BuildingKind::Space(kind), n); 143 | } 144 | 145 | for p in planet.map.iter_idx() { 146 | if let Some(structure) = &planet.map[p].structure { 147 | let kind = structure.kind(); 148 | if kind != StructureKind::Settlement { 149 | *working_buildings 150 | .entry(BuildingKind::Structure(kind)) 151 | .or_insert(0) += 1; 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/planet/debug.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::fmt::Write; 3 | use std::sync::{LazyLock, RwLock}; 4 | 5 | use geom::Coords; 6 | 7 | use crate::planet::*; 8 | 9 | static POS_FOR_LOG: LazyLock>> = LazyLock::new(RwLock::default); 10 | static TILE_LOGS: LazyLock>> = LazyLock::new(RwLock::default); 11 | 12 | pub fn clear_logs(p: Option) { 13 | *POS_FOR_LOG.write().unwrap() = p; 14 | TILE_LOGS.write().unwrap().clear(); 15 | } 16 | 17 | pub fn tile_logs() -> impl std::ops::Deref> { 18 | TILE_LOGS.read().unwrap() 19 | } 20 | 21 | #[allow(unused)] 22 | pub(super) fn tile_log T, T: ToString>( 23 | target: Coords, 24 | name: &'static str, 25 | f: F, 26 | ) { 27 | if *POS_FOR_LOG.read().unwrap() == Some(target) { 28 | TILE_LOGS.write().unwrap().insert(name, f(target).to_string()); 29 | } 30 | } 31 | 32 | pub fn tile_debug_info(planet: &Planet, sim: &Sim, p: Coords) -> Vec<(&'static str, String)> { 33 | let mut v = Vec::new(); 34 | 35 | v.push(("height", format!("{:.1}", planet.map[p].height))); 36 | v.push(("humidity", format!("{:.1}", sim.humidity[p]))); 37 | v.push(("albedo", format!("{}", sim.albedo[p]))); 38 | v.push(( 39 | "sea_temp", 40 | format!("{:.1}", planet.map[p].sea_temp - KELVIN_CELSIUS), 41 | )); 42 | v.push(("ice", format!("{}", planet.map[p].ice))); 43 | v.push(( 44 | "buried carbon", 45 | format!("{:.2e}", planet.map[p].buried_carbon), 46 | )); 47 | v.push(( 48 | "animal0", 49 | animals_debug_text_in_tile(&planet.map[p].animal[0]), 50 | )); 51 | v.push(( 52 | "animal1", 53 | animals_debug_text_in_tile(&planet.map[p].animal[1]), 54 | )); 55 | v.push(( 56 | "animal2", 57 | animals_debug_text_in_tile(&planet.map[p].animal[2]), 58 | )); 59 | v.push(( 60 | "pop, tech_exp", 61 | match &planet.map[p].structure { 62 | Some(Structure::Settlement(settlement)) => { 63 | format!( 64 | "{}: {:.2}, {:+.1}", 65 | settlement.id, settlement.pop, settlement.tech_exp, 66 | ) 67 | } 68 | _ => "-".into(), 69 | }, 70 | )); 71 | v.push(( 72 | "settlement state", 73 | match &planet.map[p].structure { 74 | Some(Structure::Settlement(settlement)) => { 75 | format!( 76 | "{} {}", 77 | settlement.state.as_ref(), 78 | settlement.since_state_changed, 79 | ) 80 | } 81 | _ => "-".into(), 82 | }, 83 | )); 84 | v.push(( 85 | "strength", 86 | match &planet.map[p].structure { 87 | Some(Structure::Settlement(settlement)) => { 88 | format!("{}", settlement.str) 89 | } 90 | _ => "-".into(), 91 | }, 92 | )); 93 | v.push(("energy efficiency", format!("{:.1}", sim.energy_eff[p]))); 94 | 95 | v 96 | } 97 | 98 | fn animals_debug_text_in_tile(animal: &Option) -> String { 99 | let Some(animal) = animal else { 100 | return "-".into(); 101 | }; 102 | 103 | format!("{}(n={:.3})", animal.id, animal.n) 104 | } 105 | 106 | pub trait PlanetDebug { 107 | fn edit_biome(&mut self, p: Coords, biome: Biome); 108 | fn change_height(&mut self, p: Coords, value: f32, sim: &mut Sim, params: &Params); 109 | fn place_settlement(&mut self, p: Coords, settlement: Settlement); 110 | fn cause_decadence(&mut self, p: Coords, sim: &mut Sim, params: &Params); 111 | fn cause_civil_war(&mut self, p: Coords, sim: &mut Sim, params: &Params); 112 | fn cause_nuclear_explosion(&mut self, p: Coords, sim: &mut Sim, params: &Params); 113 | fn delete_civilization(&mut self); 114 | fn delete_animals(&mut self); 115 | fn height_map_as_string(&self) -> String; 116 | } 117 | 118 | impl PlanetDebug for Planet { 119 | fn edit_biome(&mut self, p: Coords, biome: Biome) { 120 | self.map[p].biome = biome; 121 | } 122 | 123 | fn change_height(&mut self, p: Coords, value: f32, sim: &mut Sim, params: &Params) { 124 | let h = &mut self.map[p].height; 125 | *h = (*h + value).max(0.0); 126 | super::water::update_sea_level(self, sim, params); 127 | } 128 | 129 | fn place_settlement(&mut self, p: Coords, settlement: Settlement) { 130 | if self.map[p].structure.is_none() { 131 | self.civs.entry(settlement.id).or_default(); 132 | self.map[p].structure = Some(Structure::Settlement(settlement)); 133 | } 134 | } 135 | 136 | fn cause_decadence(&mut self, p: Coords, sim: &mut Sim, params: &Params) { 137 | super::decadence::cause_decadence(self, sim, params, p); 138 | } 139 | 140 | fn cause_civil_war(&mut self, p: Coords, sim: &mut Sim, params: &Params) { 141 | if let Some(Structure::Settlement(settlement)) = self.map[p].structure.clone() { 142 | super::war::start_civil_war(self, sim, params, p, settlement); 143 | } 144 | } 145 | 146 | fn cause_nuclear_explosion(&mut self, p: Coords, _sim: &mut Sim, params: &Params) { 147 | self.map[p].tile_events.insert(TileEvent::NuclearExplosion { 148 | remaining_cycles: params.event.nuclear_explosion_cycles, 149 | }); 150 | } 151 | 152 | fn delete_civilization(&mut self) { 153 | for p in self.map.iter_idx() { 154 | if matches!(self.map[p].structure, Some(Structure::Settlement(_))) { 155 | self.map[p].structure = None; 156 | } 157 | self.map[p].tile_events.remove(TileEventKind::Vehicle); 158 | self.map[p].tile_events.remove(TileEventKind::Troop); 159 | } 160 | } 161 | 162 | fn delete_animals(&mut self) { 163 | for p in self.map.iter_idx() { 164 | self.map[p].animal = [None; 3]; 165 | } 166 | } 167 | 168 | fn height_map_as_string(&self) -> String { 169 | let mut s = String::new(); 170 | write!(s, "[").unwrap(); 171 | for (i, p) in self.map.iter_idx().enumerate() { 172 | let separator = if i == 0 { "" } else { "," }; 173 | let newline = if i % 64 == 0 { "\n" } else { "" }; 174 | write!(s, "{}{}{:.2}", separator, newline, self.map[p].height).unwrap(); 175 | } 176 | write!(s, "]").unwrap(); 177 | s 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/planet/decadence.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use arrayvec::ArrayVec; 4 | use geom::Coords; 5 | use rand::{Rng, seq::IndexedRandom}; 6 | 7 | use super::{Planet, ReportContent, Sim, defs::*}; 8 | 9 | pub fn cause_decadence_random(planet: &mut Planet, sim: &mut Sim, params: &Params) { 10 | let events: HashMap<_, _> = planet 11 | .events 12 | .in_progress_iter() 13 | .filter_map(|e| match &e.event { 14 | PlanetEvent::Decadence(decadence) => Some((decadence.id, decadence.clone())), 15 | _ => None, 16 | }) 17 | .collect(); 18 | 19 | for p in planet.map.iter_idx() { 20 | let Some(Structure::Settlement(settlement)) = &mut planet.map[p].structure else { 21 | continue; 22 | }; 23 | if events.contains_key(&settlement.id) { 24 | continue; 25 | } 26 | let civ_age = planet.civs[&settlement.id].most_advanced_age; 27 | 28 | if civ_age < CivilizationAge::Iron { 29 | continue; 30 | } 31 | 32 | if settlement.age == civ_age 33 | && matches!( 34 | settlement.state, 35 | SettlementState::Growing | SettlementState::Stable 36 | ) 37 | && settlement.since_state_changed > params.sim.settlement_state_changeable_cycles 38 | && settlement.pop 39 | > params.sim.settlement_max_pop[civ_age as usize] 40 | * params.event.decadence_pop_threshold 41 | && sim.rng.random_bool(params.event.decadence_prob) 42 | { 43 | cause_decadence(planet, sim, params, p); 44 | } 45 | } 46 | } 47 | 48 | pub fn cause_decadence(planet: &mut Planet, sim: &mut Sim, params: &Params, p: Coords) { 49 | let Some(Structure::Settlement(settlement)) = planet.map[p].structure else { 50 | return; 51 | }; 52 | 53 | planet.map[p] 54 | .tile_events 55 | .insert(TileEvent::Decadence { cured: false }); 56 | let remaining_cycles = sim 57 | .rng 58 | .random_range(params.event.decadence_cycles.0..params.event.decadence_cycles.1); 59 | let duration = remaining_cycles 60 | + sim.rng.random_range( 61 | params.event.decadence_interval_cycles.0..params.event.decadence_interval_cycles.1, 62 | ); 63 | 64 | planet.events.start_event( 65 | PlanetEvent::Decadence(DecadenceEvent { 66 | id: settlement.id, 67 | start_pos: p, 68 | age: settlement.age, 69 | remaining_cycles: remaining_cycles as i32, 70 | }), 71 | duration as u64, 72 | ); 73 | planet.reports.append( 74 | planet.cycles, 75 | ReportContent::EventCivDecadence { 76 | id: settlement.id, 77 | name: planet.civ_name(settlement.id), 78 | pos: p, 79 | }, 80 | ); 81 | } 82 | 83 | pub fn sim_decadence(planet: &mut Planet, sim: &mut Sim, params: &Params) { 84 | let events: HashMap<_, _> = planet 85 | .events 86 | .in_progress_iter_mut() 87 | .filter_map(|e| match &mut e.event { 88 | PlanetEvent::Decadence(decadence) => { 89 | decadence.remaining_cycles -= 1; 90 | Some((decadence.id, decadence.clone())) 91 | } 92 | _ => None, 93 | }) 94 | .collect(); 95 | 96 | for p in planet.map.iter_idx() { 97 | let (id, age) = { 98 | let tile = &mut planet.map[p]; 99 | let Some(TileEvent::Decadence { cured }) = 100 | tile.tile_events.get_mut(TileEventKind::Decadence) 101 | else { 102 | continue; 103 | }; 104 | if *cured { 105 | continue; 106 | } 107 | let Some(Structure::Settlement(settlement)) = &mut tile.structure else { 108 | continue; 109 | }; 110 | let DecadenceEvent { 111 | remaining_cycles, 112 | age, 113 | .. 114 | } = events[&settlement.id]; 115 | 116 | if remaining_cycles <= 0 { 117 | tile.tile_events.remove(TileEventKind::Decadence); 118 | continue; 119 | } 120 | 121 | if settlement.age < age { 122 | *cured = true; 123 | settlement.change_state_after_bad_event(sim, params); 124 | continue; 125 | } 126 | 127 | if matches!( 128 | settlement.state, 129 | SettlementState::Growing | SettlementState::Stable 130 | ) { 131 | settlement.state = SettlementState::Declining; 132 | } 133 | 134 | (settlement.id, settlement.age) 135 | }; 136 | 137 | if sim.rng.random_bool(params.event.decadence_infectivity) { 138 | let mut target_tiles: ArrayVec = ArrayVec::new(); 139 | for d in geom::CHEBYSHEV_DISTANCE_1_COORDS { 140 | if let Some(p_adj) = sim.convert_p_cyclic(p + *d) { 141 | if let Some(Structure::Settlement(settlement)) = &planet.map[p_adj].structure { 142 | if settlement.id == id 143 | && settlement.age == age 144 | && planet.map[p_adj] 145 | .tile_events 146 | .list() 147 | .iter() 148 | .all(|te| !te.is_settlement_event()) 149 | { 150 | target_tiles.push(p_adj); 151 | } 152 | } 153 | } 154 | } 155 | if let Some(p_target) = target_tiles.choose(&mut sim.rng) { 156 | planet.map[*p_target] 157 | .tile_events 158 | .insert(TileEvent::Decadence { cured: false }); 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/planet/event.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::*; 4 | 5 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] 6 | pub struct Events { 7 | in_progress: Vec, 8 | } 9 | 10 | impl Events { 11 | pub fn start_event(&mut self, event: PlanetEvent, duration: impl Into>) { 12 | self.in_progress.push(EventInProgress { 13 | event, 14 | duration: duration.into(), 15 | progress: 0, 16 | }); 17 | } 18 | 19 | pub fn in_progress_iter(&self) -> impl Iterator { 20 | self.in_progress.iter() 21 | } 22 | 23 | pub fn in_progress_iter_mut(&mut self) -> impl Iterator { 24 | self.in_progress.iter_mut() 25 | } 26 | 27 | pub fn in_progress_event_cycles(&mut self, kind: PlanetEventKind) -> impl Iterator { 28 | self.in_progress.iter_mut().filter_map(move |e| { 29 | if e.event.kind() == kind { 30 | Some(e.progress) 31 | } else { 32 | None 33 | } 34 | }) 35 | } 36 | 37 | pub fn in_war(&self, a: AnimalId, b: AnimalId) -> Option { 38 | for e in self.in_progress_iter() { 39 | if let PlanetEvent::War(war_event) = &e.event { 40 | if !war_event.ceased { 41 | if let WarKind::InterSpecies(id0, id1) = &war_event.kind { 42 | if (*id0 == a && *id1 == b) || (*id0 == b && *id1 == a) { 43 | return Some(war_event.i); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | None 50 | } 51 | } 52 | 53 | #[derive(Clone, Debug, Serialize, Deserialize)] 54 | pub struct EventInProgress { 55 | pub event: PlanetEvent, 56 | pub progress: u64, 57 | pub duration: Option, 58 | } 59 | 60 | pub fn advance(planet: &mut Planet, sim: &mut Sim, params: &Params) { 61 | let mut completed_events = Vec::new(); 62 | let mut plague_ended = false; 63 | 64 | let mut event_kind_list: Vec<_> = planet 65 | .events 66 | .in_progress 67 | .iter() 68 | .map(|e| e.event.kind()) 69 | .collect(); 70 | event_kind_list.sort(); 71 | event_kind_list.dedup(); 72 | for event_kind in event_kind_list { 73 | match event_kind { 74 | PlanetEventKind::Plague => { 75 | plague_ended = super::plague::sim_plague(planet, sim, params); 76 | } 77 | PlanetEventKind::Decadence => { 78 | super::decadence::sim_decadence(planet, sim, params); 79 | } 80 | PlanetEventKind::War => { 81 | super::war::sim_war(planet, sim, params); 82 | } 83 | } 84 | } 85 | 86 | for ein in &mut planet.events.in_progress { 87 | ein.progress += 1; 88 | } 89 | 90 | planet.events.in_progress.retain(|ein| { 91 | // Check the event is completed by the duration 92 | if let Some(duration) = ein.duration { 93 | if ein.progress >= duration { 94 | completed_events.push(ein.event.clone()); 95 | return false; 96 | } 97 | } 98 | // Check plague event is ended 99 | if plague_ended && ein.event.kind() == PlanetEventKind::Plague { 100 | return false; 101 | } 102 | 103 | // Check civil war is ended 104 | if let PlanetEvent::War(WarEvent { i, kind, .. }) = &ein.event { 105 | if *kind == WarKind::CivilWar && matches!(sim.war_counter.get(i), Some(0) | None) { 106 | return false; 107 | } 108 | } 109 | 110 | true 111 | }); 112 | } 113 | -------------------------------------------------------------------------------- /src/planet/initial_conditions.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | use super::{Biome, InitialCondition, Params, Planet, Sim, misc::SymmetricalLinearDist}; 4 | 5 | pub fn apply_initial_condition( 6 | planet: &mut Planet, 7 | sim: &mut Sim, 8 | initial_condition: InitialCondition, 9 | _params: &Params, 10 | ) { 11 | match initial_condition { 12 | InitialCondition::Snowball { thickness } => { 13 | for p in planet.map.iter_idx() { 14 | let t = 250.0; 15 | let tile = &mut planet.map[p]; 16 | if tile.biome.is_land() { 17 | tile.biome = Biome::IceSheet; 18 | } else { 19 | tile.biome = Biome::SeaIce; 20 | tile.sea_temp = t; 21 | } 22 | tile.ice = sim.rng.sample(SymmetricalLinearDist::from(thickness)); 23 | tile.temp = t; 24 | tile.vapor = 0.0; 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/planet/map_generator.rs: -------------------------------------------------------------------------------- 1 | use geom::Array2d; 2 | use noise::{ 3 | Perlin, ScalePoint, 4 | utils::{NoiseMapBuilder, SphereMapBuilder}, 5 | }; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct GenConf { 9 | pub w: u32, 10 | pub h: u32, 11 | pub max_height: f32, 12 | pub height_table: Vec<(f32, f32)>, 13 | pub height_map: Vec, 14 | } 15 | 16 | pub fn generate(conf: GenConf) -> Array2d { 17 | if let Some(map) = from_height_map(&conf) { 18 | return map; 19 | } 20 | 21 | let noise_fn = ScalePoint::new(Perlin::new(rand::random())).set_scale(2.0); 22 | let map_builder = SphereMapBuilder::new(noise_fn) 23 | .set_size(conf.w as _, conf.h as _) 24 | .set_bounds(-80.0, 80.0, -180.0, 180.0) 25 | .build(); 26 | 27 | Array2d::from_fn(conf.w, conf.h, |(x, y)| { 28 | let h = (map_builder.get_value(x as _, y as _) as f32 + 1.0) * 0.5; // Convert to 0.0..1.0 29 | 30 | let h = if conf.height_table.is_empty() { 31 | h 32 | } else { 33 | super::misc::linear_interpolation(&conf.height_table, h) 34 | }; 35 | 36 | h * conf.max_height 37 | }) 38 | } 39 | 40 | fn from_height_map(conf: &GenConf) -> Option> { 41 | if conf.height_map.is_empty() { 42 | return None; 43 | } 44 | if conf.height_map.len() != (conf.w * conf.h) as usize { 45 | log::warn!("invalid length height_map"); 46 | return None; 47 | } 48 | 49 | Some(Array2d::from_fn(conf.w, conf.h, |(x, y)| { 50 | let i = x + conf.w * y; 51 | conf.height_map[i as usize] 52 | })) 53 | } 54 | -------------------------------------------------------------------------------- /src/planet/misc.rs: -------------------------------------------------------------------------------- 1 | use geom::{Coords, CyclicMode}; 2 | use rand::{Rng, SeedableRng, rngs::SmallRng}; 3 | 4 | pub fn linear_interpolation(table: &[(f32, f32)], x: f32) -> f32 { 5 | assert!(table.len() >= 2); 6 | let first = table.first().unwrap(); 7 | let last = table.last().unwrap(); 8 | if first.0 >= x { 9 | return first.1; 10 | } else if last.0 <= x { 11 | return last.1; 12 | } 13 | 14 | for i in 0..(table.len() - 1) { 15 | let x0 = table[i].0; 16 | let x1 = table[i + 1].0; 17 | if x0 < x && x <= x1 { 18 | let y0 = table[i].1; 19 | let y1 = table[i + 1].1; 20 | let a = (y1 - y0) / (x1 - x0); 21 | let b = (x1 * y0 - x0 * y1) / (x1 - x0); 22 | return a * x + b; 23 | } 24 | } 25 | 26 | panic!("invalid input for interpolation: {}", x) 27 | } 28 | 29 | pub fn bisection f32>( 30 | mut f: F, 31 | mut a: f32, 32 | mut b: f32, 33 | n_max: usize, 34 | target_diff: f32, 35 | ) -> f32 { 36 | let mut c = (a + b) / 2.0; 37 | 38 | for _ in 0..n_max { 39 | if f(c) < 0.0 { 40 | a = c; 41 | } else { 42 | b = c; 43 | } 44 | c = (a + b) / 2.0; 45 | if (b - a) < target_diff * 2.0 { 46 | return c; 47 | } 48 | } 49 | c 50 | } 51 | 52 | pub fn range_to_livability_trapezoid((min, max): (f32, f32), a: f32, x: f32) -> f32 { 53 | let (min, max) = if min <= max { 54 | (min, max) 55 | } else { 56 | let a = (max + min) / 2.0; 57 | (a, a) 58 | }; 59 | 60 | let l = max - min; 61 | let b = l / a; 62 | 63 | let result = if x <= min - b { 64 | 0.0 65 | } else if x <= min + b { 66 | x / (2.0 * b) - min / (2.0 * b) + 0.5 67 | } else if x <= max - b { 68 | 1.0 69 | } else if x <= max + b { 70 | -x / (2.0 * b) + max / (2.0 * b) + 0.5 71 | } else { 72 | 0.0 73 | }; 74 | 75 | debug_assert!(result.is_finite(), "{:?},{}", (min, max, a, x), result); 76 | // Clamp result because of float precision 77 | result.clamp(0.0, 1.0) 78 | } 79 | 80 | #[rustfmt::skip] 81 | const CALC_CONGESTION_TARGET_TILES: &[((i32, i32), u32)] = &[ 82 | ((-2, -2), 1), ((-1, -2), 1), ((0, -2), 1), ((1, -2), 1), ((2, -2), 1), 83 | ((-2, -1), 1), ((-1, -1), 2), ((0, -1), 2), ((1, -1), 2), ((2, -1), 1), 84 | ((-2, 0), 1), ((-1, 0), 2), ((1, 0), 2), ((2, 0), 1), 85 | ((-2, 1), 1), ((-1, 1), 2), ((0, 1), 2), ((1, 1), 2), ((2, 1), 1), 86 | ((-2, 2), 1), ((-1, 2), 1), ((0, 2), 1), ((1, 2), 1), ((2, 2), 1), 87 | ]; 88 | 89 | pub fn calc_congestion_rate f32>(p: Coords, size: (u32, u32), mut f: F) -> f32 { 90 | let mut sum = 0; 91 | let mut crowded = 0.0; 92 | 93 | for &(dp, a) in CALC_CONGESTION_TARGET_TILES { 94 | let Some(p) = CyclicMode::X.convert_coords(size, p + dp) else { 95 | continue; 96 | }; 97 | 98 | crowded += f(p) * a as f32; 99 | sum += a; 100 | } 101 | 102 | crowded / sum as f32 103 | } 104 | 105 | pub fn apply_control_value(value: f32, ratio: f32, control_multiplier: i16) -> f32 { 106 | let control_multiplier = control_multiplier as f32 / 100.0; 107 | let base = value * (1.0 - ratio); 108 | base + value * ratio * control_multiplier 109 | } 110 | 111 | /// Random sampling [mean - d, mean + d] with constant distribution. 112 | #[derive(Clone, Copy, Debug)] 113 | pub struct ConstantDist { 114 | mean: f32, 115 | d: f32, 116 | } 117 | 118 | impl rand::distr::Distribution for ConstantDist { 119 | fn sample(&self, rng: &mut R) -> f32 { 120 | rng.random_range((self.mean - self.d)..=(self.mean - self.d)) 121 | } 122 | } 123 | 124 | impl ConstantDist { 125 | pub fn new(mean: f32, d: f32) -> Self { 126 | Self { mean, d } 127 | } 128 | } 129 | 130 | impl From<(f32, f32)> for ConstantDist { 131 | fn from(a: (f32, f32)) -> Self { 132 | Self::new(a.0, a.1) 133 | } 134 | } 135 | 136 | /// Random sampling [mean - d, mean + d] with linear distribution. 137 | #[derive(Clone, Copy, Debug)] 138 | pub struct SymmetricalLinearDist { 139 | mean: f32, 140 | d: f32, 141 | } 142 | 143 | impl SymmetricalLinearDist { 144 | pub fn new(mean: f32, d: f32) -> Self { 145 | Self { mean, d } 146 | } 147 | } 148 | 149 | impl From<(f32, f32)> for SymmetricalLinearDist { 150 | fn from(a: (f32, f32)) -> Self { 151 | Self::new(a.0, a.1) 152 | } 153 | } 154 | 155 | pub fn get_rng() -> SmallRng { 156 | let mut thread_rng = rand::rng(); 157 | rand::rngs::SmallRng::from_rng(&mut thread_rng) 158 | } 159 | 160 | impl rand::distr::Distribution for SymmetricalLinearDist { 161 | fn sample(&self, rng: &mut R) -> f32 { 162 | let r: f32 = rng.random_range(0.0..=1.0); 163 | 164 | let x = if r < 0.5 { 165 | (2.0 * r).sqrt() - 1.0 166 | } else { 167 | -(2.0 - 2.0 * r).sqrt() + 1.0 168 | }; 169 | 170 | self.mean + self.d * x 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/planet/monitoring.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl Planet { 4 | pub fn monitor(&mut self, params: &Params, report_span: u64) { 5 | if self.cycles % params.monitoring.interval_cycles != 1 { 6 | return; 7 | } 8 | 9 | self.reports.remove_outdated(self.cycles, report_span); 10 | 11 | // Temperature warnings 12 | if self.stat.average_air_temp > params.monitoring.warn_high_temp_threshold { 13 | self.reports 14 | .append_persitent_warn(self.cycles, ReportContent::WarnHighTemp); 15 | } else { 16 | self.reports.remove_persitent_warn(&ReportContent::WarnHighTemp); 17 | } 18 | 19 | if self.stat.average_air_temp < params.monitoring.warn_low_temp_threshold { 20 | self.reports 21 | .append_persitent_warn(self.cycles, ReportContent::WarnLowTemp); 22 | } else { 23 | self.reports.remove_persitent_warn(&ReportContent::WarnLowTemp); 24 | } 25 | 26 | // Atmosphere warnings 27 | if self.atmo.partial_pressure(GasKind::Oxygen) < params.monitoring.warn_low_oxygen_threshold 28 | { 29 | self.reports 30 | .append_persitent_warn(self.cycles, ReportContent::WarnLowOxygen); 31 | } else { 32 | self.reports 33 | .remove_persitent_warn(&ReportContent::WarnLowOxygen); 34 | } 35 | 36 | if self.atmo.partial_pressure(GasKind::CarbonDioxide) 37 | < params.monitoring.warn_low_carbon_dioxide_threshold 38 | { 39 | self.reports 40 | .append_persitent_warn(self.cycles, ReportContent::WarnLowCarbonDioxide); 41 | } else { 42 | self.reports 43 | .remove_persitent_warn(&ReportContent::WarnLowCarbonDioxide); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/planet/new.rs: -------------------------------------------------------------------------------- 1 | use sim::CoordsConverter; 2 | 3 | use super::*; 4 | 5 | impl Planet { 6 | pub fn new(start_params: &StartParams, params: &Params) -> Planet { 7 | let mut map = Array2d::new(start_params.size.0, start_params.size.1, Tile::default()); 8 | 9 | let gen_conf = map_generator::GenConf { 10 | w: start_params.size.0, 11 | h: start_params.size.1, 12 | max_height: start_params.difference_in_elevation, 13 | height_table: start_params.height_table.clone(), 14 | height_map: start_params.height_map.clone(), 15 | }; 16 | let height_map = map_generator::generate(gen_conf); 17 | for (p, height) in height_map.iter_with_idx() { 18 | map[p].height = *height; 19 | } 20 | 21 | let mut planet = Planet { 22 | cycles: 0, 23 | basics: start_params.basics.clone(), 24 | state: State::default(), 25 | res: Resources::new(start_params), 26 | map, 27 | atmo: Atmosphere::new(start_params, params), 28 | water: Water::new(start_params), 29 | space_buildings: SpaceBuildingKind::iter() 30 | .map(|kind| (kind, Building::default())) 31 | .collect(), 32 | events: Events::default(), 33 | civs: Civs::default(), 34 | stat: Stat::new(params), 35 | reports: Reports::default(), 36 | }; 37 | 38 | for (&kind, &n) in &start_params.space_buildings { 39 | let building = planet.space_building_mut(kind); 40 | building.n = n; 41 | } 42 | // Locate initial buried carbon 43 | if let Some(initial_buried_carbon) = &start_params.initial_buried_carbon { 44 | locate_initial_buried_carbon(&mut planet, initial_buried_carbon); 45 | } 46 | 47 | // Adjust water volume 48 | if start_params.target_sea_level.is_some() || start_params.target_sea_area.is_some() { 49 | let sim = Sim::new(&planet, params); 50 | let target_sea_level = start_params 51 | .target_sea_level 52 | .map(|target_sea_level| target_sea_level * start_params.difference_in_elevation); 53 | let target_diff = if target_sea_level.is_some() { 54 | 50.0 55 | } else { 56 | 0.02 57 | }; 58 | let max_water_volume = planet.water.water_volume; 59 | let water_volume = misc::bisection( 60 | |water_volume| { 61 | planet.water.water_volume = water_volume; 62 | super::water::update_sea_level(&mut planet, &sim, params); 63 | if let Some(target_sea_level) = target_sea_level { 64 | planet.water.sea_level - target_sea_level 65 | } else { 66 | let n_sea_tile = 67 | planet.map.iter().filter(|tile| tile.biome.is_sea()).count(); 68 | let size = planet.map.size(); 69 | let ratio = n_sea_tile as f32 / (size.0 * size.1) as f32; 70 | ratio - start_params.target_sea_area.unwrap() 71 | } 72 | }, 73 | 0.0, 74 | max_water_volume * 10.0, 75 | 20, 76 | target_diff, 77 | ); 78 | planet.water.water_volume = water_volume; 79 | } 80 | 81 | // Simulate before start 82 | let mut sim = Sim::new(&planet, params); 83 | planet.advance(&mut sim, params); 84 | for initial_condition in &start_params.initial_conditions { 85 | initial_conditions::apply_initial_condition( 86 | &mut planet, 87 | &mut sim, 88 | initial_condition.clone(), 89 | params, 90 | ); 91 | } 92 | planet.advance(&mut sim, params); 93 | heat_transfer::init_temp(&mut planet, &mut sim, params); 94 | 95 | let water_volume = planet.water.water_volume; 96 | planet.water.water_volume = 0.0; 97 | planet.water.water_volume = water_volume; 98 | planet.advance(&mut sim, params); 99 | 100 | sim.before_start = true; 101 | for _ in 0..start_params.cycles_before_start { 102 | if start_params 103 | .initial_conditions 104 | .iter() 105 | .any(|ic| matches!(ic, InitialCondition::Snowball { .. })) 106 | { 107 | sim.albedo.fill(0.8); 108 | } 109 | planet.advance(&mut sim, params); 110 | } 111 | 112 | // Reset 113 | planet.cycles = 0; 114 | planet.stat.clear_history(); 115 | planet.res.material = 0.0; 116 | self::stat::record_stats(&mut planet, params); 117 | 118 | planet 119 | } 120 | } 121 | 122 | fn locate_initial_buried_carbon(planet: &mut Planet, initial_buried_carbon: &InitialBuriedCarbon) { 123 | let coords_converter = CoordsConverter::new(planet); 124 | let size = planet.map.size(); 125 | let InitialBuriedCarbon { 126 | n_spot, 127 | mass, 128 | radius, 129 | scattering, 130 | } = *initial_buried_carbon; 131 | let mut rng = super::misc::get_rng(); 132 | let n_spot = rng.random_range(n_spot.0..n_spot.1); 133 | 134 | for _ in 0..n_spot { 135 | let mass = 10.0_f32.powf(rng.random_range(mass.0.log10()..mass.1.log10())); 136 | let radius = rng.random_range(radius.0..=radius.1); 137 | let center = (rng.random_range(0..size.0), rng.random_range(0..size.1)); 138 | let shape = geom::Shape::Circle { 139 | center: center.into(), 140 | radius, 141 | }; 142 | let tiles: Vec<_> = shape 143 | .iter() 144 | .into_iter() 145 | .filter_map(|p| coords_converter.conv(p)) 146 | .collect(); 147 | let m = mass / tiles.len() as f32; 148 | for p in tiles { 149 | planet.map[p].buried_carbon = 150 | m * rng.random_range((1.0 - scattering)..(1.0 + scattering)); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/planet/report.rs: -------------------------------------------------------------------------------- 1 | use geom::Coords; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use std::cmp::Reverse; 5 | use std::collections::BTreeMap; 6 | use std::mem::discriminant; 7 | 8 | use super::{AnimalId, CivilizationAge}; 9 | 10 | #[derive(Clone, Default, Debug, Serialize, Deserialize)] 11 | pub struct Reports { 12 | count: Reverse, 13 | reports: BTreeMap, Report>, 14 | persistent_warns: Vec, 15 | } 16 | 17 | #[derive(Clone, Debug, Serialize, Deserialize)] 18 | pub struct Report { 19 | pub cycles: u64, 20 | pub content: ReportContent, 21 | } 22 | 23 | impl Reports { 24 | pub fn append(&mut self, cycles: u64, content: ReportContent) { 25 | self.count = Reverse(self.count.0.wrapping_add(1)); 26 | self.reports.insert(self.count, Report { cycles, content }); 27 | } 28 | 29 | pub fn iter(&self) -> impl Iterator { 30 | ReportIter { 31 | reports: self.reports.values().peekable(), 32 | persistent_warn_reports: self.persistent_warns.iter().peekable(), 33 | } 34 | } 35 | 36 | pub fn n_reports(&self) -> usize { 37 | self.reports.len() + self.persistent_warns.len() 38 | } 39 | 40 | pub fn append_persitent_warn(&mut self, cycles: u64, content: ReportContent) { 41 | let new_report = Report { cycles, content }; 42 | if let Some(report) = self 43 | .persistent_warns 44 | .iter_mut() 45 | .find(|report| report.content == new_report.content) 46 | { 47 | if new_report.cycles - report.cycles > 1000 { 48 | *report = new_report; 49 | } 50 | } else { 51 | self.persistent_warns.push(new_report); 52 | } 53 | } 54 | 55 | pub fn remove_persitent_warn(&mut self, target: &ReportContent) { 56 | self.persistent_warns 57 | .retain(|report| discriminant(&report.content) != discriminant(target)); 58 | } 59 | 60 | pub fn remove_outdated(&mut self, cycles: u64, span: u64) { 61 | self.reports.retain(|_, report| { 62 | if report.content.remove_by_cycle_progress() { 63 | report.cycles + span > cycles 64 | } else { 65 | true 66 | } 67 | }); 68 | } 69 | } 70 | 71 | struct ReportIter<'a> { 72 | reports: std::iter::Peekable, Report>>, 73 | persistent_warn_reports: std::iter::Peekable>, 74 | } 75 | 76 | impl<'a> Iterator for ReportIter<'a> { 77 | type Item = &'a Report; 78 | 79 | fn next(&mut self) -> Option { 80 | match (self.reports.peek(), self.persistent_warn_reports.peek()) { 81 | (Some(report), Some(temp_report)) => { 82 | if report.cycles > temp_report.cycles { 83 | self.reports.next() 84 | } else { 85 | self.persistent_warn_reports.next() 86 | } 87 | } 88 | (Some(_), None) => self.reports.next(), 89 | (None, Some(_)) => self.persistent_warn_reports.next(), 90 | (None, None) => None, 91 | } 92 | } 93 | } 94 | 95 | #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] 96 | pub enum ReportContent { 97 | WarnHighTemp, 98 | WarnLowTemp, 99 | WarnLowOxygen, 100 | WarnLowCarbonDioxide, 101 | EventCivilized { 102 | pos: Coords, 103 | animal: AnimalId, 104 | }, 105 | EventCivAdvance { 106 | pos: Coords, 107 | id: AnimalId, 108 | name: String, 109 | age: CivilizationAge, 110 | }, 111 | EventCivExtinct { 112 | id: AnimalId, 113 | name: String, 114 | }, 115 | EventCivDecadence { 116 | pos: Coords, 117 | id: AnimalId, 118 | name: String, 119 | }, 120 | EventNuclearWar {}, 121 | EventInterSpeciesWar { 122 | id_a: AnimalId, 123 | id_b: AnimalId, 124 | name_a: String, 125 | name_b: String, 126 | }, 127 | EventInterSpeciesWarCeased { 128 | id_a: AnimalId, 129 | id_b: AnimalId, 130 | name_a: String, 131 | name_b: String, 132 | }, 133 | } 134 | 135 | impl ReportContent { 136 | pub fn remove_by_cycle_progress(&self) -> bool { 137 | !matches!( 138 | self, 139 | Self::WarnHighTemp 140 | | Self::WarnLowTemp 141 | | Self::WarnLowOxygen 142 | | Self::WarnLowCarbonDioxide 143 | ) 144 | } 145 | 146 | pub fn pos(&self) -> Option { 147 | match self { 148 | Self::EventCivilized { pos, .. } 149 | | Self::EventCivAdvance { pos, .. } 150 | | Self::EventCivDecadence { pos, .. } => Some(*pos), 151 | _ => None, 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/planet/requirement.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)] 4 | #[serde(rename_all = "snake_case", tag = "req")] 5 | pub enum Requirement { 6 | StructureBuilt { 7 | kind: StructureKind, 8 | n: u32, 9 | }, 10 | SpaceBuildingBuilt { 11 | kind: SpaceBuildingKind, 12 | n: u32, 13 | }, 14 | BiomeTiles { 15 | biomes: Vec, 16 | n: u32, 17 | }, 18 | PartialPressureHigherThan { 19 | kind: GasKind, 20 | value: f32, 21 | }, 22 | AnimalTiles { 23 | id: AnimalId, 24 | size: AnimalSize, 25 | n: u32, 26 | }, 27 | Settlements { 28 | n: u32, 29 | animal_id: Option, 30 | }, 31 | CivPopGrowthAdjust { 32 | range: std::ops::RangeInclusive, 33 | }, 34 | OrbitalMirrorAdjust { 35 | range: std::ops::RangeInclusive, 36 | }, 37 | } 38 | 39 | impl Requirement { 40 | pub fn check(&self, planet: &Planet) -> bool { 41 | match self { 42 | Self::StructureBuilt { kind, n } => { 43 | planet 44 | .map 45 | .iter() 46 | .filter(|tile| { 47 | tile.structure.as_ref().map(|structure| structure.kind()) == Some(*kind) 48 | }) 49 | .count() 50 | >= *n as usize 51 | } 52 | Self::SpaceBuildingBuilt { kind, n } => planet.space_building(*kind).n >= *n, 53 | Self::BiomeTiles { biomes, n } => { 54 | planet 55 | .map 56 | .iter() 57 | .filter(|tile| biomes.contains(&tile.biome)) 58 | .count() 59 | >= *n as usize 60 | } 61 | Self::PartialPressureHigherThan { kind, value } => { 62 | planet.atmo.partial_pressure(*kind) >= *value 63 | } 64 | Self::AnimalTiles { id, size, n } => { 65 | planet 66 | .map 67 | .iter() 68 | .filter(|tile| { 69 | if let Some(animal) = tile.animal[*size as usize] { 70 | &animal.id == id 71 | } else { 72 | false 73 | } 74 | }) 75 | .count() 76 | >= *n as usize 77 | } 78 | Self::Settlements { n, animal_id } => { 79 | planet 80 | .map 81 | .iter() 82 | .filter(|tile| { 83 | if let Some(Structure::Settlement(settlement)) = tile.structure { 84 | if let Some(animal_id) = animal_id { 85 | if animal_id != &settlement.id { 86 | return false; 87 | } 88 | } 89 | true 90 | } else { 91 | false 92 | } 93 | }) 94 | .count() 95 | >= *n as usize 96 | } 97 | Self::CivPopGrowthAdjust { range } => { 98 | for civ in planet.civs.values() { 99 | if range.contains(&civ.civ_control.pop_growth) { 100 | return true; 101 | } 102 | } 103 | false 104 | } 105 | Self::OrbitalMirrorAdjust { range } => { 106 | if let BuildingControlValue::IncreaseRate(rate) = 107 | &planet.space_building(SpaceBuildingKind::OrbitalMirror).control 108 | { 109 | range.contains(rate) 110 | } else { 111 | false 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/planet/resources.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | const MATERIAL_MAX: f32 = 0.999e+6; 5 | const GENE_POINT_MAX: f32 = 999.0; 6 | 7 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] 8 | pub struct Resources { 9 | pub power: f32, 10 | pub used_power: f32, 11 | pub material: f32, 12 | pub diff_material: f32, 13 | pub gene_point: f32, 14 | pub diff_gene_point: f32, 15 | } 16 | 17 | impl Resources { 18 | pub fn new(start_params: &StartParams) -> Self { 19 | Self { 20 | material: start_params.material, 21 | ..Default::default() 22 | } 23 | } 24 | 25 | pub fn surplus_power(&self) -> f32 { 26 | self.power - self.used_power 27 | } 28 | 29 | pub fn reset_before_update(&mut self) { 30 | self.power = 0.0; 31 | self.used_power = 0.0; 32 | self.diff_material = 0.0; 33 | self.diff_gene_point = 0.0; 34 | } 35 | 36 | pub fn apply_diff(&mut self) { 37 | self.material = (self.material + self.diff_material).min(MATERIAL_MAX); 38 | self.gene_point = (self.gene_point + self.diff_gene_point).min(GENE_POINT_MAX); 39 | } 40 | 41 | pub fn debug_max(&mut self) { 42 | self.material = MATERIAL_MAX; 43 | self.gene_point = GENE_POINT_MAX; 44 | } 45 | 46 | pub fn consume(&mut self, cost: Cost) { 47 | match cost { 48 | Cost::Power(_, _) => todo!(), 49 | Cost::Material(value) => { 50 | assert!(self.material >= value); 51 | self.material -= value; 52 | } 53 | Cost::GenePoint(value) => { 54 | assert!(self.gene_point >= value); 55 | self.gene_point -= value; 56 | } 57 | } 58 | } 59 | 60 | pub fn enough_to_consume(&self, cost: Cost) -> bool { 61 | match cost { 62 | Cost::Power(value, _) => self.surplus_power() > value, 63 | Cost::Material(value) => self.material >= value, 64 | Cost::GenePoint(value) => self.gene_point >= value, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/planet/serde_with_types.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use serde_with::{DeserializeAs, SerializeAs}; 3 | 4 | use super::defs::KELVIN_CELSIUS; 5 | 6 | #[derive(Clone, Copy, Debug)] 7 | pub struct Celsius; 8 | 9 | impl SerializeAs for Celsius { 10 | fn serialize_as(source: &f32, serializer: S) -> Result 11 | where 12 | S: serde::Serializer, 13 | { 14 | serializer.serialize_f32(*source - KELVIN_CELSIUS) 15 | } 16 | } 17 | 18 | impl<'de> DeserializeAs<'de, f32> for Celsius { 19 | fn deserialize_as(deserializer: D) -> Result 20 | where 21 | D: serde::Deserializer<'de>, 22 | { 23 | let v = f32::deserialize(deserializer).map_err(serde::de::Error::custom)? + KELVIN_CELSIUS; 24 | 25 | if !v.is_finite() { 26 | return Err(serde::de::Error::custom( 27 | "invalid float value for temperature", 28 | )); 29 | } 30 | 31 | Ok(v) 32 | } 33 | } 34 | 35 | #[derive(Clone, Copy, Debug)] 36 | pub struct Percent; 37 | 38 | impl SerializeAs for Percent { 39 | fn serialize_as(source: &f32, serializer: S) -> Result 40 | where 41 | S: serde::Serializer, 42 | { 43 | serializer.serialize_f32(*source / 100.0) 44 | } 45 | } 46 | 47 | impl<'de> DeserializeAs<'de, f32> for Percent { 48 | fn deserialize_as(deserializer: D) -> Result 49 | where 50 | D: serde::Deserializer<'de>, 51 | { 52 | let v = f32::deserialize(deserializer).map_err(serde::de::Error::custom)? / 100.0; 53 | 54 | if !v.is_finite() { 55 | return Err(serde::de::Error::custom("invalid float value for percent")); 56 | } 57 | 58 | Ok(v) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/planet/stat.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use super::*; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Clone, Debug, Serialize, Deserialize)] 8 | pub struct Stat { 9 | pub average_air_temp: f32, 10 | pub average_sea_temp: f32, 11 | pub average_rainfall: f32, 12 | pub sum_biomass: f32, 13 | pub sum_buried_carbon: f32, 14 | history: VecDeque, 15 | } 16 | 17 | #[derive(Clone, Debug, Serialize, Deserialize)] 18 | pub struct Record { 19 | pub average_air_temp: f32, 20 | pub average_sea_temp: f32, 21 | pub average_rainfall: f32, 22 | pub biomass: f32, 23 | pub buried_carbon: f32, 24 | pub p_o2: f32, 25 | pub p_n2: f32, 26 | pub p_co2: f32, 27 | pub pop: fnv::FnvHashMap, 28 | } 29 | 30 | impl Stat { 31 | pub fn new(params: &Params) -> Self { 32 | Self { 33 | average_air_temp: 0.0, 34 | average_sea_temp: 0.0, 35 | average_rainfall: 0.0, 36 | sum_biomass: 0.0, 37 | sum_buried_carbon: 0.0, 38 | history: VecDeque::with_capacity(params.history.max_record + 1), 39 | } 40 | } 41 | 42 | pub fn history(&self) -> &VecDeque { 43 | &self.history 44 | } 45 | 46 | pub fn record(&self, cycles: u64, params: &Params) -> Option<&Record> { 47 | let n = cycles / params.history.interval_cycles; 48 | self.history.get(n as usize) 49 | } 50 | 51 | pub fn clear_history(&mut self) { 52 | self.history.clear(); 53 | } 54 | } 55 | 56 | pub fn record_stats(planet: &mut Planet, params: &Params) { 57 | if planet.cycles % params.history.interval_cycles != 0 { 58 | return; 59 | } 60 | 61 | let mut pop = fnv::FnvHashMap::default(); 62 | for p in planet.map.iter_idx() { 63 | if let Some(Structure::Settlement(settlement)) = &planet.map[p].structure { 64 | *pop.entry(settlement.id).or_default() += settlement.pop; 65 | } 66 | } 67 | 68 | let record = Record { 69 | average_air_temp: planet.stat.average_air_temp, 70 | average_sea_temp: planet.stat.average_sea_temp, 71 | average_rainfall: planet.stat.average_rainfall, 72 | biomass: planet.stat.sum_biomass, 73 | buried_carbon: planet.stat.sum_buried_carbon, 74 | p_o2: planet.atmo.partial_pressure(GasKind::Oxygen), 75 | p_n2: planet.atmo.partial_pressure(GasKind::Nitrogen), 76 | p_co2: planet.atmo.partial_pressure(GasKind::CarbonDioxide), 77 | pop, 78 | }; 79 | 80 | planet.stat.history.push_front(record); 81 | if planet.stat.history.len() > params.history.max_record { 82 | planet.stat.history.pop_back(); 83 | } 84 | } 85 | 86 | impl Record { 87 | pub fn pop(&self, animal_id: Option) -> f32 { 88 | if let Some(animal_id) = animal_id { 89 | self.pop.get(&animal_id).copied().unwrap_or_default() 90 | } else { 91 | self.pop.values().sum() 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/planet/water.rs: -------------------------------------------------------------------------------- 1 | use super::misc::{bisection, linear_interpolation}; 2 | use super::*; 3 | use geom::Direction; 4 | use rayon::prelude::*; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Clone, Debug, Serialize, Deserialize)] 8 | pub struct Water { 9 | /// Volume of water including ice [m^3] 10 | pub water_volume: f32, 11 | /// Sea level [m] 12 | pub sea_level: f32, 13 | /// Volume of ice [m^3] 14 | pub ice_volume: f32, 15 | } 16 | 17 | impl Water { 18 | pub fn new(start_params: &StartParams) -> Self { 19 | Water { 20 | water_volume: start_params.water_volume, 21 | sea_level: 0.0, 22 | ice_volume: 0.0, 23 | } 24 | } 25 | 26 | pub fn sea_water_volume(&self) -> f32 { 27 | let v = self.water_volume - self.ice_volume; 28 | if v > 0.0 { v } else { 0.0 } 29 | } 30 | } 31 | 32 | pub fn sim_water(planet: &mut Planet, sim: &mut Sim, params: &Params) { 33 | update_sea_level(planet, sim, params); 34 | advance_rainfall_calc(planet, sim, params); 35 | snow_calc(planet, sim, params); 36 | } 37 | 38 | pub fn update_sea_level(planet: &mut Planet, sim: &Sim, params: &Params) { 39 | planet.water.sea_level = bisection(|x| target_function(planet, sim, x), 0.0, 10000.0, 10, 10.0); 40 | 41 | for p in planet.map.iter_idx() { 42 | let tile = &mut planet.map[p]; 43 | 44 | if tile.height < planet.water.sea_level && tile.biome.is_land() { 45 | tile.biome = Biome::Ocean; 46 | tile.sea_temp = tile.temp; 47 | } else if tile.height >= planet.water.sea_level && tile.biome.is_sea() { 48 | tile.fertility *= params.sim.change_from_ocean_fertility_factor; 49 | tile.biome = Biome::Rock; 50 | } 51 | } 52 | } 53 | 54 | fn target_function(planet: &Planet, sim: &Sim, assumed_sea_level: f32) -> f32 { 55 | let mut v = 0.0; 56 | 57 | for p in planet.map.iter_idx() { 58 | let h = planet.map[p].height; 59 | 60 | if h < assumed_sea_level { 61 | v += (assumed_sea_level - h) * sim.tile_area; 62 | } 63 | } 64 | 65 | v - planet.water.sea_water_volume() 66 | } 67 | 68 | pub fn advance_rainfall_calc(planet: &mut Planet, sim: &mut Sim, params: &Params) { 69 | let map_iter_idx = planet.map.iter_idx(); 70 | let size = planet.map.size(); 71 | let coords_converter = sim.coords_converter(); 72 | 73 | // Calculate new vapor amount of tiles 74 | for _ in 0..params.sim.n_loop_vapor_calc { 75 | let par_iter = sim.vapor_new.par_iter_mut().enumerate(); 76 | par_iter.for_each(|(i, vapor_new)| { 77 | let p = Coords::from_index_size(i, size); 78 | let biome = planet.map[p].biome; 79 | let building_effect = planet.working_building_effect(p, params); 80 | 81 | if biome == Biome::Ocean { 82 | *vapor_new = 83 | linear_interpolation(¶ms.sim.ocean_vaporization_table, planet.map[p].temp) 84 | / RAINFALL_DURATION; 85 | } else if let Some(BuildingEffect::Vapor { value }) = building_effect { 86 | *vapor_new = value / RAINFALL_DURATION; 87 | } else { 88 | let adjacent_tile_flow: f32 = Direction::FOUR_DIRS 89 | .into_iter() 90 | .map(|dir| { 91 | if let Some(adjacent_tile) = coords_converter.conv(p + dir.as_coords()) { 92 | let diff_height = 93 | (planet.map[adjacent_tile].height - planet.map[p].height).max(0.0); 94 | let d = params.sim.vapor_diffusion_factor 95 | / (1.0 96 | + diff_height 97 | * params.sim.coeff_vapor_diffusion_adjust_by_h_diff); 98 | let delta_vapor = (sim.vapor[adjacent_tile] - sim.vapor[p]).max(0.0); 99 | 0.5 * d * delta_vapor 100 | } else { 101 | 0.0 102 | } 103 | }) 104 | .sum(); 105 | let loss = sim.vapor[p] 106 | * params.sim.vapor_loss_ratio 107 | * (1.0 - params.biomes[&biome].revaporization_ratio); 108 | *vapor_new = sim.vapor[p] + adjacent_tile_flow - loss; 109 | } 110 | }); 111 | std::mem::swap(&mut sim.vapor, &mut sim.vapor_new); 112 | } 113 | 114 | let mut sum_rainfall = 0.0; 115 | 116 | // Set calculated new rainfall 117 | for p in map_iter_idx { 118 | planet.map[p].vapor = sim.vapor[p]; 119 | let rainfall = sim.vapor[p] * RAINFALL_DURATION; 120 | planet.map[p].rainfall = rainfall; 121 | sum_rainfall += rainfall as f64; 122 | let temp = (planet.map[p].temp - KELVIN_CELSIUS).max(0.0); 123 | sim.humidity[p] = (rainfall 124 | - params.sim.drying_factors.0 * (temp - params.sim.drying_factors.1)) 125 | .max(0.0); 126 | } 127 | 128 | planet.stat.average_rainfall = sum_rainfall as f32 / planet.n_tile() as f32; 129 | } 130 | 131 | pub fn snow_calc(planet: &mut Planet, sim: &mut Sim, params: &Params) { 132 | let mut ice_height_sum = 0.0; 133 | 134 | for p in planet.map.iter_idx() { 135 | let t = planet.map[p].temp; 136 | let tile = &mut planet.map[p]; 137 | 138 | if t > params.sim.ice_melting_temp { 139 | if tile.ice > 0.0 { 140 | let d = t - params.sim.ice_melting_temp; 141 | tile.ice -= params.sim.ice_melting_height_per_temp * d; 142 | if tile.ice < 0.0 { 143 | tile.ice = 0.0; 144 | } 145 | } 146 | } else { 147 | let a = tile.rainfall + 0.1 * -(t - KELVIN_CELSIUS); 148 | debug_assert!(a >= 0.0); 149 | let ice_limit = linear_interpolation(¶ms.sim.ice_thickness_limit_table, a); 150 | let mass = tile.rainfall * params.sim.fallen_snow_factor; 151 | if ice_limit > tile.ice + mass { 152 | tile.ice += mass; 153 | } else if ice_limit > tile.ice { 154 | tile.ice = ice_limit; 155 | } 156 | } 157 | 158 | ice_height_sum += tile.ice as f64; 159 | } 160 | 161 | planet.water.ice_volume = ice_height_sum as f32 * sim.tile_area; 162 | } 163 | -------------------------------------------------------------------------------- /src/platform/client.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, Write}; 2 | 3 | use anyhow::{Result, bail}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Clone, Debug, Serialize, Deserialize)] 7 | pub enum Request { 8 | Start {}, 9 | } 10 | 11 | #[derive(Clone, Debug, Serialize, Deserialize)] 12 | pub enum Response { 13 | Start {}, 14 | } 15 | 16 | pub fn run_client(port: u16) { 17 | let stream = std::net::TcpStream::connect(format!("127.0.0.1:{}", port)) 18 | .expect("cannot open stream with launcher"); 19 | 20 | std::thread::spawn(move || { 21 | if let Err(e) = client_task(stream) { 22 | eprintln!("launcher connection error: {:?}", e); 23 | } 24 | }); 25 | } 26 | 27 | fn client_task(stream: std::net::TcpStream) -> Result<()> { 28 | let mut client = Client::new(stream); 29 | 30 | client.send(Request::Start {})?; 31 | let resp = client.recv()?; 32 | if !matches!(resp, Response::Start {}) { 33 | bail!("invalid response"); 34 | } 35 | eprintln!("connected to game launcher"); 36 | 37 | Ok(()) 38 | } 39 | 40 | pub struct Client { 41 | stream: std::io::BufReader, 42 | buf: Vec, 43 | } 44 | 45 | impl Client { 46 | fn new(stream: std::net::TcpStream) -> Self { 47 | Self { 48 | stream: std::io::BufReader::new(stream), 49 | buf: Vec::new(), 50 | } 51 | } 52 | 53 | fn recv(&mut self) -> Result { 54 | self.buf.clear(); 55 | self.stream.read_until(b'\0', &mut self.buf)?; 56 | let resp = serde_json::from_slice(&self.buf[0..self.buf.len() - 1])?; 57 | Ok(resp) 58 | } 59 | 60 | fn send(&mut self, req: Request) -> Result<()> { 61 | self.buf.clear(); 62 | serde_json::to_writer(&mut self.buf, &req)?; 63 | self.buf.push(b'\0'); 64 | self.stream.get_mut().write_all(&self.buf)?; 65 | Ok(()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/platform/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | #[cfg(not(target_arch = "wasm32"))] 3 | mod native; 4 | #[cfg(target_arch = "wasm32")] 5 | mod wasm; 6 | 7 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 8 | pub enum PreferredWindowResolution { 9 | Size(u32, u32), 10 | #[allow(unused)] 11 | Maximized, 12 | } 13 | 14 | impl Default for PreferredWindowResolution { 15 | fn default() -> Self { 16 | Self::Size(1600, 900) 17 | } 18 | } 19 | 20 | #[cfg(not(target_arch = "wasm32"))] 21 | pub use native::*; 22 | #[cfg(target_arch = "wasm32")] 23 | pub use wasm::*; 24 | -------------------------------------------------------------------------------- /src/text.rs: -------------------------------------------------------------------------------- 1 | use compact_str::format_compact; 2 | 3 | use crate::planet::{Report, ReportContent}; 4 | 5 | #[derive(Clone, Copy, PartialEq, Debug)] 6 | pub enum WithUnitDisplay { 7 | Power(f32), 8 | Material(f32), 9 | GenePoint(f32), 10 | } 11 | 12 | impl std::fmt::Display for WithUnitDisplay { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 14 | match *self { 15 | WithUnitDisplay::Power(value) => { 16 | write!(f, "{}TW", value) 17 | } 18 | WithUnitDisplay::Material(value) => { 19 | if value < 1.0 { 20 | write!(f, "{}Mt", value) 21 | } else if value < 100000.0 { 22 | write!(f, "{:.0}Mt", value) 23 | } else { 24 | write!(f, "{:.0}Gt", value / 1000.0) 25 | } 26 | } 27 | WithUnitDisplay::GenePoint(value) => { 28 | write!(f, "{:.0}", value) 29 | } 30 | } 31 | } 32 | } 33 | 34 | impl Report { 35 | pub fn text(&self) -> (MsgStyle, String) { 36 | use MsgStyle::*; 37 | match &self.content { 38 | ReportContent::WarnHighTemp => (Warn, t!("report/warn-high-temp")), 39 | ReportContent::WarnLowTemp => (Warn, t!("report/warn-low-temp")), 40 | ReportContent::WarnLowOxygen => (Warn, t!("report/warn-low-oxygen")), 41 | ReportContent::WarnLowCarbonDioxide => (Warn, t!("report/warn-low-carbon-dioxide")), 42 | ReportContent::EventCivilized { animal, .. } => { 43 | let animal = t!("animal", animal); 44 | (Notice, t!("report/civilized"; animal = animal)) 45 | } 46 | ReportContent::EventCivAdvance { age, name, .. } => { 47 | let age = t!("age", age); 48 | (Notice, t!("report/civ-advance"; civ = name, age = age)) 49 | } 50 | ReportContent::EventCivExtinct { name, .. } => { 51 | (Notice, t!("report/civ-extinct"; civ = name)) 52 | } 53 | ReportContent::EventCivDecadence { name, .. } => { 54 | (Notice, t!("report/civ-decadence"; civ = name)) 55 | } 56 | ReportContent::EventInterSpeciesWar { name_a, name_b, .. } => ( 57 | Notice, 58 | t!("report/inter-species-war"; civ_a = name_a, civ_b = name_b), 59 | ), 60 | ReportContent::EventInterSpeciesWarCeased { name_a, name_b, .. } => ( 61 | Notice, 62 | t!("report/inter-species-war-ceased"; civ_a = name_a, civ_b = name_b), 63 | ), 64 | ReportContent::EventNuclearWar { .. } => (Notice, t!("report/nuclear-war")), 65 | } 66 | } 67 | } 68 | 69 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 70 | pub enum MsgStyle { 71 | Notice, 72 | Warn, 73 | } 74 | 75 | impl MsgStyle { 76 | pub fn icon(&self) -> &str { 77 | match self { 78 | MsgStyle::Notice => "ℹ", 79 | MsgStyle::Warn => "⚠", 80 | } 81 | } 82 | } 83 | 84 | pub fn format_float_1000(value: f32, precision: usize) -> compact_str::CompactString { 85 | if value > 1000.0 || precision == 0 { 86 | format_compact!("{:.0}", value) 87 | } else if value > 100.0 || precision == 1 { 88 | format_compact!("{:.1}", value) 89 | } else if value > 10.0 || precision == 2 { 90 | format_compact!("{:.2}", value) 91 | } else if value > 1.0 || precision == 3 { 92 | format_compact!("{:.3}", value) 93 | } else { 94 | format_compact!("{}", value) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/title_screen.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use crate::{GameState, draw::UpdateDraw}; 4 | 5 | #[derive(Clone, Copy, Debug)] 6 | pub struct TitleScreenPlugin; 7 | 8 | impl Plugin for TitleScreenPlugin { 9 | fn build(&self, app: &mut App) { 10 | app.add_event::() 11 | .add_systems(Startup, setup_title_screen) 12 | .add_systems(OnEnter(GameState::MainMenu), enter_title_screen) 13 | .add_systems(OnExit(GameState::MainMenu), exit_title_screen) 14 | .add_systems(Update, on_resize.run_if(in_state(GameState::MainMenu))); 15 | } 16 | } 17 | 18 | #[derive(Component)] 19 | struct TitleScreen; 20 | 21 | #[derive(Component)] 22 | pub struct TitleScreenLogo; 23 | 24 | #[derive(Component)] 25 | pub struct TitleScreenBackground; 26 | 27 | const LOGO_SIZE: (f32, f32) = (316.0, 250.0); 28 | const BACKGROUND_SIZE: (f32, f32) = (1920.0, 896.0); 29 | 30 | fn setup_title_screen( 31 | mut commands: Commands, 32 | mut meshes: ResMut>, 33 | mut materials: ResMut>, 34 | asset_server: Res, 35 | ) { 36 | // Background 37 | let bg_material = materials.add(ColorMaterial { 38 | color: Srgba { 39 | red: 0.05, 40 | green: 0.05, 41 | blue: 0.05, 42 | alpha: 1.0, 43 | } 44 | .into(), 45 | ..default() 46 | }); 47 | let bg_mesh = meshes.add(Mesh::from(Rectangle::new(100000.0, 100000.0))); 48 | commands 49 | .spawn(( 50 | Mesh2d(bg_mesh), 51 | MeshMaterial2d(bg_material), 52 | Transform::from_xyz(0.0, 0.0, 990.0), 53 | )) 54 | .insert(TitleScreen); 55 | 56 | // Logo 57 | let logo = asset_server.load("logo.webp"); 58 | let mesh_handle = meshes.add(Rectangle::from_size(Vec2::new(LOGO_SIZE.0, LOGO_SIZE.1))); 59 | commands 60 | .spawn(( 61 | Mesh2d(mesh_handle.clone()), 62 | MeshMaterial2d(materials.add(ColorMaterial { 63 | texture: Some(logo), 64 | ..default() 65 | })), 66 | Transform::from_xyz(0.0, LOGO_SIZE.1 / 2.0, 992.0), 67 | )) 68 | .insert(TitleScreen) 69 | .insert(TitleScreenLogo); 70 | 71 | // Background 72 | let background = asset_server.load("title-screen.webp"); 73 | let mesh_handle = meshes.add(Rectangle::from_size(Vec2::new( 74 | BACKGROUND_SIZE.0, 75 | BACKGROUND_SIZE.1, 76 | ))); 77 | commands 78 | .spawn(( 79 | Mesh2d(mesh_handle.clone()), 80 | MeshMaterial2d(materials.add(ColorMaterial { 81 | texture: Some(background), 82 | ..default() 83 | })), 84 | Transform::from_xyz(0.0, 0.0, 991.0), 85 | )) 86 | .insert(TitleScreen) 87 | .insert(TitleScreenBackground); 88 | } 89 | 90 | fn enter_title_screen( 91 | mut camera_query: Query<(&mut OrthographicProjection, &mut Transform)>, 92 | mut meshes: Query<&mut Visibility, With>, 93 | mut bg_transform: Query< 94 | &mut Transform, 95 | (With, Without), 96 | >, 97 | window: Query<&Window, With>, 98 | ) { 99 | let Ok(window) = window.get_single() else { 100 | return; 101 | }; 102 | let mut camera = camera_query.single_mut(); 103 | camera.0.scale = 1.0; 104 | let cpos = &mut camera.1.translation; 105 | cpos.x = 0.0; 106 | cpos.y = 0.0; 107 | crate::screen::adjust_camera_pos(&mut cpos.x, &mut cpos.y, window.width(), window.height()); 108 | 109 | for mut bg in meshes.iter_mut() { 110 | *bg = Visibility::Visible; 111 | } 112 | 113 | let bg_scale = (window.width() / BACKGROUND_SIZE.0) 114 | .max(window.height() / BACKGROUND_SIZE.1) 115 | .max(1.0); 116 | let mut t = bg_transform.get_single_mut().unwrap(); 117 | *t = t.with_scale(Vec3::new(bg_scale, bg_scale, 1.0)); 118 | } 119 | 120 | fn exit_title_screen(mut meshes: Query<&mut Visibility, With>) { 121 | for mut bg in meshes.iter_mut() { 122 | *bg = Visibility::Hidden; 123 | } 124 | } 125 | 126 | fn on_resize( 127 | mut er: EventReader, 128 | mut camera_query: Query<(&OrthographicProjection, &mut Transform)>, 129 | mut bg_transform: Query< 130 | &mut Transform, 131 | (With, Without), 132 | >, 133 | ) { 134 | let cpos = &mut camera_query.get_single_mut().unwrap().1.translation; 135 | if let Some(e) = er.read().last() { 136 | crate::screen::adjust_camera_pos(&mut cpos.x, &mut cpos.y, e.width, e.height); 137 | 138 | let bg_scale = (e.width / BACKGROUND_SIZE.0) 139 | .max(e.height / BACKGROUND_SIZE.1) 140 | .max(1.0); 141 | let mut t = bg_transform.get_single_mut().unwrap(); 142 | *t = t.with_scale(Vec3::new(bg_scale, bg_scale, 1.0)); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/tools.rs: -------------------------------------------------------------------------------- 1 | macro_rules! define_asset_list_from_enum { 2 | ( 3 | #[asset(dir_path = $dir_path:expr)] 4 | #[asset(extension = $file_ext:expr)] 5 | $struct_vis:vis struct $struct_name:ident { 6 | $field_vis:vis $field:ident: HashMap<$enum_name:ident, Handle<$asset_type:ty>>, 7 | } 8 | ) => { 9 | #[derive(bevy::prelude::Resource)] 10 | $struct_vis struct $struct_name { 11 | $field_vis $field: HashMap<$enum_name, Handle<$asset_type>>, 12 | } 13 | 14 | impl $struct_name { 15 | pub fn get(&self, e: $enum_name) -> Handle<$asset_type> { 16 | self.$field[&e].clone() 17 | } 18 | } 19 | 20 | impl AssetCollection for $struct_name { 21 | fn create(world: &mut World) -> Self { 22 | world.resource_scope( 23 | |world, _asset_keys: Mut<::bevy_asset_loader::dynamic_asset::DynamicAssets>| { 24 | let mut map = HashMap::default(); 25 | for e in <$enum_name as strum::IntoEnumIterator>::iter() { 26 | let s: &str = e.as_ref(); 27 | let asset_server = world 28 | .get_resource::() 29 | .expect("Cannot get AssetServer"); 30 | let handle = asset_server 31 | .get_handle(&format!("{}/{}.{}", $dir_path, s, $file_ext)).unwrap(); 32 | map.insert(e, handle); 33 | } 34 | $struct_name { $field: map } 35 | }, 36 | ) 37 | } 38 | 39 | fn load(world: &mut World) -> Vec { 40 | let asset_server = world 41 | .get_resource::() 42 | .expect("Cannot get AssetServer"); 43 | let _asset_keys = world 44 | .get_resource::() 45 | .expect("Cannot get bevy_asset_loader::prelude::DynamicAssets"); 46 | 47 | <$enum_name as strum::IntoEnumIterator>::iter() 48 | .map(|e| { 49 | let s: &str = e.as_ref(); 50 | asset_server.load_untyped(&format!("{}/{}.{}", $dir_path, s, $file_ext)).untyped() 51 | }) 52 | .collect() 53 | } 54 | } 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/ui/achivements.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_egui::{EguiContexts, egui}; 3 | use compact_str::format_compact; 4 | 5 | use super::{UiTextures, WindowsOpenState}; 6 | use crate::{ 7 | achivement_save::UnlockedAchivements, planet::ACHIVEMENTS, screen::OccupiedScreenSpace, 8 | }; 9 | 10 | pub fn achivements_window( 11 | mut egui_ctxs: EguiContexts, 12 | mut occupied_screen_space: ResMut, 13 | mut wos: ResMut, 14 | unlocked_achivements: Res, 15 | textures: Res, 16 | ) { 17 | if !wos.achivements { 18 | return; 19 | } 20 | 21 | let ctx = egui_ctxs.ctx_mut(); 22 | let rect = egui::Window::new(t!("achivements")) 23 | .constrain_to(super::misc::constrain_to_rect(ctx, &occupied_screen_space)) 24 | .resizable(egui::Vec2b::new(false, false)) 25 | .open(&mut wos.achivements) 26 | .show(ctx, |ui| { 27 | show_achivement_list(ui, &unlocked_achivements, &textures); 28 | }) 29 | .unwrap() 30 | .response 31 | .rect; 32 | occupied_screen_space.push_egui_window_rect(rect); 33 | } 34 | 35 | fn show_achivement_list( 36 | ui: &mut egui::Ui, 37 | unlocked_achivements: &UnlockedAchivements, 38 | textures: &UiTextures, 39 | ) { 40 | let description_width = 220.0; 41 | 42 | let layout = egui::Layout::left_to_right(egui::Align::Min); 43 | for achivement_chunk in ACHIVEMENTS.chunks(6) { 44 | ui.with_layout(layout, |ui| { 45 | for achivement in achivement_chunk { 46 | let unlocked = unlocked_achivements.0.contains(achivement); 47 | let texture_name = if unlocked { 48 | format_compact!("ui/achivement-{}", achivement.as_ref()) 49 | } else { 50 | "ui/achivement-locked".into() 51 | }; 52 | ui.image(textures.get(texture_name)).on_hover_ui(|ui| { 53 | ui.set_min_width(description_width); 54 | if unlocked { 55 | ui.strong(t!("achivement", achivement.as_ref())); 56 | } else { 57 | ui.strong("???"); 58 | } 59 | ui.label(t!("achivement/desc", achivement.as_ref())); 60 | }); 61 | } 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ui/animals.rs: -------------------------------------------------------------------------------- 1 | use super::{OccupiedScreenSpace, UiTextures, WindowsOpenState, misc::label_with_icon}; 2 | use crate::{audio::SoundEffectPlayer, planet::*, screen::CursorMode, text::WithUnitDisplay}; 3 | use bevy::prelude::*; 4 | use bevy_egui::{EguiContexts, egui}; 5 | use compact_str::format_compact; 6 | 7 | #[derive(Debug)] 8 | pub struct State { 9 | ordered_ids: Vec, 10 | current: AnimalId, 11 | civ_default_name: String, 12 | civ_name: String, 13 | } 14 | 15 | pub fn animals_window( 16 | mut egui_ctxs: EguiContexts, 17 | mut occupied_screen_space: ResMut, 18 | mut wos: ResMut, 19 | mut cursor_mode: ResMut, 20 | planet: Res, 21 | params: Res, 22 | textures: Res, 23 | mut state: Local>, 24 | se_player: SoundEffectPlayer, 25 | ) { 26 | if !wos.animals { 27 | return; 28 | } 29 | if state.is_none() { 30 | *state = Some(State::new(¶ms)); 31 | } 32 | let state = state.as_mut().unwrap(); 33 | 34 | let rect = egui::Window::new("animal-window") 35 | .anchor( 36 | egui::Align2::LEFT_TOP, 37 | [ 38 | occupied_screen_space.tools_expander_width, 39 | occupied_screen_space.toolbar_height, 40 | ], 41 | ) 42 | .title_bar(false) 43 | .show(egui_ctxs.ctx_mut(), |ui| { 44 | ui.horizontal(|ui| { 45 | if ui.button("◀").clicked() { 46 | wos.animals = false; 47 | } 48 | ui.heading(t!("animals")); 49 | }); 50 | ui.separator(); 51 | 52 | ui.horizontal(|ui| { 53 | select_panel(ui, state, ¶ms, &se_player); 54 | ui.separator(); 55 | ui.vertical(|ui| { 56 | contents( 57 | ui, 58 | state, 59 | &planet, 60 | ¶ms, 61 | &textures, 62 | &mut cursor_mode, 63 | &se_player, 64 | ); 65 | }); 66 | }); 67 | }) 68 | .unwrap() 69 | .response 70 | .rect; 71 | occupied_screen_space.push_egui_window_rect(rect); 72 | } 73 | 74 | fn contents( 75 | ui: &mut egui::Ui, 76 | state: &mut State, 77 | _planet: &Planet, 78 | params: &Params, 79 | textures: &UiTextures, 80 | cursor_mode: &mut CursorMode, 81 | se_player: &SoundEffectPlayer, 82 | ) { 83 | ui.horizontal(|ui| { 84 | ui.add(egui::Image::new( 85 | textures.get(format_compact!("animals/{}", state.current)), 86 | )); 87 | ui.heading(t!("animal", state.current)); 88 | }); 89 | 90 | let attr = params.animals.get(&state.current).unwrap(); 91 | 92 | egui::Grid::new("table_atmo").striped(true).show(ui, |ui| { 93 | ui.label(t!("size")); 94 | ui.label(t!(attr.size)); 95 | ui.end_row(); 96 | 97 | ui.label(t!("cost")); 98 | label_with_icon( 99 | ui, 100 | textures, 101 | "ui/icon-gene", 102 | WithUnitDisplay::GenePoint(attr.cost).to_string(), 103 | ); 104 | ui.end_row(); 105 | 106 | ui.label(t!("habitat")); 107 | let s = match &attr.habitat { 108 | AnimalHabitat::Land => t!("land"), 109 | AnimalHabitat::Sea => t!("sea"), 110 | AnimalHabitat::Biomes(biomes) => biomes.iter().fold(String::new(), |s, biome| { 111 | if s.is_empty() { 112 | t!(biome) 113 | } else { 114 | format!("{}, {}", s, t!(biome)) 115 | } 116 | }), 117 | }; 118 | ui.label(s); 119 | ui.end_row(); 120 | 121 | ui.label(t!("livable-temperature")); 122 | ui.label(format!( 123 | "{}°C - {}°C", 124 | attr.temp.0 - KELVIN_CELSIUS, 125 | attr.temp.1 - KELVIN_CELSIUS, 126 | )); 127 | ui.end_row(); 128 | 129 | if attr.civ.is_some() { 130 | ui.label(t!("civilizable")); 131 | ui.end_row(); 132 | } 133 | }); 134 | 135 | ui.separator(); 136 | ui.horizontal(|ui| { 137 | if ui.button(t!("spawn")).clicked() { 138 | *cursor_mode = CursorMode::SpawnAnimal(state.current); 139 | se_player.play("select-item"); 140 | } 141 | }); 142 | } 143 | 144 | fn select_panel( 145 | ui: &mut egui::Ui, 146 | state: &mut State, 147 | params: &Params, 148 | se_player: &SoundEffectPlayer, 149 | ) { 150 | let before = state.current; 151 | egui::ScrollArea::vertical() 152 | .min_scrolled_height(200.0) 153 | .show(ui, |ui| { 154 | ui.set_min_width(80.0); 155 | ui.set_min_height(180.0); 156 | ui.vertical(|ui| { 157 | for id in &state.ordered_ids { 158 | if ui 159 | .selectable_value(&mut state.current, *id, t!("animal", id)) 160 | .clicked() 161 | { 162 | se_player.play("select-item"); 163 | } 164 | } 165 | }); 166 | }); 167 | 168 | // Selected animal changed 169 | if before != state.current 170 | && params 171 | .animals 172 | .get(&state.current) 173 | .map(|attr| attr.civ.is_some()) 174 | .unwrap_or_default() 175 | { 176 | state.civ_default_name = t!("civ", state.current); 177 | state.civ_name = state.civ_default_name.clone(); 178 | } 179 | } 180 | 181 | impl State { 182 | fn new(params: &Params) -> Self { 183 | let mut ids: Vec<_> = params.animals.keys().cloned().collect(); 184 | ids.sort_unstable(); 185 | let current = ids[0]; 186 | Self { 187 | ordered_ids: ids, 188 | current, 189 | civ_default_name: String::new(), 190 | civ_name: String::new(), 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/ui/dialogs.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_egui::{EguiContexts, egui}; 3 | 4 | use super::{Dialog, OccupiedScreenSpace, WindowsOpenState}; 5 | use crate::{ 6 | audio::SoundEffectPlayer, 7 | draw::UpdateDraw, 8 | manage_planet::ManagePlanetError, 9 | planet::{Params, Planet}, 10 | }; 11 | 12 | pub fn dialogs( 13 | mut egui_ctxs: EguiContexts, 14 | mut occupied_screen_space: ResMut, 15 | mut wos: ResMut, 16 | mut update_draw: ResMut, 17 | mut planet: ResMut, 18 | params: Res, 19 | se_player: SoundEffectPlayer, 20 | ) { 21 | let Some(dialog) = wos.dialogs.last() else { 22 | return; 23 | }; 24 | 25 | let mut close = false; 26 | 27 | let Dialog::Civilize { p, id } = dialog; 28 | 29 | occupied_screen_space.opening_modal = true; 30 | 31 | egui::Modal::new("civilize-animal".into()).show(egui_ctxs.ctx_mut(), |ui| { 32 | ui.vertical_centered(|ui| { 33 | ui.label(t!("msg/civilize-animal")); 34 | ui.label(t!("animal", id)); 35 | ui.separator(); 36 | if ui.button(t!("ok")).clicked() { 37 | planet.civilize_animal(*p, *id, ¶ms); 38 | close = true; 39 | update_draw.update(); 40 | se_player.play_with_priority("civilize", true); 41 | } 42 | if ui.button(t!("cancel")).clicked() { 43 | close = true; 44 | se_player.play("window-close"); 45 | } 46 | }); 47 | }); 48 | 49 | if close { 50 | wos.dialogs.pop(); 51 | } 52 | } 53 | 54 | pub fn error_popup( 55 | mut egui_ctxs: EguiContexts, 56 | mut er_manage_planet_error: EventReader, 57 | mut occupied_screen_space: ResMut, 58 | mut wos: ResMut, 59 | ) { 60 | if wos.error_popup.is_none() { 61 | if let Some(e) = er_manage_planet_error.read().next() { 62 | wos.error_popup = Some(e.clone()); 63 | } 64 | } 65 | let Some(e) = &wos.error_popup else { 66 | return; 67 | }; 68 | 69 | occupied_screen_space.opening_modal = true; 70 | 71 | let mut close = false; 72 | egui::Modal::new("error_popup".into()).show(egui_ctxs.ctx_mut(), |ui| { 73 | ui.vertical_centered(|ui| { 74 | ui_management_planet_error(ui, e); 75 | ui.separator(); 76 | if ui.button(t!("close")).clicked() { 77 | close = true; 78 | } 79 | }); 80 | }); 81 | 82 | if close { 83 | wos.error_popup = None; 84 | } 85 | } 86 | 87 | pub fn ui_management_planet_error(ui: &mut egui::Ui, e: &ManagePlanetError) { 88 | if matches!(e, ManagePlanetError::Decode) { 89 | ui.label(t!("msg/loading-failed-desc-decode-error")); 90 | } else { 91 | ui.label(t!("msg/loading-failed-desc-not-found")); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/ui/hover_tile_tooltip.rs: -------------------------------------------------------------------------------- 1 | use bevy::{prelude::*, window::PrimaryWindow}; 2 | use bevy_egui::{EguiContexts, egui}; 3 | 4 | use crate::{ 5 | conf::Conf, 6 | planet::{Cost, Params, Planet}, 7 | screen::{CursorMode, OccupiedScreenSpace}, 8 | }; 9 | 10 | use super::UiTextures; 11 | 12 | pub fn hover_tile_tooltip( 13 | mut egui_ctxs: EguiContexts, 14 | (planet, textures, params, _conf): (Res, Res, Res, Res), 15 | window: Query<&Window, With>, 16 | cursor_mode: Res, 17 | occupied_screen_space: Res, 18 | ) { 19 | let window = window.get_single().unwrap(); 20 | 21 | let cursor_pos = if let Some(pos) = window.cursor_position() { 22 | Vec2::new(pos.x, window.height() - pos.y) 23 | } else { 24 | return; 25 | }; 26 | if !occupied_screen_space.check(window.width(), window.height(), cursor_pos) { 27 | return; 28 | } 29 | 30 | let cost_list = crate::action::cursor_mode_lack_and_cost(&planet, ¶ms, &cursor_mode); 31 | if cost_list.iter().all(|(lack, _)| !lack) { 32 | return; 33 | } 34 | 35 | egui::show_tooltip_at_pointer( 36 | egui_ctxs.ctx_mut(), 37 | egui::LayerId::new( 38 | egui::Order::Tooltip, 39 | egui::Id::new("hover_tile_tooltip_layer"), 40 | ), 41 | egui::Id::new("hover_tile_tooltip"), 42 | |ui| { 43 | ui.horizontal_centered(|ui| { 44 | ui.label(egui::RichText::new(t!("not-enough")).color(egui::Color32::RED)); 45 | for (lack, cost) in cost_list { 46 | if lack { 47 | let image = match cost { 48 | Cost::Power(_, _) => "ui/icon-power", 49 | Cost::Material(_) => "ui/icon-material", 50 | Cost::GenePoint(_) => "ui/icon-gene", 51 | }; 52 | ui.image(textures.get(image)); 53 | } 54 | } 55 | }); 56 | }, 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src/ui/misc.rs: -------------------------------------------------------------------------------- 1 | use bevy_egui::egui::{self, epaint, load::SizedTexture}; 2 | 3 | use crate::screen::OccupiedScreenSpace; 4 | 5 | use super::UiTextures; 6 | 7 | pub fn label_with_icon( 8 | ui: &mut egui::Ui, 9 | textures: &UiTextures, 10 | icon: &str, 11 | s: impl Into, 12 | ) { 13 | let icon = textures.get(icon); 14 | ui.add(LabelWithIcon::new(icon, s)); 15 | } 16 | 17 | pub struct LabelWithIcon { 18 | icon: SizedTexture, 19 | text: egui::WidgetText, 20 | } 21 | 22 | impl LabelWithIcon { 23 | pub fn new(icon: impl Into, text: impl Into) -> Self { 24 | Self { 25 | icon: icon.into(), 26 | text: text.into(), 27 | } 28 | } 29 | } 30 | 31 | impl egui::Widget for LabelWithIcon { 32 | fn ui(self, ui: &mut egui::Ui) -> egui::Response { 33 | let layout_job = 34 | self.text 35 | .into_layout_job(ui.style(), egui::FontSelection::Default, egui::Align::Min); 36 | let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); 37 | 38 | let icon_size = self.icon.size; 39 | let galley_size = galley.rect.size(); 40 | let desired_size = 41 | egui::Vec2::new(icon_size.x + galley_size.x, icon_size.y.max(galley_size.y)); 42 | 43 | let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click()); 44 | 45 | response.widget_info(|| { 46 | egui::WidgetInfo::labeled(egui::WidgetType::Label, ui.is_enabled(), galley.text()) 47 | }); 48 | 49 | let (icon_pos_y, galley_pos_y) = if icon_size.y > galley_size.y { 50 | (0.0, (icon_size.y - galley_size.y) / 2.0) 51 | } else { 52 | ((galley_size.y - icon_size.y) / 2.0, 0.0) 53 | }; 54 | 55 | let icon_rect = egui::Rect::from_min_size(egui::Pos2::new(0.0, icon_pos_y), icon_size) 56 | .translate(rect.left_top().to_vec2()); 57 | let galley_pos = rect.left_top() + egui::Vec2::new(icon_size.x, galley_pos_y); 58 | 59 | if ui.is_rect_visible(response.rect) { 60 | let painter = ui.painter(); 61 | 62 | painter.add( 63 | epaint::RectShape::filled(icon_rect, 0, egui::Color32::WHITE).with_texture( 64 | self.icon.id, 65 | egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)), 66 | ), 67 | ); 68 | painter.add(epaint::TextShape::new( 69 | galley_pos, 70 | galley, 71 | ui.style().visuals.text_color(), 72 | )); 73 | } 74 | response 75 | } 76 | } 77 | 78 | pub fn small_window_frame(ctx: &egui::Context) -> egui::Frame { 79 | let visuals = &ctx.style().visuals; 80 | egui::Frame::default() 81 | .fill(visuals.window_fill.gamma_multiply(0.95)) 82 | .stroke(visuals.window_stroke) 83 | .inner_margin(egui::Margin::same(4)) 84 | } 85 | 86 | pub fn constrain_to_rect( 87 | ctx: &egui::Context, 88 | occupied_screen_space: &OccupiedScreenSpace, 89 | ) -> egui::Rect { 90 | let rect = ctx.screen_rect(); 91 | egui::Rect::from_two_pos( 92 | rect.left_top() + egui::vec2(0.0, occupied_screen_space.occupied_top), 93 | rect.right_bottom(), 94 | ) 95 | } 96 | -------------------------------------------------------------------------------- /src/ui/preferences.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_egui::{EguiContexts, egui}; 3 | use strum::IntoEnumIterator; 4 | 5 | use super::{OccupiedScreenSpace, WindowsOpenState}; 6 | use crate::{ 7 | audio::SoundEffectPlayer, 8 | conf::{Conf, ConfChange, HighLow3}, 9 | }; 10 | 11 | pub fn preferences_window( 12 | mut egui_ctxs: EguiContexts, 13 | mut wos: ResMut, 14 | mut occupied_screen_space: ResMut, 15 | mut conf: ResMut, 16 | mut ew_conf_change: EventWriter, 17 | se_player: SoundEffectPlayer, 18 | ) { 19 | if !wos.preferences { 20 | return; 21 | } 22 | let conf_before_change = conf.clone(); 23 | 24 | let rect = egui::Window::new(t!("preferences")) 25 | .open(&mut wos.preferences) 26 | .show(egui_ctxs.ctx_mut(), |ui| { 27 | if ui 28 | .checkbox( 29 | &mut conf.autosave_enabled, 30 | t!("preference", "autosave-enabled"), 31 | ) 32 | .clicked() 33 | { 34 | se_player.play("select-item"); 35 | } 36 | ui.horizontal(|ui| { 37 | ui.label(t!("preference", "screen-refresh-rate")); 38 | let response = egui::ComboBox::from_id_salt("screen-refresh-rate") 39 | .selected_text(t!(conf.screen_refresh_rate)) 40 | .show_ui(ui, |ui| { 41 | for item in HighLow3::iter() { 42 | if ui 43 | .selectable_value(&mut conf.screen_refresh_rate, item, t!(item)) 44 | .clicked() 45 | { 46 | se_player.play("select-item"); 47 | } 48 | } 49 | }) 50 | .response; 51 | if response.clicked() { 52 | se_player.play("select-item"); 53 | } 54 | }); 55 | if ui 56 | .checkbox(&mut conf.show_fps, t!("preference", "show-fps")) 57 | .clicked() 58 | { 59 | se_player.play("select-item"); 60 | } 61 | 62 | egui::Grid::new("volume_preferences").show(ui, |ui| { 63 | ui.label(t!("preference", "bgm-volume")); 64 | if ui 65 | .add(egui::Slider::new(&mut conf.bgm_volume, 0..=100).suffix("%")) 66 | .changed() 67 | { 68 | se_player.play_if_stopped("slider"); 69 | } 70 | ui.end_row(); 71 | ui.label(t!("preference", "se-volume")); 72 | if ui 73 | .add(egui::Slider::new(&mut conf.sound_effect_volume, 0..=100).suffix("%")) 74 | .changed() 75 | { 76 | se_player.play_if_stopped("slider"); 77 | } 78 | }); 79 | }) 80 | .unwrap() 81 | .response 82 | .rect; 83 | occupied_screen_space.push_egui_window_rect(rect); 84 | 85 | if *conf != conf_before_change { 86 | ew_conf_change.send_default(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/ui/reports.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_egui::{EguiContexts, egui}; 3 | 4 | use crate::{ 5 | audio::SoundEffectPlayer, 6 | manage_planet::SwitchPlanet, 7 | planet::{Planet, TILE_SIZE}, 8 | screen::{Centering, OccupiedScreenSpace}, 9 | }; 10 | 11 | use super::{UiTextures, WindowsOpenState}; 12 | 13 | const DEFAULT_WINDOW_WIDTH: f32 = 220.0; 14 | const DEFAULT_WINDOW_HEIGHT: f32 = 105.0; 15 | 16 | pub fn reports_window( 17 | mut egui_ctxs: EguiContexts, 18 | mut occupied_screen_space: ResMut, 19 | mut wos: ResMut, 20 | mut ew_centering: EventWriter, 21 | textures: Res, 22 | planet: Res, 23 | se_player: SoundEffectPlayer, 24 | mut er_switch_planet: EventReader, 25 | mut n_items_prev: Local>, 26 | ) { 27 | if er_switch_planet.read().last().is_some() { 28 | *n_items_prev = None; 29 | } 30 | 31 | let n_items = planet.reports.n_reports(); 32 | if let Some(n_items_prev) = &mut *n_items_prev { 33 | if *n_items_prev < n_items { 34 | se_player.play("report"); 35 | *n_items_prev = n_items; 36 | } 37 | } else { 38 | *n_items_prev = Some(n_items); 39 | } 40 | 41 | if !wos.reports { 42 | let rect = egui::Window::new("reports-expander") 43 | .anchor( 44 | egui::Align2::LEFT_BOTTOM, 45 | [occupied_screen_space.stat_width, 0.0], 46 | ) 47 | .frame(super::misc::small_window_frame(egui_ctxs.ctx_mut())) 48 | .resizable(false) 49 | .title_bar(false) 50 | .show(egui_ctxs.ctx_mut(), |ui| { 51 | if ui 52 | .add(egui::ImageButton::new(textures.get("ui/icon-reports"))) 53 | .on_hover_text(t!("reports")) 54 | .clicked() 55 | { 56 | wos.reports = true; 57 | } 58 | }) 59 | .unwrap() 60 | .response 61 | .rect; 62 | occupied_screen_space.push_egui_window_rect(rect); 63 | return; 64 | } 65 | 66 | let rect = egui::Window::new("reports-window") 67 | .anchor( 68 | egui::Align2::LEFT_BOTTOM, 69 | [occupied_screen_space.stat_width, 0.0], 70 | ) 71 | .title_bar(false) 72 | .default_width(DEFAULT_WINDOW_WIDTH) 73 | .default_height(DEFAULT_WINDOW_HEIGHT) 74 | .show(egui_ctxs.ctx_mut(), |ui| { 75 | ui.horizontal(|ui| { 76 | if ui.button("▼").clicked() { 77 | wos.reports = false; 78 | } 79 | ui.heading(t!("reports")); 80 | }); 81 | ui.separator(); 82 | 83 | egui::ScrollArea::vertical() 84 | .auto_shrink(egui::Vec2b::new(false, false)) 85 | .show(ui, |ui| { 86 | report_list(ui, &planet, &mut ew_centering); 87 | }); 88 | }) 89 | .unwrap() 90 | .response 91 | .rect; 92 | occupied_screen_space.push_egui_window_rect(rect); 93 | } 94 | 95 | pub fn report_list(ui: &mut egui::Ui, planet: &Planet, ew_centering: &mut EventWriter) { 96 | for report in planet.reports.iter() { 97 | let (style, text) = report.text(); 98 | ui.horizontal(|ui| { 99 | ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap); 100 | ui.label(style.icon()); 101 | if let Some(pos) = report.content.pos() { 102 | if ui.link(text).clicked() { 103 | ew_centering.send(Centering::new(Vec2::new( 104 | pos.0 as f32 * TILE_SIZE, 105 | pos.1 as f32 * TILE_SIZE, 106 | ))); 107 | } 108 | } else { 109 | ui.label(text); 110 | } 111 | }); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/ui/tools_expander.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_egui::{EguiContexts, egui}; 3 | 4 | use crate::screen::OccupiedScreenSpace; 5 | 6 | use super::{UiTextures, WindowsOpenState}; 7 | 8 | pub fn tools_expander( 9 | mut egui_ctxs: EguiContexts, 10 | mut occupied_screen_space: ResMut, 11 | mut wos: ResMut, 12 | textures: Res, 13 | ) { 14 | let ctx = egui_ctxs.ctx_mut(); 15 | let rect = egui::Window::new("tools-expander") 16 | .anchor( 17 | egui::Align2::LEFT_TOP, 18 | [0.0, occupied_screen_space.toolbar_height], 19 | ) 20 | .frame(super::misc::small_window_frame(ctx)) 21 | .resizable(false) 22 | .title_bar(false) 23 | .show(ctx, |ui| { 24 | if ui 25 | .add( 26 | egui::ImageButton::new(textures.get("ui/icon-space-buildings")) 27 | .selected(wos.space_building), 28 | ) 29 | .on_hover_text(t!("space-buildings")) 30 | .clicked() 31 | { 32 | wos.space_building = !wos.space_building; 33 | wos.animals = false; 34 | wos.control = false; 35 | } 36 | if ui 37 | .add(egui::ImageButton::new(textures.get("ui/icon-animal")).selected(wos.animals)) 38 | .on_hover_text(t!("animals")) 39 | .clicked() 40 | { 41 | wos.animals = !wos.animals; 42 | wos.space_building = false; 43 | wos.control = false; 44 | } 45 | if ui 46 | .add(egui::ImageButton::new(textures.get("ui/icon-control")).selected(wos.control)) 47 | .on_hover_text(t!("control")) 48 | .clicked() 49 | { 50 | wos.control = !wos.control; 51 | wos.space_building = false; 52 | wos.animals = false; 53 | } 54 | }) 55 | .unwrap() 56 | .response 57 | .rect; 58 | occupied_screen_space.tools_expander_width = rect.width(); 59 | occupied_screen_space.push_egui_window_rect(rect); 60 | } 61 | --------------------------------------------------------------------------------