Compare commits

...
32 Commits
Author SHA1 Message Date
Richard ZinkandClaude Opus 5.5 d8aa0e5fc1 Bump version to v202611-00076
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 13:33:43 +01:00
Richard ZinkandClaude Opus 5.5 98b78eb518 Desktop release builds: Linux AppImage, Windows exe and macOS app via tools/build_desktop.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 12:22:11 +01:00
Richard ZinkandClaude Opus 5.5 2475a8ca3c Desktop build plays music and sound through the system sound output; desktop build documented
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 23:49:45 +01:00
Richard ZinkandClaude Opus 5.5 db521ed2ee Desktop build: settings and progress kept as files, savegames in a saves folder, system file dialog for loading
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 23:46:11 +01:00
Richard ZinkandClaude Opus 5.5 1df97b4af2 Bump version to v202611-00075
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:08:28 +01:00
Richard ZinkandClaude Opus 5.5 bd3f42b8b8 Hero waves break in where the warning shows: they round the realm to the marked entry; the entry tile glows red
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:08:28 +01:00
Richard ZinkandClaude Opus 5.5 dab31579df Bump version to v202611-00074
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:34:04 +01:00
Richard ZinkandClaude Opus 5.5 e85e602491 Balance: easy AI stays smaller; balance runs documented
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:34:03 +01:00
Richard ZinkandClaude Opus 5.5 28eac52953 Memorial stones only for level 10 creatures, at most 30 per map
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:34:03 +01:00
Richard ZinkandClaude Opus 5.5 66eaedf016 Automated balance runs; AI masters keep growing, mine more, defend with the rally flag and spells; Builder talent stronger
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 15:07:02 +01:00
Richard ZinkandClaude Opus 5.5 24ba7fb5e3 Bump version to v202611-00073
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:48:07 +01:00
Richard ZinkandClaude Opus 5.5 beb53a2e0c Serve the wasm and scripts gzip-compressed (packed at build time)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:48:07 +01:00
Richard ZinkandClaude Opus 5.5 c52fc4c4ae Hall of the fallen: kills and causes of death, in the game and over all games in the profile
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:48:06 +01:00
Richard ZinkandClaude Opus 5.5 065fe80218 Bump version to v202611-00072
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:37:28 +01:00
Richard ZinkandClaude Opus 5.5 bc1f89b4a7 Sounds for weather, rogues and groves: thunder, rain and wind ambience, coin theft, snares, sermons, spores
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:37:28 +01:00
Richard ZinkandClaude Opus 5.5 340014c582 Bump version to v202611-00071
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:29:44 +01:00
Richard ZinkandClaude Opus 5.5 eb219f6b01 Script blocks for weather and talent experience, groves as ground; the co-op campaign brings rogues and a storm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:29:44 +01:00
Richard ZinkandClaude Opus 5.5 4e3158cc19 Bump version to v202611-00070
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:21:07 +01:00
Richard ZinkandClaude Opus 5.5 70f112543f Interface size setting: auto, large, medium or small on the world's pixel grid
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:21:07 +01:00
Richard ZinkandClaude Opus 5.5 95655a0886 Bump version to v202611-00069
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:12:42 +01:00
Richard ZinkandClaude Opus 5.5 5a2ac32071 Command markers only with the debug setting; removing a marker clears them at once
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:12:41 +01:00
Richard ZinkandClaude Opus 5.5 be4459e19e Bump version to v202611-00068
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:40:22 +01:00
Richard ZinkandClaude Opus 5.5 ad5ceb8c13 Named creatures: names per kind and language, epithets at higher levels, memorial stones for the fallen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:40:22 +01:00
Richard ZinkandClaude Opus 5.5 189da6f408 Bump version to v202611-00067
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:19:37 +01:00
Richard ZinkandClaude Opus 5.5 0cbcd1c8d1 Markers: a second click or tap removes them; right click with the flag tool takes the flag
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:19:37 +01:00
Richard ZinkandClaude Opus 5.5 c3f7939627 Bump version to v202611-00066
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:11:20 +01:00
Richard ZinkandClaude Opus 5.5 3e17bdf52d Weather above ground: rain puts out fires, fog shortens sight, storms strike with lightning
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:11:19 +01:00
Richard ZinkandClaude Opus 5.5 f98f05f0d2 Bump version to v202611-00065
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:48:06 +01:00
Richard ZinkandClaude Opus 5.5 25334e7b17 Portrait and square screens: world pixels, interface and zoom count on the shorter side
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:48:06 +01:00
Richard ZinkandClaude Opus 5.5 16857f1b9d Bump version to v202611-00064
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:35:25 +01:00
Richard ZinkandClaude Opus 5.5 9804e24015 Smaller touch interface: the same pixel density as on the desktop
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:35:11 +01:00
Richard ZinkandClaude Opus 5.5 bd956a4f57 Master talents: experience, points and four branches (Builder, Warlord, Warlock, Merchant) with a toolbar window
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:35:11 +01:00
62 changed files with 4771 additions and 116 deletions

No files matched your search

+1
View File
@@ -4,3 +4,4 @@ Cargo.lock.bak
www/dungeoncraft.wasm www/dungeoncraft.wasm
www/mq_js_bundle.js www/mq_js_bundle.js
play/ play/
dist/
Generated
+829 -4
View File
@@ -14,6 +14,28 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "alsa"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
dependencies = [
"alsa-sys",
"bitflags 2.13.2",
"cfg-if",
"libc",
]
[[package]]
name = "alsa-sys"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04"
dependencies = [
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.1" version = "1.5.1"
@@ -26,6 +48,27 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.25.2" version = "1.25.2"
@@ -38,6 +81,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.5" version = "1.0.5"
@@ -50,6 +99,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "combine"
version = "4.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92"
dependencies = [
"bytes",
"memchr",
]
[[package]] [[package]]
name = "core_maths" name = "core_maths"
version = "0.1.1" version = "0.1.1"
@@ -59,6 +118,50 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "coreaudio-rs"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58"
dependencies = [
"bitflags 2.13.2",
"libc",
"objc2-audio-toolbox",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
]
[[package]]
name = "cpal"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f02e8d0327b42d3e2e4ab2119af397344eb9fc54a34bf0ddeaa1277af8681f1"
dependencies = [
"alsa",
"block2",
"coreaudio-rs",
"dasp_sample",
"jni",
"js-sys",
"libc",
"mach2",
"ndk",
"ndk-context",
"num-derive",
"num-traits",
"objc2",
"objc2-audio-toolbox",
"objc2-avf-audio",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation",
"web-sys",
"windows",
"windows-core",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.5.4" version = "1.5.4"
@@ -68,11 +171,31 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "dasp_sample"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags 2.13.2",
"block2",
"libc",
"objc2",
]
[[package]] [[package]]
name = "dungeoncraft" name = "dungeoncraft"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cpal",
"macroquad", "macroquad",
"rfd",
"serde", "serde",
"serde_json", "serde_json",
] ]
@@ -115,10 +238,34 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e796cb6894e6418daacea84b28dd4f98d3b32c74d5bb452ba1ecfbf5ad8e84ca" checksum = "e796cb6894e6418daacea84b28dd4f98d3b32c74d5bb452ba1ecfbf5ad8e84ca"
dependencies = [ dependencies = [
"hashbrown", "hashbrown 0.15.5",
"ttf-parser", "ttf-parser",
] ]
[[package]]
name = "futures-core"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-task"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]] [[package]]
name = "glam" name = "glam"
version = "0.27.0" version = "0.27.0"
@@ -136,6 +283,12 @@ dependencies = [
"foldhash", "foldhash",
] ]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]] [[package]]
name = "image" name = "image"
version = "0.24.9" version = "0.24.9"
@@ -149,12 +302,91 @@ dependencies = [
"png", "png",
] ]
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.18" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jni"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
"cfg-if",
"combine",
"jni-macros",
"jni-sys 0.4.1",
"log",
"simd_cesu8",
"thiserror 2.0.21",
"walkdir",
"windows-link",
]
[[package]]
name = "jni-macros"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"simd_cesu8",
"syn 2.0.119",
]
[[package]]
name = "jni-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
dependencies = [
"jni-sys 0.4.1",
]
[[package]]
name = "jni-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
dependencies = [
"jni-sys-macros",
]
[[package]]
name = "jni-sys-macros"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
dependencies = [
"quote",
"syn 2.0.119",
]
[[package]]
name = "js-sys"
version = "0.3.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a"
dependencies = [
"cfg-if",
"futures-util",
"wasm-bindgen",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.191" version = "0.2.191"
@@ -167,6 +399,18 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "log"
version = "0.4.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c"
[[package]]
name = "mach2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
[[package]] [[package]]
name = "macroquad" name = "macroquad"
version = "0.4.16" version = "0.4.16"
@@ -209,7 +453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbaea978d741f3b60ced666b06a2a1e4a2bc81cb8b9dd29f4a40965e1f59e65" checksum = "dcbaea978d741f3b60ced666b06a2a1e4a2bc81cb8b9dd29f4a40965e1f59e65"
dependencies = [ dependencies = [
"libc", "libc",
"ndk-sys", "ndk-sys 0.2.2",
"objc-rs", "objc-rs",
"winapi", "winapi",
] ]
@@ -234,12 +478,52 @@ dependencies = [
"simd-adler32", "simd-adler32",
] ]
[[package]]
name = "ndk"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
dependencies = [
"bitflags 2.13.2",
"jni-sys 0.3.1",
"log",
"ndk-sys 0.6.0+11769913",
"num_enum",
"thiserror 1.0.69",
]
[[package]]
name = "ndk-context"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
[[package]] [[package]]
name = "ndk-sys" name = "ndk-sys"
version = "0.2.2" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121"
[[package]]
name = "ndk-sys"
version = "0.6.0+11769913"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
dependencies = [
"jni-sys 0.3.1",
]
[[package]]
name = "num-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -249,6 +533,28 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "num_enum"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
dependencies = [
"num_enum_derive",
"rustversion",
]
[[package]]
name = "num_enum_derive"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "objc-rs" name = "objc-rs"
version = "0.2.8" version = "0.2.8"
@@ -258,19 +564,154 @@ dependencies = [
"malloc_buf", "malloc_buf",
] ]
[[package]]
name = "objc2"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-app-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags 2.13.2",
"block2",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-audio-toolbox"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
dependencies = [
"bitflags 2.13.2",
"libc",
"objc2",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-avf-audio"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
dependencies = [
"bitflags 2.13.2",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-audio"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
dependencies = [
"dispatch2",
"objc2",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-core-audio-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
dependencies = [
"bitflags 2.13.2",
"objc2",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.13.2",
"block2",
"dispatch2",
"libc",
"objc2",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.13.2",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]] [[package]]
name = "png" name = "png"
version = "0.17.16" version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"crc32fast", "crc32fast",
"fdeflate", "fdeflate",
"flate2", "flate2",
"miniz_oxide 0.8.9", "miniz_oxide 0.8.9",
] ]
[[package]]
name = "pollster"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
[[package]]
name = "proc-macro-crate"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"toml_edit",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.107" version = "1.0.107"
@@ -295,6 +736,66 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "raw-window-handle"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]]
name = "rfd"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220"
dependencies = [
"block2",
"dispatch2",
"js-sys",
"libc",
"log",
"objc2",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation",
"percent-encoding",
"pollster",
"raw-window-handle",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows-sys",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.229" version = "1.0.229"
@@ -322,7 +823,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 3.0.9",
] ]
[[package]] [[package]]
@@ -344,6 +845,39 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "simd_cesu8"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
dependencies = [
"rustc_version",
"simdutf8",
]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.9" version = "3.0.9"
@@ -355,6 +889,85 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
dependencies = [
"thiserror-impl 2.0.21",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "thiserror-impl"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
]
[[package]]
name = "tokio"
version = "1.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
dependencies = [
"pin-project-lite",
]
[[package]]
name = "toml_datetime"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173f25c883cd2176a2d8212155638c0a60d484a9d84546d270866f40f56b6e49"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.25.19+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd43688c4858d7264ae1cf9a9f874a67c3feb74a883b1cd7bebd6a4ea8307218"
dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "toml_parser"
version = "1.1.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
dependencies = [
"winnow",
]
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.25.1" version = "0.25.1"
@@ -370,6 +983,81 @@ version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c"
dependencies = [
"cfg-if",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e14e0327cb5368ad60ccb22d04a9827a693c95a926d8d125e9fa39e0baec501"
dependencies = [
"js-sys",
"tokio",
"wasm-bindgen",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 3.0.9",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d179596ff5ec0f6de4db64d54011bde6289cc49603d3a6e4ce3f329207ab88"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@@ -386,12 +1074,149 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link",
]
[[package]]
name = "winnow"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3b93199df0ab874cdf0c6a94f0a0dc3423fe41f1cafb62b8bd7b1deb277aef"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "zlib-rs" name = "zlib-rs"
version = "0.6.8" version = "0.6.8"
+5
View File
@@ -17,3 +17,8 @@ codegen-units = 1
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1
# desktop builds: system file dialog (xdg portal on Linux) and sound output
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rfd = { version = "0.17", default-features = false, features = ["xdg-portal"] }
cpal = "0.18"
+2
View File
@@ -31,4 +31,6 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY www/ /usr/share/nginx/html/ COPY www/ /usr/share/nginx/html/
COPY --from=build /out/ /usr/share/nginx/html/ COPY --from=build /out/ /usr/share/nginx/html/
COPY --from=standalone /out/standalone/ /usr/share/nginx/html/standalone/ COPY --from=standalone /out/standalone/ /usr/share/nginx/html/standalone/
# packed once for gzip_static (nginx.conf)
RUN cd /usr/share/nginx/html && gzip -k -9 dungeoncraft.wasm mq_js_bundle.js standalone/DungeonCraft.html
EXPOSE 80 EXPOSE 80
+27
View File
@@ -37,6 +37,33 @@ cp ~/.cargo/registry/src/*/macroquad-*/js/mq_js_bundle.js www/
python3 -m http.server 8080 -d www # open http://localhost:8080 python3 -m http.server 8080 -d www # open http://localhost:8080
``` ```
### Desktop build
The game also runs as a native program (Linux, Windows, macOS), without server and multiplayer:
```sh
cargo build --release
./target/release/dungeoncraft
```
On Linux the build needs the ALSA headers (`alsa-lib` / `libasound2-dev`); the sound goes through ALSA (PipeWire and
PulseAudio take it). The settings, the autosave, the campaign progress and the profile are kept as files in the
data folder: `~/.local/share/dungeoncraft/` (Linux, or `$XDG_DATA_HOME/dungeoncraft`), `%APPDATA%\DungeonCraft`
(Windows), `~/Library/Application Support/DungeonCraft` (macOS); `DUNGEONCRAFT_DATA` chooses another one.
*Save Game* and saved campaigns land in its `saves/` folder; *Load Game* opens the system file dialog there (on
Linux through the desktop portal), and dropping a file onto the window works as well.
Release packages are built by `python3 tools/build_desktop.py [linux] [windows] [macos]` into `dist/`:
- **Linux**: `DungeonCraft-<version>-x86_64.AppImage`, built against glibc 2.28 (distributions from about 2019 on).
- **Windows**: `DungeonCraft-<version>-windows-x64.exe`, a single file for Windows 10 and newer, cross-compiled on Linux.
- **macOS**: `DungeonCraft-<version>-macos.zip` with a universal `DungeonCraft.app` (Apple silicon and Intel); only on a
Mac. It is signed ad hoc, not notarised: on first start open it with right click -> *Open*.
Linux and Windows need [zig](https://ziglang.org/) (`mise install zig@0.15` works) and
`cargo install --locked cargo-zigbuild`, plus `rustup target add x86_64-pc-windows-gnu`; the AppImage tool is downloaded
once into `~/.cache/dungeoncraft/`.
### Testing the game logic ### Testing the game logic
The tests run natively: a headless simulation of whole games, a savegame round trip, rendering of all music modules and the audio engine: The tests run natively: a headless simulation of whole games, a savegame round trip, rendering of all music modules and the audio engine:
+1 -1
View File
@@ -1 +1 @@
v202611-00063 v202611-00076
+51
View File
@@ -0,0 +1,51 @@
# Balance runs
Whole games played headless by the AI brain on every side (`src/balance.rs`), deterministic per seed:
```bash
cargo test --release balance_ -- --ignored --nocapture --test-threads=3
BALANCE_SEEDS=16 cargo test --release balance_difficulty -- --ignored --nocapture # more seeds
BALANCE_PAIR=0,1 ... # one difficulty pairing only (0 easy, 1 medium, 2 hard)
BALANCE_TRACE=1 ... # wave runs: a status line every two minutes
```
- **balance_difficulty** – duels of two AI masters (112x112, no hero waves, 45 min); the sides swap
every other seed. Undecided games count as draws.
- **balance_talents** – four AI masters (medium), each held to one talent branch, the branches moving
round the four starts; placement by survival and strength.
- **balance_waves** – one AI master alone against the hero waves (96x96, 40 min).
## Findings and changes (2026-11-06)
First runs showed:
- AI masters stopped growing after their build order (99 room tiles, 24 creatures from minute 10).
- They mined one vein at a time and only below 1500 gold.
- They fought hero waves piecemeal and never cast spells.
- "Hard" attacked even when weaker (boldness 0.9) and was no stronger than "easy".
Changes:
- AI masters keep building lairs and gardens up to 24 / 50 / 80 beds (easy / medium / hard), and a
bigger treasury when the old one is nearly full.
- They mine several veins (one per three imps) whenever gold is below 1.5 paydays + 1000.
- Defence: with intruders in the dungeon the rally flag goes on them, the master casts lightning on
them and heals his hurt creatures; an attack is called off to defend.
- Boldness (own army / enemy army needed to attack): easy 1.0, medium 1.2, hard 1.35.
- Builder talent: imps dig +15% and rooms cost -10% per level (was +10% / -6%).
Results after the changes:
| | before | after |
|---|---|---|
| one master vs waves: fallen of 8 (easy/medium/hard) | 2 / 3 / 4 | 2 / 2 / 2 |
| duels, 16 seeds: easy vs medium | 6 : 6 | 3 : 6 |
| medium vs hard | 5 : 6 (8 seeds 3 : 2) | 5 : 6 |
| easy vs hard | 3 : 7 | 3 : 7 |
| talents, average place (builder / warlord / warlock / merchant) | 3.3 / 1.9 / 3.0 / 1.9 | 3.1 / 2.1 / 2.5 / 2.3 |
Notes:
- The noise is about ±2 wins at 16 seeds; medium vs medium ended 5 : 7.
- The AI uses only lightning and heal, so Warlock is still worth more to a human player than the
tournament shows.
- Builder pays off where space, not gold, limits building; AI dungeons are bounded by their radius.
- One map seed (1333) never brings heroes to the master. It shows as "wave 11, heroes 0" in the
wave table and is a map outlier.
+4 -2
View File
@@ -58,10 +58,12 @@ creatures and heroes.
| Units | Spawn | count, kind, level, side, where (the core, the trigger unit, a zone), optional name | | Units | Spawn | count, kind, level, side, where (the core, the trigger unit, a zone), optional name |
| | Hero wave | count (sent at once) | | | Hero wave | count (sent at once) |
| | Remove | side, zone (removes those units) | | | Remove | side, zone (removes those units) |
| World | Change ground | zone → floor, earth, rock, water, lava, gold (the core, portals, towers and the gate stay) | | World | Change ground | zone → floor, earth, rock, water, lava, gold, mushroom grove (the core, portals, towers and the gate stay) |
| | Reveal | zone (lifts the fog of war) | | | Reveal | zone (lifts the fog of war) |
| | Show | zone (moves the camera there) | | | Show | zone (moves the camera there) |
| | Gold + / Mana + | amount (negative takes) | | | Gold + / Mana + | amount (negative takes) |
| | Weather above ground | fair, rain, fog or storm, for seconds (0: until a block changes it); it fades in softly. Only maps with land above ground have weather |
| | Talent experience + | amount (experience for the player's master talents) |
| Logic | If / else | condition; blocks inside | | Logic | If / else | condition; blocks inside |
| | Repeat | times (0–100); blocks inside | | | Repeat | times (0–100); blocks inside |
| | Repeat until | condition, checked **before** each run (already true: the inside never runs); blocks inside | | | Repeat until | condition, checked **before** each run (already true: the inside never runs); blocks inside |
@@ -80,7 +82,7 @@ Every number field holds a **value**; the switch in front of it changes the valu
side/kind), *Count* in a zone, *Random* (whole number between a and b, deterministic), side/kind), *Count* in a zone, *Random* (whole number between a and b, deterministic),
*Calculation* (`+ - * /`, division by 0 gives 0), *Calculation* (`+ - * /`, division by 0 gives 0),
- conditions (for *If*): *Comparison* (`< <= = != >= >`), *and*, *or*, *not*, *Yes/no*, - conditions (for *If*): *Comparison* (`< <= = != >= >`), *and*, *or*, *not*, *Yes/no*,
*Trigger unit is a* (kind). *Trigger unit is a* (kind), *Weather above ground is* (fair, rain, fog, storm).
Variables are numbers the mission remembers (saved with the game); a variable is known once some Variables are numbers the mission remembers (saved with the game); a variable is known once some
script of the mission sets it. script of the mission sets it.
+17 -4
View File
@@ -129,7 +129,20 @@ grid), guard and attack markers (shields and swords), savegames with the game st
to the gate; slain, his loot is yours; from wave 3), monk (spells do not touch him, 5), ranger (shoots, lays to the gate; slain, his loot is yours; from wave 3), monk (spells do not touch him, 5), ranger (shoots, lays
snares that hurt and stun a creature, 7), inquisitor (fragile, every 20 s turns a nearby kobold or spider to snares that hurt and stun a creature, 7), inquisitor (fragile, every 20 s turns a nearby kobold or spider to
the heroes, 9). In the campaign from missions 5, 9, 12 and 16 on. the heroes, 9). In the campaign from missions 5, 9, 12 and 16 on.
4. **Master talents** - Builder, Warlord, Warlock, Merchant (5 levels each), a toolbar button; per game, 4. **Master talents** (done, v202611-00064, talents.rs) - experience from slain heroes, digging, rooms, walls,
carried over in the campaign. traps, towers, spells and gold; points get dearer (300, 450, 600 ...). Builder (imps dig +10%, rooms -6% per
5. **Weather above ground** - rain (puts out fire), fog (less sight), storms (lightning in trees and units). level), Warlord (creatures +5% damage, -5% damage taken), Warlock (mana +15%, spells -6%), Merchant (wages
6. **Named creatures** - names per kind and language, epithets at higher levels, memorial stones. -6%, treasure +6%); 5 levels each. The "Master" button (number of free points, pulsing) opens the window.
AI masters spend their points themselves; per game, carried over in the campaign and co-op runs.
5. **Weather above ground** (done, v202611-00066, weather.rs) - about 60% fair, 20% rain, 12% fog, 8% storm,
minutes each with soft 20 s changes, only on maps with land above ground. Rain (and storms) put out burning
bridges and keep fire from catching; fog halves the sight of units outside; storms strike units (40 damage)
or trees with lightning every 6-20 s. Dimmed daylight, rain streaks, fog veils, flashes; a HUD label.
6. **Named creatures** (done, v202611-00068, names.rs) - every creature gets a name from its kind's list in the
language files (20 per kind, per language), a humorous epithet from level 5 and a grand one from level 8;
names in the hover info, the creature overview and the messages. A creature of level 10 that falls leaves a
memorial stone on its master's ground (hover: who lies there; at most 30 per map).
7. **Hall of the fallen** (done, v202611-00073) - every named creature of level 3+ that falls is remembered
with its level, heroes slain and how it fell (slain by a kind, lava, water, snare, lightning): in the game
(creature overview "Fallen", or a click with the hand on a memorial stone) and over all games on the
achievements page (kept in the profile, at most 200, merged with the server copy).
+75
View File
@@ -264,6 +264,8 @@ val.medium = Střední
val.high = Vysoká val.high = Vysoká
val.normal = Normální val.normal = Normální
val.large = Velké val.large = Velké
val.small = Malé
val.auto_plain = Automaticky
# ---- key binding names # ---- key binding names
key.cam_forward = Kamera vpřed key.cam_forward = Kamera vpřed
@@ -1374,6 +1376,8 @@ blk.reveal = Odhalit
blk.camera = Ukázat blk.camera = Ukázat
blk.gold = Zlato + blk.gold = Zlato +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Počasí na povrchu
blk.talentxp = Zkušenosti talentů +
blk.if = Když blk.if = Když
blk.repeat = Opakovat blk.repeat = Opakovat
blk.trigger = Přepnout spouštěč blk.trigger = Přepnout spouštěč
@@ -1393,6 +1397,8 @@ blk.help.reveal = Odhalí mlhu války nad zónou.
blk.help.camera = Přesune kameru na zónu. blk.help.camera = Přesune kameru na zónu.
blk.help.gold = Dá (nebo vezme) zlato. blk.help.gold = Dá (nebo vezme) zlato.
blk.help.mana = Dá (nebo vezme) manu. blk.help.mana = Dá (nebo vezme) manu.
blk.help.weather = Nastaví počasí na povrchu (jasno, déšť, mlha nebo bouře) na tolik sekund; 0 ho ponechá, dokud ho jiný blok nezmění.
blk.help.talentxp = Dá hráči zkušenosti pro pánovy talenty.
blk.help.if = Spustí vnitřní bloky, jen když platí podmínka, jinak ty pod "jinak". blk.help.if = Spustí vnitřní bloky, jen když platí podmínka, jinak ty pod "jinak".
blk.help.repeat = Spustí vnitřní bloky několikrát. blk.help.repeat = Spustí vnitřní bloky několikrát.
blk.help.trigger = Zapne spouštěč (může se spustit znovu) nebo ho vypne. blk.help.trigger = Zapne spouštěč (může se spustit znovu) nebo ho vypne.
@@ -1415,6 +1421,7 @@ blk.v.or = nebo
blk.v.not = ne blk.v.not = ne
blk.v.bool = Ano/ne blk.v.bool = Ano/ne
blk.v.eventkind = Spouštěcí jednotka je blk.v.eventkind = Spouštěcí jednotka je
blk.v.weatheris = Počasí na povrchu je
blk.w_at = na blk.w_at = na
blk.w_by = o blk.w_by = o
blk.w_in = v blk.w_in = v
@@ -1463,6 +1470,7 @@ blk.until = Opakovat dokud
blk.dountil = Opakovat, pak ověřit: dokud blk.dountil = Opakovat, pak ověřit: dokud
blk.wait = Čekat blk.wait = Čekat
blk.w_seconds = sekund blk.w_seconds = sekund
blk.w_for = na
blk.help.until = Spouští vnitřní bloky znovu a znovu, dokud neplatí podmínka - ověřuje se před každým během, takže pokud už platí, vnitřek se nespustí vůbec. blk.help.until = Spouští vnitřní bloky znovu a znovu, dokud neplatí podmínka - ověřuje se před každým během, takže pokud už platí, vnitřek se nespustí vůbec.
blk.help.dountil = Spustí vnitřní bloky, pak ověří podmínku a opakuje, dokud neplatí - vnitřek proběhne vždy aspoň jednou. blk.help.dountil = Spustí vnitřní bloky, pak ověří podmínku a opakuje, dokud neplatí - vnitřek proběhne vždy aspoň jednou.
blk.help.wait = Pozastaví tento skript na několik sekund, pak pokračuje dalším blokem (i uvnitř smyček). blk.help.wait = Pozastaví tento skript na několik sekund, pak pokračuje dalším blokem (i uvnitř smyček).
@@ -1693,3 +1701,70 @@ tut.slap = Křivoprst: Ha! Slyšel jsi to? Něco přichází od východu. Připr
tut.obj6 = Poraz dva rytíře. tut.obj6 = Poraz dva rytíře.
tut.knights = Křivoprst: Dva rytíři! Pošli na ně tvory útočnou vlajkou (Rozkazy) nebo je zasáhni bleskem. tut.knights = Křivoprst: Dva rytíři! Pošli na ně tvory útočnou vlajkou (Rozkazy) nebo je zasáhni bleskem.
tut.win = Křivoprst: Výborně, pane! To je všechno. No - skoro. Zbytek poví kampaň. Přistavím vodu na čaj. tut.win = Křivoprst: Výborně, pane! To je všechno. No - skoro. Zbytek poví kampaň. Přistavím vodu na čaj.
# master talents
cat.master = Pán
talent.title = Pánovy talenty
talent.points = Volné body talentů: {0}
talent.next = {0}/{1} zkušeností do dalšího bodu
talent.all = Všechny talenty jsou naučené.
talent.builder = Stavitel
talent.warlord = Vojevůdce
talent.warlock = Černokněžník
talent.merchant = Obchodník
talent.builder.desc = Čertíci kopou o {0}% rychleji, místnosti stojí o {1}% méně.
talent.warlord.desc = Tvorové udeří o {0}% silněji a dostávají o {1}% méně poškození.
talent.warlock.desc = Mana proudí o {0}% rychleji, kouzla stojí o {1}% méně.
talent.merchant.desc = Mzdy jsou o {0}% nižší, doručené poklady mají o {1}% vyšší hodnotu.
talent.none = Zatím nenaučeno.
talent.then = Další úroveň: {0}
msg.talent_point = Nový bod talentu! Použij ho v okně pána.
msg.talent_learned = {0}: úroveň {1} naučena.
# weather above ground
weather.fair = Jasno
weather.rain = Déšť
weather.fog = Mlha
weather.storm = Bouře
msg.weather_clear = Na povrchu se vyjasňuje.
msg.weather_rain = Na povrchu začíná pršet - oheň nechytne.
msg.weather_fog = Nad krajinou se valí mlha - venku každý vidí méně.
msg.weather_storm = Na povrchu se blíží bouře! Blesky zasahují jednotky a stromy.
msg.lightning_unit = Zásah bleskem: {0}!
# creature names
names.imp = Drobek, Soplík, Hrabík, Bláťák, Špindíra, Prcek, Rýpal, Čuník, Smradík, Škrabka, Hrouda, Čvachta, Mazal, Flekáč, Hrudka, Lopatka, Čumák, Drobeček, Šmudla, Čmudla
names.kobold = Ňafík, Štípal, Zubáč, Prašivec, Kousal, Rezák, Šmik, Kousek, Čumil, Krysák, Hadrník, Bodlák, Drápek, Rejpal, Kořínek, Chechtal, Strkal, Zoubek, Křup, Škrábek
names.spider = Lezoun, Chlupka, Pavoučík, Lechtal, Síťka, Osminožka, Nitka, Strašidlík, Šmatlík, Hedvábka, Tkadlenka, Skokan, Chlupáček, Tekla, Cívka, Chňapka, Klubíčko, Lepka, Vrtílek, Jehlička
names.mage = Bartoloměj, Ignác, Leopold, Theobald, Vojtěch, Jeroným, Valdemar, Gotfríd, Kornelius, Otakar, Florián, Engelbert, Pankrác, Ruprecht, Konstantin, Eusebius, Servác, Barnabáš, Archibald, Bonifác
names.ogre = Hromotluk, Špalek, Bum, Balvan, Tupoun, Pařez, Bouchal, Mlátič, Dupák, Dřevák, Žbluňk, Buřtík, Valoun, Břicháč, Kyjan, Vůl, Rachot, Kus, Buben, Gorbo
names.jailer = Zachmuřenec, Závora, Okov, Zámek, Žalář, Železovous, Kroužek, Šroub, Přísný, Mřížník, Mrzout, Hák, Oprátka, Klíčník, Temný, Řinčal, Kláda, Strážný, Klec, Hořký
names.priestess = Žhavka, Plamena, Popelice, Serafína, Vatra, Ohnivka, Morana, Brunhilda, Ignie, Jiskra, Šarlata, Žárka, Havranka, Lilit, Troud, Izolda, Planoucí, Nyx, Večernice, Plaménka
names.chainbreaker = Řetězolam, Ulfgar, Železoduch, Brannok, Kragmor, Valdrek, Trn, Gorath, Nespoutaný, Rurik, Torvald, Hagen, Durnok, Střep, Plamen, Vlkoslav, Korrun, Arvak, Osvoboditel, Halvard
names.bat = Pískal, Třepetal, Pípa, Šustík, Ozvěna, Mávalka, Soumrak, Pískálek, Cvrlík, Zoubek, Vír, Netopýrek, Mol, Pípálek, Šuměl, Visálek, Samet, Šmik, Kvičal, Blesk
names.gargoyle = Žula, Břidlice, Čedič, Kameník, Malta, Oblázek, Štěrk, Pazourek, Spára, Ruina, Křemen, Pemza, Římsa, Suk, Kamenovous, Bručoun, Štít, Chrlič, Dláto, Sokl
names.owl = Výr, Chytrolín, Brk, Bartold, Minerva, Tobiáš, Hůhů, Sovička, Pírko, Povzdech, Myslitel, Stařešina, Kulíšek, Noctua, Sovoslav, Pergamen, Šero, Puštík, Profesor, Prachovka
names.wyrm = Vermitrax, Popelochřtán, Spálenec, Kalderon, Ignivar, Žhnoroh, Drakkar, Pyrrhos, Obsidián, Žhavochřtán, Vulkanor, Pálizub, Malgrath, Tyrax, Inferno, Síra, Kaldor, Fafnir, Ragnor, Uhlík
names.cogling = Ozubák, Kolečko, Šroubek, Řehtačka, Nýtek, Převod, Kutil, Udělátko, Klíček, Pastorek, Cinkal, Šťoural, Matička, Bzučák, Klička, Káča, Příruba, Cvak, Tiktak, Vrut
names.revenant = Stesk, Hořkost, Popelavý, Sten, Prázdno, Žal, Bledý, Trud, Stín, Prach, Hrobník, Soumrak, Krypta, Tesknota, Bdění, Mohyla, Rubáš, Elegie, Kal, Pustina
names.beast = Chlupáč, Míša, Bruno, Huňáč, Mlaskal, Mechoun, Bručal, Medvídek, Křoví, Chroup, Cvakal, Lezec, Štětinka, Mech, Burácel, Ostružina, Cvrkal, Brblal, Brumla, Čmuchal
names.human = Gerhard, Roland, Konrád, Albrecht, Tristan, Eckhart, Gawain, Lancelot, Parsifal, Hugo, Bertram, Gotfríd, Ortvin, Balduin, Rajnold, Vilfríd, Dětřich, Gilbert, Ruprecht, Bořivoj
epithets.mid = Kostilam, Blatonoh, Krysokous, Špinoxicht, Kamenožrout, Lebkolam, Smradopěst, Rezokloub, Břichobij, Holenokop, Plísňovous, Špinodráp, Soplomet, Uchokous, Kolenolam, Červojed
epithets.high = Královrah, Zhoubopěst, Hrůzozub, Světoborec, Hrdinobij, Bouřepěst, Můrovládce, Duševrah, Železosrdce, Hradoborec, Drakobijce, Pekelník, Stínovládce, Krvokorunec, Zhoubotok, Hvězdolam
name.epithet = {0} {1}
msg.creature_fallen = Padl tvůj tvor: {0}. Pamětní kámen uchová jméno.
tile.memorial = Pamětní kámen
tile.memorial_info = Zde odpočívá {0} ({1}, úroveň {2}).
# hall of the fallen
hall.title = Síň padlých
hall.count = Padlí tvorové v paměti: {0}
hall.empty = Zatím nikdo nepadl. Ať to tak vydrží.
hall.kills = Zabití hrdinové: {0}
ov.fallen = Padlí ({0})
ov.living = Živí
cause.slain = vrah: {0}
cause.lava = smrt v lávě
cause.water = utonutí
cause.snare = smrt v pasti
cause.lightning = zásah bleskem
cause.fell = smrt v boji
+75
View File
@@ -264,6 +264,8 @@ val.medium = Middel
val.high = Høj val.high = Høj
val.normal = Normal val.normal = Normal
val.large = Stor val.large = Stor
val.small = Lille
val.auto_plain = Automatisk
# ---- key binding names # ---- key binding names
key.cam_forward = Kamera frem key.cam_forward = Kamera frem
@@ -1373,6 +1375,8 @@ blk.reveal = Afslør
blk.camera = Vis blk.camera = Vis
blk.gold = Guld + blk.gold = Guld +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Vejret over jorden
blk.talentxp = Talenterfaring +
blk.if = Hvis blk.if = Hvis
blk.repeat = Gentag blk.repeat = Gentag
blk.trigger = Skift udløser blk.trigger = Skift udløser
@@ -1392,6 +1396,8 @@ blk.help.reveal = Løfter krigens tåge over en zone.
blk.help.camera = Flytter kameraet til en zone. blk.help.camera = Flytter kameraet til en zone.
blk.help.gold = Giver (eller tager) guld. blk.help.gold = Giver (eller tager) guld.
blk.help.mana = Giver (eller tager) mana. blk.help.mana = Giver (eller tager) mana.
blk.help.weather = Sætter vejret over jorden (klart, regn, tåge eller storm) i så mange sekunder; 0 beholder det, til en anden blok ændrer det.
blk.help.talentxp = Giver spilleren erfaring til mestertalenterne.
blk.help.if = Kører blokkene indeni kun hvis betingelsen gælder, ellers dem under "ellers". blk.help.if = Kører blokkene indeni kun hvis betingelsen gælder, ellers dem under "ellers".
blk.help.repeat = Kører blokkene indeni flere gange. blk.help.repeat = Kører blokkene indeni flere gange.
blk.help.trigger = Slår en udløser til (den kan udløses igen) eller fra. blk.help.trigger = Slår en udløser til (den kan udløses igen) eller fra.
@@ -1414,6 +1420,7 @@ blk.v.or = eller
blk.v.not = ikke blk.v.not = ikke
blk.v.bool = Ja/nej blk.v.bool = Ja/nej
blk.v.eventkind = Udløserenhed er en blk.v.eventkind = Udløserenhed er en
blk.v.weatheris = Vejret over jorden er
blk.w_at = ved blk.w_at = ved
blk.w_by = med blk.w_by = med
blk.w_in = i blk.w_in = i
@@ -1462,6 +1469,7 @@ blk.until = Gentag indtil
blk.dountil = Gentag, tjek så: indtil blk.dountil = Gentag, tjek så: indtil
blk.wait = Vent blk.wait = Vent
blk.w_seconds = sekunder blk.w_seconds = sekunder
blk.w_for = i
blk.help.until = Kører blokkene indeni igen og igen, indtil betingelsen gælder - tjekkes før hver kørsel, så hvis den allerede gælder, kører indholdet slet ikke. blk.help.until = Kører blokkene indeni igen og igen, indtil betingelsen gælder - tjekkes før hver kørsel, så hvis den allerede gælder, kører indholdet slet ikke.
blk.help.dountil = Kører blokkene indeni, tjekker så betingelsen og gentager, indtil den gælder - indholdet kører altid mindst én gang. blk.help.dountil = Kører blokkene indeni, tjekker så betingelsen og gentager, indtil den gælder - indholdet kører altid mindst én gang.
blk.help.wait = Sætter dette script på pause i nogle sekunder, derefter fortsætter det med næste blok (også inde i løkker). blk.help.wait = Sætter dette script på pause i nogle sekunder, derefter fortsætter det med næste blok (også inde i løkker).
@@ -1692,3 +1700,70 @@ tut.slap = Krumfinger: Ha! Hørte du det? Noget kommer fra øst. Gør dig klar:
tut.obj6 = Besejr de to riddere. tut.obj6 = Besejr de to riddere.
tut.knights = Krumfinger: To riddere! Send dine væsner af sted med angrebsflaget (Ordrer), eller ram dem med lynet. tut.knights = Krumfinger: To riddere! Send dine væsner af sted med angrebsflaget (Ordrer), eller ram dem med lynet.
tut.win = Krumfinger: Godt gået, mester! Det er alt, hvad der skal til. Altså – næsten. Kampagnen fortæller resten. Jeg sætter kedlen over. tut.win = Krumfinger: Godt gået, mester! Det er alt, hvad der skal til. Altså – næsten. Kampagnen fortæller resten. Jeg sætter kedlen over.
# master talents
cat.master = Mester
talent.title = Mestertalenter
talent.points = Frie talentpoint: {0}
talent.next = {0}/{1} erfaring til næste point
talent.all = Alle talenter er lært.
talent.builder = Bygmester
talent.warlord = Krigsherre
talent.warlock = Heksemester
talent.merchant = Købmand
talent.builder.desc = Imps graver {0}% hurtigere, rum koster {1}% mindre.
talent.warlord.desc = Væsner slår {0}% hårdere og tager {1}% mindre skade.
talent.warlock.desc = Mana flyder {0}% hurtigere, besværgelser koster {1}% mindre.
talent.merchant.desc = Lønninger er {0}% lavere, afleverede skatte er {1}% mere værd.
talent.none = Endnu ikke lært.
talent.then = Næste niveau: {0}
msg.talent_point = Et nyt talentpoint! Brug det i mestervinduet.
msg.talent_learned = {0} niveau {1} lært.
# weather above ground
weather.fair = Klart
weather.rain = Regn
weather.fog = Tåge
weather.storm = Storm
msg.weather_clear = Over jorden klarer det op.
msg.weather_rain = Over jorden begynder det at regne - ild fænger ikke.
msg.weather_fog = Tåge driver ind over landet - udenfor ser alle mindre.
msg.weather_storm = Over jorden trækker en storm op! Lynet rammer enheder og træer.
msg.lightning_unit = Ramt af lynet: {0}!
# creature names
names.imp = Krumme, Snotte, Graver, Mudder, Fnug, Pjok, Rodemus, Snavs, Bussemand, Kradser, Brokke, Splatter, Søle, Pirk, Knold, Skovl, Snude, Pusling, Plet, Grums
names.kobold = Glam, Knib, Griberen, Skurv, Hap, Rusten, Svup, Bidder, Gab, Rotte, Flænge, Pigge, Klo, Ballade, Rod, Fnis, Skubber, Hak, Knæk, Skramme
names.spider = Kravle, Uldben, Edderkop, Kilde, Netto, Ottefod, Tråde, Gyselig, Fimre, Silke, Væverske, Hopsa, Fnugben, Tekla, Spole, Snappe, Garnnøgle, Klistre, Vimse, Nålen
names.mage = Bartholomæus, Ignatius, Leopold, Theobald, Adalbert, Hieronymus, Valdemar, Gotfred, Cornelius, Ottokar, Florentin, Engelbrecht, Pankratius, Rupertus, Konstantin, Eusebius, Severin, Barnabas, Archibald, Vitus
names.ogre = Brum, Klods, Bums, Klump, Dunk, Tumpe, Bøvl, Klasker, Tramp, Træhoved, Plump, Mops, Bolle, Vom, Knippel, Okse, Rumle, Klat, Hump, Gorbo
names.jailer = Grum, Slå, Lænke, Lås, Fangekælder, Jernskæg, Bøjle, Bolt, Streng, Tremme, Gnaven, Krog, Strikke, Nøgle, Dyster, Klirr, Stokmand, Slutter, Bur, Bitter
names.priestess = Glød, Flamme, Aske, Serafina, Lue, Ildine, Morwen, Brynhild, Ignia, Gnist, Skarlagen, Funke, Ravna, Lilith, Tønder, Isolde, Brandhild, Nyx, Vesper, Glimt
names.chainbreaker = Lænkebryder, Ulfgar, Jernsjæl, Brannok, Kragmor, Valdrek, Torn, Gorath, Ubunden, Rurik, Torvald, Hagen, Durnok, Splint, Brand, Ulfrik, Korrun, Arvak, Sigurd, Halvard
names.bat = Pip, Flagre, Piber, Susle, Ekko, Flap, Tusmørke, Hvisle, Kvidre, Tandsen, Hvirvel, Flagermus, Møl, Pippe, Suse, Dingle, Fløjl, Smutte, Hvin, Lynet
names.gargoyle = Granit, Skifer, Basalt, Grimbert, Mørtel, Kisel, Skærver, Flint, Fuge, Murbrok, Kvarts, Pimpsten, Gesims, Knort, Stenskæg, Gnaven, Gavl, Vandspyer, Mejsel, Sokkel
names.owl = Uhu, Klogeåge, Fjerpen, Bartholdus, Minerva, Tobias, Huhu, Uglalia, Fjerdrik, Suk, Grubler, Ældste, Kirkeugle, Noctua, Uglebert, Pergament, Skumring, Natugle, Professor, Dun
names.wyrm = Vermithrax, Askegab, Svider, Calderon, Ignivar, Gløderhorn, Drakkar, Pyrrhus, Obsidian, Glødgab, Vulkanor, Brandtand, Malgrath, Tyrax, Inferno, Svovl, Kaldor, Fafner, Ragnor, Sinderos
names.cogling = Tandhjul, Hjulet, Skrue, Skralde, Bolt, Gear, Fikser, Dimsedut, Nøglen, Drev, Klirre, Fedter, Nitte, Snurre, Krumtap, Snurretop, Flange, Klikklak, Tiktak, Nippel
names.revenant = Morgen, Nag, Askegrå, Klage, Hul, Sorg, Bleg, Kummer, Skygge, Muld, Gravmand, Tusmørke, Krypt, Vemod, Vågen, Høj, Ligklæde, Elegi, Bærme, Øde
names.beast = Pjusk, Brumbjørn, Bruno, Lurvet, Mumle, Mosryg, Brummer, Ursa, Knort, Knas, Klikker, Kravler, Børste, Mos, Rumlen, Tjørn, Knirke, Gnavpot, Bamse, Snuse
names.human = Gerhard, Roland, Konrad, Albrecht, Tristan, Eskild, Gavin, Lancelot, Palle, Holger, Bertram, Gotfred, Esbern, Valdemar, Regnar, Vilfred, Ditlev, Gilbert, Rasmus, Dirk
epithets.mid = Knoglebryder, Mudderfod, Rottebider, Skidtfjæs, Stengnaver, Kraniebanker, Stinkenæve, Rustknogle, Mavestøder, Skinnebensspark, Skimmelskæg, Skidtklo, Snotslynger, Øregnaver, Knæknækker, Ormeæder
epithets.high = Kongedræber, Undergangshånd, Rædselstand, Verdensknuser, Heltebane, Stormnæve, Natterædsel, Sjælehøster, Jernhjerte, Borgknuser, Drageskræk, Helvedesvandrer, Skyggefyrste, Blodkrone, Dommeflod, Stjerneknuser
name.epithet = {0} {1}
msg.creature_fallen = {0} er faldet. En mindesten bevarer navnet.
tile.memorial = Mindesten
tile.memorial_info = Her hviler {0} ({1}, niveau {2}).
# hall of the fallen
hall.title = De faldnes hal
hall.count = Faldne væsner i erindring: {0}
hall.empty = Endnu er ingen faldet. Længe må det vare.
hall.kills = Dræbte helte: {0}
ov.fallen = Faldne ({0})
ov.living = Levende
cause.slain = dræbt af: {0}
cause.lava = brændt i lava
cause.water = druknet
cause.snare = trådt i en saks
cause.lightning = ramt af lynet
cause.fell = faldet
+75
View File
@@ -263,6 +263,8 @@ val.medium = Mittel
val.high = Hoch val.high = Hoch
val.normal = Normal val.normal = Normal
val.large = Groß val.large = Groß
val.small = Klein
val.auto_plain = Automatisch
# ---- Namen der Tastenbelegung # ---- Namen der Tastenbelegung
key.cam_forward = Kamera vor key.cam_forward = Kamera vor
@@ -1373,6 +1375,8 @@ blk.reveal = Aufdecken
blk.camera = Zeigen blk.camera = Zeigen
blk.gold = Gold + blk.gold = Gold +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Wetter über der Erde
blk.talentxp = Talent-Erfahrung +
blk.if = Wenn blk.if = Wenn
blk.repeat = Wiederhole blk.repeat = Wiederhole
blk.trigger = Trigger schalten blk.trigger = Trigger schalten
@@ -1392,6 +1396,8 @@ blk.help.reveal = Hebt den Nebel über einer Zone.
blk.help.camera = Bewegt die Kamera zu einer Zone. blk.help.camera = Bewegt die Kamera zu einer Zone.
blk.help.gold = Gibt (oder nimmt) Gold. blk.help.gold = Gibt (oder nimmt) Gold.
blk.help.mana = Gibt (oder nimmt) Mana. blk.help.mana = Gibt (oder nimmt) Mana.
blk.help.weather = Setzt das Wetter über der Erde (heiter, Regen, Nebel oder Sturm) für so viele Sekunden; 0 hält es, bis ein anderer Block es ändert.
blk.help.talentxp = Gibt dem Spieler Erfahrung für die Meister-Talente.
blk.help.if = Führt die Blöcke darin nur aus, wenn die Bedingung gilt, sonst die unter "sonst". blk.help.if = Führt die Blöcke darin nur aus, wenn die Bedingung gilt, sonst die unter "sonst".
blk.help.repeat = Führt die Blöcke darin mehrmals aus. blk.help.repeat = Führt die Blöcke darin mehrmals aus.
blk.help.trigger = Schaltet einen Trigger ein (er darf erneut auslösen) oder aus. blk.help.trigger = Schaltet einen Trigger ein (er darf erneut auslösen) oder aus.
@@ -1414,6 +1420,7 @@ blk.v.or = oder
blk.v.not = nicht blk.v.not = nicht
blk.v.bool = Ja/Nein blk.v.bool = Ja/Nein
blk.v.eventkind = Auslöser ist ein blk.v.eventkind = Auslöser ist ein
blk.v.weatheris = Wetter über der Erde ist
blk.w_at = bei blk.w_at = bei
blk.w_by = um blk.w_by = um
blk.w_in = in blk.w_in = in
@@ -1462,6 +1469,7 @@ blk.until = Wiederhole bis
blk.dountil = Wiederhole, prüfe danach: bis blk.dountil = Wiederhole, prüfe danach: bis
blk.wait = Warte blk.wait = Warte
blk.w_seconds = Sekunden blk.w_seconds = Sekunden
blk.w_for = für
blk.help.until = Führt die Blöcke darin immer wieder aus, bis die Bedingung gilt - geprüft vor jedem Durchlauf; gilt sie schon, läuft der Inhalt gar nicht. blk.help.until = Führt die Blöcke darin immer wieder aus, bis die Bedingung gilt - geprüft vor jedem Durchlauf; gilt sie schon, läuft der Inhalt gar nicht.
blk.help.dountil = Führt die Blöcke darin aus, prüft dann die Bedingung und wiederholt, bis sie gilt - der Inhalt läuft mindestens einmal. blk.help.dountil = Führt die Blöcke darin aus, prüft dann die Bedingung und wiederholt, bis sie gilt - der Inhalt läuft mindestens einmal.
blk.help.wait = Hält dieses Skript einige Sekunden an, danach geht es mit dem nächsten Block weiter (auch in Schleifen). blk.help.wait = Hält dieses Skript einige Sekunden an, danach geht es mit dem nächsten Block weiter (auch in Schleifen).
@@ -1692,3 +1700,70 @@ tut.slap = Crookfinger: Ha! Hast du das gehört? Aus dem Osten kommt etwas. Mach
tut.obj6 = Besiege die beiden Ritter. tut.obj6 = Besiege die beiden Ritter.
tut.knights = Crookfinger: Zwei Ritter! Schick deine Kreaturen mit der Angriffsflagge (Befehle) oder triff sie mit dem Blitz. tut.knights = Crookfinger: Zwei Ritter! Schick deine Kreaturen mit der Angriffsflagge (Befehle) oder triff sie mit dem Blitz.
tut.win = Crookfinger: Gut gemacht, Meister! Mehr ist es nicht. Na ja – fast. Den Rest erzählt die Kampagne. Ich setze Wasser auf. tut.win = Crookfinger: Gut gemacht, Meister! Mehr ist es nicht. Na ja – fast. Den Rest erzählt die Kampagne. Ich setze Wasser auf.
# master talents
cat.master = Meister
talent.title = Meister-Talente
talent.points = Freie Talentpunkte: {0}
talent.next = {0}/{1} Erfahrung bis zum nächsten Punkt
talent.all = Alle Talente sind gelernt.
talent.builder = Baumeister
talent.warlord = Kriegsherr
talent.warlock = Hexer
talent.merchant = Händler
talent.builder.desc = Imps graben {0}% schneller, Räume kosten {1}% weniger.
talent.warlord.desc = Kreaturen schlagen {0}% härter zu und nehmen {1}% weniger Schaden.
talent.warlock.desc = Mana fließt {0}% schneller, Zauber kosten {1}% weniger.
talent.merchant.desc = Löhne sind {0}% niedriger, gelieferte Schätze sind {1}% mehr wert.
talent.none = Noch nicht gelernt.
talent.then = Nächste Stufe: {0}
msg.talent_point = Ein neuer Talentpunkt! Verteile ihn im Meister-Fenster.
msg.talent_learned = {0} Stufe {1} gelernt.
# weather above ground
weather.fair = Heiter
weather.rain = Regen
weather.fog = Nebel
weather.storm = Sturm
msg.weather_clear = Über der Erde klart es auf.
msg.weather_rain = Über der Erde beginnt es zu regnen - Feuer fängt nicht.
msg.weather_fog = Nebel zieht über das Land - draußen sieht jeder weniger.
msg.weather_storm = Über der Erde zieht ein Sturm auf! Blitze treffen Einheiten und Bäume.
msg.lightning_unit = Vom Blitz getroffen: {0}!
# creature names
names.imp = Krümel, Schnodder, Buddel, Matsch, Fussel, Knirps, Wühli, Schmutzi, Popel, Kratzer, Grummel, Bröckel, Zappel, Schlammi, Pfriem, Knubbel, Grabi, Rotzi, Wusel, Schaufel
names.kobold = Kläff, Zwick, Raffzahn, Krätz, Schnapp, Rostig, Zack, Beißer, Klau, Ratz, Fetz, Zausel, Kralle, Stänker, Wurz, Fiesling, Rempel, Zinken, Knick, Schrammi
names.spider = Krabbel, Wollbein, Spinnchen, Kitzel, Netti, Achtfuß, Fädchen, Gruselchen, Tüdel, Seidi, Webbi, Huschel, Krümelbein, Tekla, Spindel, Fangi, Zwirn, Klebi, Wuschel, Nadelchen
names.mage = Bartholomäus, Ignatius, Leopold, Theobald, Adalbert, Kunibert, Wendelin, Gottlieb, Hieronymus, Ottokar, Florian, Engelbert, Pankratius, Ruprecht, Konstantin, Eusebius, Sebaldus, Cornelius, Archibald, Waldemar
names.ogre = Grunz, Klotz, Bums, Brocken, Rumms, Dumpf, Wumme, Klopper, Trampel, Holzkopf, Plumps, Mops, Bolle, Wanst, Knüppel, Ochs, Rumpel, Batzen, Hauruck, Gorbo
names.jailer = Grimm, Riegel, Kettler, Schloss, Kerker, Eisenbart, Fessel, Bolzen, Streng, Gitter, Mürrisch, Haken, Strick, Schlüssel, Finster, Klirr, Stockmann, Wächter, Zwinger, Bitter
names.priestess = Glut, Flamma, Asche, Seraphine, Lodera, Feuria, Morwen, Brunhild, Ignia, Kriemhild, Scharlach, Funkia, Rabea, Lilith, Zunder, Isolde, Brennhild, Nyx, Vesper, Glimma
names.chainbreaker = Kettenbann, Ulfgar, Eisenseele, Brannok, Kragmor, Valdrek, Dorn, Gorath, Ungebunden, Rurik, Torvald, Hagen, Durnok, Sprengel, Brand, Wulfrich, Korrun, Arvak, Sigbert, Halvard
names.bat = Piepsi, Flatter, Fiepe, Schwirr, Echo, Flapp, Dämmer, Zischi, Zirp, Zähnchen, Wirbel, Flederli, Motti, Piep, Sausi, Baumel, Samtchen, Huschi, Quiek, Blitzi
names.gargoyle = Granit, Schiefer, Basalt, Grimmbert, Mörtel, Kiesel, Schotter, Feuerstein, Fuge, Geröll, Quarz, Bimsstein, Sims, Knorz, Steinbart, Grantig, Giebel, Wasserspeier, Meißel, Sockel
names.owl = Uhuwald, Weisling, Kiel, Bartholdo, Minerva, Tobias, Huhu, Eulalia, Federich, Hauch, Grübel, Älteste, Kauzi, Noctua, Eulbert, Pergament, Dämmer, Waldkauz, Professor, Plusch
names.wyrm = Vermithrax, Aschenmaul, Sengbart, Calderon, Ignivar, Glimmhorn, Drakkar, Pyrrhus, Obsidian, Glutmaul, Vulkanor, Brennzahn, Malgrath, Tyrax, Inferno, Schwefel, Kaldor, Fafnir, Ragnor, Zinderos
names.cogling = Rädchen, Zahnrad, Schräubchen, Ratsche, Bolzen, Getriebe, Tüftel, Dingsbums, Schlüssel, Ritzel, Klimper, Fummel, Niete, Surr, Kurbel, Drehwurm, Flansch, Klackerle, Ticktack, Nippel
names.revenant = Morgen, Gram, Aschgrau, Klage, Hohl, Trauer, Bleich, Kummer, Schemen, Moder, Grabmann, Dämmer, Gruft, Wehmut, Wache, Hügel, Leichentuch, Elegie, Neige, Öde
names.beast = Zottel, Brummbär, Bruno, Struppi, Mampf, Moosrücken, Brummel, Ursa, Knorr, Knacks, Klicker, Krabbelbert, Borste, Moos, Rumpel, Dornbusch, Zirpel, Grummler, Wuchtel, Schnuffel
names.human = Gerhard, Roland, Konrad, Albrecht, Tristan, Eckhart, Gawan, Lienhard, Parzival, Hugo, Bertram, Gottfried, Ortwin, Balduin, Reinhold, Wilfried, Iwein, Gilbert, Ruprecht, Dietrich
epithets.mid = Knochenbrecher, Schlammfuß, Rattenbeißer, Dreckgesicht, Kieselkauer, Schädelklopfer, Stinkefaust, Rostknöchel, Bauchboxer, Schienbeintreter, Schimmelbart, Dreckkralle, Rotzschleuder, Ohrenkauer, Kniebrecher, Wurmfresser
epithets.high = Königsschreck, Unheilsfaust, Schreckenszahn, Weltenbrecher, Heldenbann, Sturmfaust, Nachtgrauen, Seelenschnitter, Eisenherz, Burgenbrecher, Drachenbann, Höllenwanderer, Schattenfürst, Blutkrone, Grimmflut, Sternbrecher
name.epithet = {0} {1}
msg.creature_fallen = {0} ist gefallen. Ein Gedenkstein bewahrt den Namen.
tile.memorial = Gedenkstein
tile.memorial_info = Hier ruht {0} ({1}, Stufe {2}).
# hall of the fallen
hall.title = Halle der Gefallenen
hall.count = {0} gefallene Kreaturen in Erinnerung
hall.empty = Noch ist niemand gefallen. Möge es so bleiben.
hall.kills = {0} Helden erschlagen
ov.fallen = Gefallene ({0})
ov.living = Lebende
cause.slain = erschlagen von: {0}
cause.lava = in Lava verbrannt
cause.water = ertrunken
cause.snare = in eine Falle getreten
cause.lightning = vom Blitz getroffen
cause.fell = gefallen
+75
View File
@@ -264,6 +264,8 @@ val.medium = Μεσαία
val.high = Υψηλή val.high = Υψηλή
val.normal = Κανονικό val.normal = Κανονικό
val.large = Μεγάλο val.large = Μεγάλο
val.small = Μικρό
val.auto_plain = Αυτόματο
# ---- key binding names # ---- key binding names
key.cam_forward = Κάμερα μπροστά key.cam_forward = Κάμερα μπροστά
@@ -1374,6 +1376,8 @@ blk.reveal = Αποκάλυψη
blk.camera = Δείξε blk.camera = Δείξε
blk.gold = Χρυσός + blk.gold = Χρυσός +
blk.mana = Μάνα + blk.mana = Μάνα +
blk.weather = Καιρός στην επιφάνεια
blk.talentxp = Εμπειρία ταλέντων +
blk.if = Αν blk.if = Αν
blk.repeat = Επανάληψη blk.repeat = Επανάληψη
blk.trigger = Διακόπτης εναύσματος blk.trigger = Διακόπτης εναύσματος
@@ -1393,6 +1397,8 @@ blk.help.reveal = Σηκώνει την ομίχλη του πολέμου πά
blk.help.camera = Μετακινεί την κάμερα σε μια ζώνη. blk.help.camera = Μετακινεί την κάμερα σε μια ζώνη.
blk.help.gold = Δίνει (ή παίρνει) χρυσό. blk.help.gold = Δίνει (ή παίρνει) χρυσό.
blk.help.mana = Δίνει (ή παίρνει) μάνα. blk.help.mana = Δίνει (ή παίρνει) μάνα.
blk.help.weather = Ορίζει τον καιρό στην επιφάνεια (αίθριο, βροχή, ομίχλη ή καταιγίδα) για τόσα δευτερόλεπτα· το 0 τον κρατά μέχρι να τον αλλάξει άλλο μπλοκ.
blk.help.talentxp = Δίνει στον παίκτη εμπειρία για τα ταλέντα του άρχοντα.
blk.help.if = Εκτελεί τα μπλοκ μέσα μόνο αν ισχύει η συνθήκη, αλλιώς αυτά κάτω από το "αλλιώς". blk.help.if = Εκτελεί τα μπλοκ μέσα μόνο αν ισχύει η συνθήκη, αλλιώς αυτά κάτω από το "αλλιώς".
blk.help.repeat = Εκτελεί τα μπλοκ μέσα πολλές φορές. blk.help.repeat = Εκτελεί τα μπλοκ μέσα πολλές φορές.
blk.help.trigger = Ενεργοποιεί ένα έναυσμα (μπορεί να ξαναπυροδοτηθεί) ή το απενεργοποιεί. blk.help.trigger = Ενεργοποιεί ένα έναυσμα (μπορεί να ξαναπυροδοτηθεί) ή το απενεργοποιεί.
@@ -1415,6 +1421,7 @@ blk.v.or = ή
blk.v.not = όχι blk.v.not = όχι
blk.v.bool = Ναι/όχι blk.v.bool = Ναι/όχι
blk.v.eventkind = Μονάδα εναύσματος είναι blk.v.eventkind = Μονάδα εναύσματος είναι
blk.v.weatheris = Ο καιρός στην επιφάνεια είναι
blk.w_at = σε blk.w_at = σε
blk.w_by = κατά blk.w_by = κατά
blk.w_in = σε blk.w_in = σε
@@ -1463,6 +1470,7 @@ blk.until = Επανάληψη ώσπου
blk.dountil = Επανάληψη, μετά έλεγχος: ώσπου blk.dountil = Επανάληψη, μετά έλεγχος: ώσπου
blk.wait = Αναμονή blk.wait = Αναμονή
blk.w_seconds = δευτερόλεπτα blk.w_seconds = δευτερόλεπτα
blk.w_for = για
blk.help.until = Εκτελεί τα μπλοκ μέσα ξανά και ξανά ώσπου να ισχύσει η συνθήκη - ελέγχεται πριν από κάθε εκτέλεση, οπότε αν ισχύει ήδη, το εσωτερικό δεν εκτελείται καθόλου. blk.help.until = Εκτελεί τα μπλοκ μέσα ξανά και ξανά ώσπου να ισχύσει η συνθήκη - ελέγχεται πριν από κάθε εκτέλεση, οπότε αν ισχύει ήδη, το εσωτερικό δεν εκτελείται καθόλου.
blk.help.dountil = Εκτελεί τα μπλοκ μέσα, μετά ελέγχει τη συνθήκη και επαναλαμβάνει ώσπου να ισχύσει - το εσωτερικό εκτελείται πάντα τουλάχιστον μία φορά. blk.help.dountil = Εκτελεί τα μπλοκ μέσα, μετά ελέγχει τη συνθήκη και επαναλαμβάνει ώσπου να ισχύσει - το εσωτερικό εκτελείται πάντα τουλάχιστον μία φορά.
blk.help.wait = Σταματά αυτό το σενάριο για μερικά δευτερόλεπτα, και μετά συνεχίζει με το επόμενο μπλοκ (και μέσα σε βρόχους). blk.help.wait = Σταματά αυτό το σενάριο για μερικά δευτερόλεπτα, και μετά συνεχίζει με το επόμενο μπλοκ (και μέσα σε βρόχους).
@@ -1693,3 +1701,70 @@ tut.slap = Στραβοδάχτυλος: Χα! Το άκουσες; Κάτι έ
tut.obj6 = Νίκησε τους δύο ιππότες. tut.obj6 = Νίκησε τους δύο ιππότες.
tut.knights = Στραβοδάχτυλος: Δύο ιππότες! Στείλε τα πλάσματά σου με τη σημαία επίθεσης (Εντολές) ή χτύπησέ τους με κεραυνό. tut.knights = Στραβοδάχτυλος: Δύο ιππότες! Στείλε τα πλάσματά σου με τη σημαία επίθεσης (Εντολές) ή χτύπησέ τους με κεραυνό.
tut.win = Στραβοδάχτυλος: Μπράβο, αφέντη! Αυτό ήταν όλο. Ε - σχεδόν. Τα υπόλοιπα θα τα πει η εκστρατεία. Βάζω νερό για τσάι. tut.win = Στραβοδάχτυλος: Μπράβο, αφέντη! Αυτό ήταν όλο. Ε - σχεδόν. Τα υπόλοιπα θα τα πει η εκστρατεία. Βάζω νερό για τσάι.
# master talents
cat.master = Άρχοντας
talent.title = Ταλέντα του άρχοντα
talent.points = Ελεύθεροι πόντοι ταλέντου: {0}
talent.next = {0}/{1} εμπειρία ως τον επόμενο πόντο
talent.all = Όλα τα ταλέντα έχουν μαθευτεί.
talent.builder = Χτίστης
talent.warlord = Πολέμαρχος
talent.warlock = Μαυρομάγος
talent.merchant = Έμπορος
talent.builder.desc = Τα δαιμόνια σκάβουν {0}% πιο γρήγορα, τα δωμάτια κοστίζουν {1}% λιγότερο.
talent.warlord.desc = Τα πλάσματα χτυπούν {0}% πιο δυνατά και δέχονται {1}% λιγότερη ζημιά.
talent.warlock.desc = Η μάνα ρέει {0}% πιο γρήγορα, τα ξόρκια κοστίζουν {1}% λιγότερο.
talent.merchant.desc = Οι μισθοί είναι {0}% χαμηλότεροι, ο παραδομένος θησαυρός αξίζει {1}% περισσότερο.
talent.none = Δεν έχει μαθευτεί ακόμη.
talent.then = Επόμενο επίπεδο: {0}
msg.talent_point = Νέος πόντος ταλέντου! Ξόδεψέ τον στο παράθυρο του άρχοντα.
msg.talent_learned = {0}: μαθεύτηκε το επίπεδο {1}.
# weather above ground
weather.fair = Αίθριο
weather.rain = Βροχή
weather.fog = Ομίχλη
weather.storm = Καταιγίδα
msg.weather_clear = Στην επιφάνεια ο ουρανός καθαρίζει.
msg.weather_rain = Στην επιφάνεια αρχίζει να βρέχει - η φωτιά δεν πιάνει.
msg.weather_fog = Η ομίχλη απλώνεται στη γη - έξω όλοι βλέπουν λιγότερο.
msg.weather_storm = Στην επιφάνεια πλησιάζει καταιγίδα! Οι κεραυνοί χτυπούν μονάδες και δέντρα.
msg.lightning_unit = Χτυπήθηκε από κεραυνό: {0}!
# creature names
names.imp = Ψίχουλος, Μύξας, Σκαφτούλης, Λασπάκιας, Χνούδης, Μπόμπιρας, Τρυπούλης, Βρομίτσας, Τσίμπλης, Γρατζούνης, Σβόλος, Λεκές, Βουρκάκης, Κουτσουλιάς, Κοτρωνάκης, Φτυαράκης, Μουτσούνης, Τσουτσέκος, Πιτσιλάκης, Σκουληκάς
names.kobold = Γαβγίσης, Τσιμπούκος, Δοντάς, Ψωριάρης, Δαγκάνας, Σκουριάς, Σβουρίτσας, Μασουλάς, Χάχας, Αρουραίος, Κουρέλης, Αγκάθης, Νυχάς, Καβγατζής, Ρίζος, Χαχανούλης, Σπρωξάκης, Φαφούτης, Κρακ, Γρατζουνάς
names.spider = Ποδαράκια, Μαλλιαρούλα, Αραχνούλα, Γαργαλίτσα, Διχτάκι, Οχτάποδη, Κλωστίτσα, Τρομαράκι, Ψαχουλίτσα, Μεταξούλα, Υφάντρα, Χοπ, Χνουδούλα, Θέκλα, Καρούλι, Αρπαχτούλα, Κουβαράκι, Κολλιτσίδα, Σβούρα, Βελονίτσα
names.mage = Βαρθολομαίος, Ιγνάτιος, Λεοπόλδος, Θεοβάλδος, Πολυχρόνιος, Ιερώνυμος, Χαραλάμπης, Παντελεήμων, Κορνήλιος, Θεμιστοκλής, Φλωρέντιος, Ευστάθιος, Παγκράτιος, Αρχέλαος, Κωνσταντίνος, Ευσέβιος, Σεβηρίνος, Βαρνάβας, Τιμολέων, Αναστάσιος
names.ogre = Μπουμπούνας, Κούτσουρο, Μπαμ, Βράχος, Χαζούλης, Ντουβάρι, Χοντρούλης, Κοπανιστής, Ποδοβολάς, Ξυλοκέφαλος, Πλατς, Κοιλαράς, Μπουλούκος, Τουμπανάς, Ρόπαλο, Βόδι, Γδούπος, Ογκόλιθος, Τύμπανο, Γκόρμπο
names.jailer = Σκυθρωπός, Σύρτης, Αλυσίδας, Λουκέτος, Μπουντρούμης, Σιδερογένης, Κρίκος, Μπουλόνι, Αυστηρός, Κάγκελος, Γκρινιάρης, Γάντζος, Θηλιάς, Κλειδούχος, Ζοφερός, Κουδουνάς, Φρουράρχος, Δεσμοφύλακας, Κλουβής, Πικρός
names.priestess = Θράκα, Φλόγα, Στάχτη, Σεραφίνα, Πυρά, Φωτιά, Μόργκανα, Βρουνχίλδη, Ιγνία, Σπίθα, Πορφύρα, Ανθρακιά, Κοράκω, Λίλιθ, Ίσκα, Ιζόλδη, Λαμπαδίτσα, Νυξ, Εσπερία, Φλογίτσα
names.chainbreaker = Αλυσοσπάστης, Ούλφγκαρ, Σιδερόψυχος, Μπράννοκ, Κράγκμορ, Βάλντρεκ, Αγκάθι, Γκόραθ, Αδούλωτος, Ρούρικ, Τόρβαλντ, Χάγκεν, Ντούρνοκ, Θραύσμα, Δαυλός, Λυκόψυχος, Κόρρουν, Άρβακ, Ελευθερωτής, Χάλβαρντ
names.bat = Τσιρίδας, Φτερουγάκης, Πιπ, Θρόισμα, Ηχώ, Φτερουγίτσα, Σούρουπο, Σφυριχτής, Τιτίβισμα, Δοντάκι, Στρόβιλος, Νυχτεριδάκι, Σκόρος, Πιπίνος, Σβουράκης, Κρεμαστός, Βελούδο, Γλιστράκης, Στρίγκλης, Αστραπή
names.gargoyle = Γρανίτης, Σχιστόλιθος, Βασάλτης, Πετρόμουτρος, Κονίαμα, Βότσαλος, Χαλίκης, Πυρόλιθος, Αρμός, Ερειπιάς, Χαλαζίας, Κίσσηρη, Γείσος, Ρόζος, Πετρογένης, Κατσούφης, Αέτωμα, Υδρορρόη, Σμιλάκης, Βάθρος
names.owl = Μπούφος, Σοφούλης, Πέννα, Βαρθόλδος, Αθηνά, Τωβίας, Χουχού, Κουκουβάγια, Φτερούλης, Αναστεναγμός, Στοχαστής, Γεροντάκος, Χαμοκελάδα, Γλαύκα, Γλαυκούλης, Περγαμηνή, Λυκόφως, Νυχτοπούλι, Καθηγητής, Πούπουλο
names.wyrm = Βερμίθραξ, Σταχτόστομος, Καψαλιάρης, Καλντέρων, Ιγνίβαρ, Πυρόκερος, Δράκκαρ, Πύρρος, Οψιδιανός, Πυρόστομος, Ηφαιστάρας, Καψόδοντος, Μάλγκραθ, Τύραξ, Κόλαση, Θειάφης, Κάλντορ, Φάφνιρ, Ράγκνορ, Δαυλίτης
names.cogling = Γραναζάκης, Ροδίτσα, Βιδούλης, Καστάνιας, Πριτσίνι, Μεταδότης, Μαστοράκος, Μαραφέτι, Κλειδάκι, Πινιόν, Κουδουνίτσα, Ψαχούλης, Παξιμάδι, Βουιτζής, Μανιβέλα, Σβούρος, Φλάντζα, Κλικ, Τικτάκ, Ελατηριάκι
names.revenant = Θρήνος, Πίκρα, Σταχτής, Βογκητής, Κενός, Πένθος, Χλωμός, Καημός, Σκιά, Χώμα, Νεκροθάφτης, Σούρουπος, Κρύπτη, Νοσταλγός, Άγρυπνος, Τύμβος, Σάβανο, Ελεγεία, Κατακάθι, Ερημιά
names.beast = Μαλλιαράς, Αρκουδάκος, Μπρούνο, Τριχωτός, Μασουλίνος, Βρυόραχος, Γρυλιστής, Αρκουδίτσα, Θάμνος, Κριτσανίας, Τσακ, Σέρνας, Σκαθαράς, Βρύο, Βρόντος, Βατομουριάς, Τριζόνης, Γκρινιάρος, Μπάρμπας, Μυριστής
names.human = Γεράρδος, Ορλάνδος, Κορράδος, Αλβέρτος, Τριστάνος, Έκαρτ, Γαβαίνος, Λάνσελοτ, Πάρσιφαλ, Ούγκος, Βέρτραμ, Γοδεφρείδος, Όρτβιν, Βαλδουίνος, Ρεϊνάλδος, Γουλιέλμος, Θεοδέριχος, Γκίλμπερτ, Ροβέρτος, Βαλεριανός
epithets.mid = Κοκαλοσπάστης, Λασποπόδαρος, Ποντικοφάγος, Βρομόμουτρος, Χαλικομάσης, Κρανιοκόπος, Βρομογροθιάς, Σκουριόκοκαλος, Κοιλιοσπάστης, Καλαμοκλότσης, Μουχλογένης, Βρομόνυχος, Μυξοπετάχτης, Αυτιοφάγος, Γονατοσπάστης, Σκουληκοφάγος
epithets.high = Βασιλοκτόνος, Ολεθροχέρης, Τρομόδοντος, Κοσμοσπάστης, Ηρωοφόνος, Θυελλογροθιάς, Νυχτότρομος, Ψυχοθερίστης, Σιδερόκαρδος, Καστροσπάστης, Δρακοκτόνος, Αδοδιαβάτης, Σκιάρχης, Αιματοστέφανος, Ολεθρόπλημμυρος, Αστροσπάστης
name.epithet = {0} {1}
msg.creature_fallen = Έπεσε: {0}. Μια αναμνηστική πέτρα κρατά το όνομα.
tile.memorial = Αναμνηστική πέτρα
tile.memorial_info = Εδώ αναπαύεται: {0} ({1}, επίπεδο {2}).
# hall of the fallen
hall.title = Αίθουσα των πεσόντων
hall.count = Πεσόντα πλάσματα στη μνήμη: {0}
hall.empty = Κανείς δεν έχει πέσει ακόμη. Είθε να κρατήσει.
hall.kills = Ήρωες που σκοτώθηκαν: {0}
ov.fallen = Πεσόντες ({0})
ov.living = Ζωντανοί
cause.slain = φονιάς: {0}
cause.lava = θάνατος στη λάβα
cause.water = πνιγμός
cause.snare = θάνατος σε παγίδα
cause.lightning = χτύπημα κεραυνού
cause.fell = θάνατος στη μάχη
+75
View File
@@ -264,6 +264,8 @@ val.medium = Medium
val.high = High val.high = High
val.normal = Normal val.normal = Normal
val.large = Large val.large = Large
val.small = Small
val.auto_plain = Auto
# ---- key binding names # ---- key binding names
key.cam_forward = Camera forward key.cam_forward = Camera forward
@@ -1374,6 +1376,8 @@ blk.reveal = Reveal
blk.camera = Show blk.camera = Show
blk.gold = Gold + blk.gold = Gold +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Weather above ground
blk.talentxp = Talent experience +
blk.if = If blk.if = If
blk.repeat = Repeat blk.repeat = Repeat
blk.trigger = Switch trigger blk.trigger = Switch trigger
@@ -1393,6 +1397,8 @@ blk.help.reveal = Lifts the fog of war over a zone.
blk.help.camera = Moves the camera to a zone. blk.help.camera = Moves the camera to a zone.
blk.help.gold = Gives (or takes) gold. blk.help.gold = Gives (or takes) gold.
blk.help.mana = Gives (or takes) mana. blk.help.mana = Gives (or takes) mana.
blk.help.weather = Sets the weather above ground (fair, rain, fog or storm) for so many seconds; 0 keeps it until another block changes it.
blk.help.talentxp = Gives the player experience for the master talents.
blk.help.if = Runs the blocks inside only if the condition holds, otherwise the ones under "else". blk.help.if = Runs the blocks inside only if the condition holds, otherwise the ones under "else".
blk.help.repeat = Runs the blocks inside several times. blk.help.repeat = Runs the blocks inside several times.
blk.help.trigger = Switches a trigger on (it may fire again) or off. blk.help.trigger = Switches a trigger on (it may fire again) or off.
@@ -1415,6 +1421,7 @@ blk.v.or = or
blk.v.not = not blk.v.not = not
blk.v.bool = Yes/no blk.v.bool = Yes/no
blk.v.eventkind = Trigger unit is a blk.v.eventkind = Trigger unit is a
blk.v.weatheris = Weather above ground is
blk.w_at = at blk.w_at = at
blk.w_by = by blk.w_by = by
blk.w_in = in blk.w_in = in
@@ -1463,6 +1470,7 @@ blk.until = Repeat until
blk.dountil = Repeat, then check: until blk.dountil = Repeat, then check: until
blk.wait = Wait blk.wait = Wait
blk.w_seconds = seconds blk.w_seconds = seconds
blk.w_for = for
blk.help.until = Runs the blocks inside again and again until the condition holds - checked before each run, so if it already holds the inside does not run at all. blk.help.until = Runs the blocks inside again and again until the condition holds - checked before each run, so if it already holds the inside does not run at all.
blk.help.dountil = Runs the blocks inside, then checks the condition and repeats until it holds - the inside always runs at least once. blk.help.dountil = Runs the blocks inside, then checks the condition and repeats until it holds - the inside always runs at least once.
blk.help.wait = Pauses this script for some seconds, then it goes on with the next block (also inside loops). blk.help.wait = Pauses this script for some seconds, then it goes on with the next block (also inside loops).
@@ -1693,3 +1701,70 @@ tut.slap = Crookfinger: Ha! Did you hear that? Something is coming from the east
tut.obj6 = Defeat the two knights. tut.obj6 = Defeat the two knights.
tut.knights = Crookfinger: Two knights! Send your creatures with the attack flag (Orders) or strike them with lightning. tut.knights = Crookfinger: Two knights! Send your creatures with the attack flag (Orders) or strike them with lightning.
tut.win = Crookfinger: Well done, master! That is all there is to it. Well - almost. The campaign tells the rest. I'll put the kettle on. tut.win = Crookfinger: Well done, master! That is all there is to it. Well - almost. The campaign tells the rest. I'll put the kettle on.
# master talents
cat.master = Master
talent.title = Master talents
talent.points = Free talent points: {0}
talent.next = {0}/{1} experience to the next point
talent.all = Every talent is learned.
talent.builder = Builder
talent.warlord = Warlord
talent.warlock = Warlock
talent.merchant = Merchant
talent.builder.desc = Imps dig {0}% faster, rooms cost {1}% less.
talent.warlord.desc = Creatures hit {0}% harder and take {1}% less damage.
talent.warlock.desc = Mana flows {0}% faster, spells cost {1}% less.
talent.merchant.desc = Wages are {0}% lower, delivered treasure is worth {1}% more.
talent.none = Not learned yet.
talent.then = Next level: {0}
msg.talent_point = A new talent point! Spend it in the Master window.
msg.talent_learned = {0} level {1} learned.
# weather above ground
weather.fair = Fair
weather.rain = Rain
weather.fog = Fog
weather.storm = Storm
msg.weather_clear = The sky clears up above ground.
msg.weather_rain = It starts to rain above ground - fire will not catch.
msg.weather_fog = Fog rolls over the land - everyone outside sees less.
msg.weather_storm = A storm is brewing above ground! Lightning strikes units and trees.
msg.lightning_unit = Lightning struck: {0}!
# creature names
names.imp = Grubnik, Snot, Pickles, Scrabble, Dimple, Muck, Nibbles, Grot, Twitch, Smudge, Pebble, Wort, Scuttle, Fizzle, Gristle, Sniv, Burrow, Crumb, Dribble, Tinker
names.kobold = Yip, Skrag, Ratchet, Snagtooth, Kibble, Rusk, Nettle, Skiv, Gnash, Scrapper, Flint, Yark, Biter, Sprocket, Dreg, Skulk, Tatter, Grizz, Nab, Scabby
names.spider = Webbly, Itsy, Nibsy, Silkie, Creepkin, Tickle, Venna, Lacey, Spindle, Skitter, Fangsy, Mopsy, Widow, Tangles, Weaver, Pip, Gossamer, Twiddle, Snare, Muffet
names.mage = Bartholomew, Ignatius, Percival, Mordecai, Fennimore, Cornelius, Aldous, Thaddeus, Barnabus, Erasmus, Montague, Ambrose, Septimus, Horatio, Quentin, Lucan, Archibald, Florian, Zebulon, Crispin
names.ogre = Grunk, Thud, Bonk, Lump, Mudge, Klod, Hulk, Gorb, Dunk, Brog, Oaf, Plonk, Boulder, Grog, Mungo, Slab, Wump, Tonk, Duh, Gullet
names.jailer = Grimm, Lockjaw, Chains, Dredd, Morrow, Shackle, Vex, Bolt, Garrote, Stern, Rook, Hask, Iron, Grist, Clank, Mordo, Bleak, Snare, Kessel, Warden
names.priestess = Ember, Seraphine, Pyra, Ashlyn, Scarlet, Morwen, Vesper, Cinder, Ravenna, Lilith, Blaze, Isolde, Brimma, Nyx, Sable, Velvet, Flare, Ophelia, Hestia, Rowan
names.chainbreaker = Fetterbane, Ulfgar, Ironsoul, Brannoc, Kragmor, Valdrek, Thorn, Gorath, Unbound, Rurik, Torvald, Hagan, Durnok, Shatter, Brand, Wulfric, Korrun, Arvak, Stigr, Halvard
names.bat = Squeak, Flutter, Pippin, Nibs, Echo, Flap, Dusk, Zippy, Chirp, Fang, Twirl, Batsy, Mothy, Peep, Swoop, Dangle, Velvet, Tiddles, Squee, Blip
names.gargoyle = Granite, Slate, Basalt, Grimsby, Mortar, Cobble, Gravel, Flint, Grout, Rubble, Quartz, Pumice, Lintel, Gnarl, Stoneface, Dour, Gable, Spout, Chisel, Ledge
names.owl = Hootsworth, Sage, Quill, Barnaby, Minerva, Tobias, Hoots, Ollivant, Feathers, Wisp, Ponder, Elder, Mopsy, Noctua, Owlbert, Scroll, Dusk, Tawny, Professor, Ruffle
names.wyrm = Vermithrax, Ashmaw, Scorch, Calderon, Ignivar, Smoulder, Drakkar, Pyrrhus, Obsidian, Embermaw, Vulcanor, Searfang, Malgrath, Tyrax, Inferno, Brimstone, Kaldor, Fyrnak, Ragnor, Cindros
names.cogling = Gizmo, Sprocket, Widget, Ratchet, Bolt, Gearwin, Tinker, Doohickey, Spanner, Pinion, Clink, Fiddle, Rivet, Whirr, Cogsworth, Twiddle, Flange, Gadget, Ticktock, Nubbin
names.revenant = Morrow, Grieve, Ashen, Lament, Hollow, Mourn, Pallor, Sorrow, Wraithe, Dirge, Barrow, Gloam, Tomb, Weep, Vigil, Cairn, Shroud, Elegy, Dregs, Bleak
names.beast = Fuzzbert, Grizzle, Bruno, Shaggs, Chomper, Mossback, Bumble, Ursa, Burl, Crunch, Clicker, Scuttles, Bristle, Moss, Rumble, Bramble, Chitter, Grumbles, Hulkster, Snuffle
names.human = Gareth, Roland, Cedric, Aldric, Tristan, Edmund, Gawain, Lionel, Percy, Hugo, Bertram, Godfrey, Osric, Baldwin, Reginald, Wilfred, Ector, Gilbert, Rupert, Duncan
epithets.mid = Bonecrusher, Mudfoot, Ratbiter, Grimeface, Pebblechewer, Skullthumper, Stinkfist, Rustknuckle, Gutpuncher, Shinkicker, Mouldbeard, Dirtclaw, Snotslinger, Earchewer, Kneebreaker, Wormeater
epithets.high = Kingslayer, Doomhand, Dreadfang, Worldbreaker, Herobane, Stormfist, Nightterror, Soulreaper, Ironheart, Castlecrusher, Dragonbane, Hellstrider, Shadowlord, Bloodcrown, Grimtide, Starbreaker
name.epithet = {0} {1}
msg.creature_fallen = {0} has fallen. A memorial stone keeps the name.
tile.memorial = Memorial stone
tile.memorial_info = Here lies {0} ({1}, level {2}).
# hall of the fallen
hall.title = Hall of the fallen
hall.count = {0} fallen creatures remembered
hall.empty = No one has fallen yet. Long may it last.
hall.kills = {0} heroes slain
ov.fallen = Fallen ({0})
ov.living = Living
cause.slain = slain by: {0}
cause.lava = burned in lava
cause.water = drowned
cause.snare = caught in a snare
cause.lightning = struck by lightning
cause.fell = fell
+75
View File
@@ -264,6 +264,8 @@ val.medium = Medio
val.high = Alto val.high = Alto
val.normal = Normal val.normal = Normal
val.large = Grande val.large = Grande
val.small = Pequeño
val.auto_plain = Automático
# ---- key binding names # ---- key binding names
key.cam_forward = Cámara adelante key.cam_forward = Cámara adelante
@@ -1366,6 +1368,8 @@ blk.reveal = Revelar
blk.camera = Mostrar blk.camera = Mostrar
blk.gold = Oro + blk.gold = Oro +
blk.mana = Maná + blk.mana = Maná +
blk.weather = Clima en la superficie
blk.talentxp = Experiencia de talentos +
blk.if = Si blk.if = Si
blk.repeat = Repetir blk.repeat = Repetir
blk.trigger = Conmutar disparador blk.trigger = Conmutar disparador
@@ -1385,6 +1389,8 @@ blk.help.reveal = Levanta la niebla de guerra sobre una zona.
blk.help.camera = Mueve la cámara a una zona. blk.help.camera = Mueve la cámara a una zona.
blk.help.gold = Da (o quita) oro. blk.help.gold = Da (o quita) oro.
blk.help.mana = Da (o quita) maná. blk.help.mana = Da (o quita) maná.
blk.help.weather = Fija el clima en la superficie (despejado, lluvia, niebla o tormenta) durante tantos segundos; 0 lo mantiene hasta que otro bloque lo cambie.
blk.help.talentxp = Da al jugador experiencia para los talentos del maestro.
blk.help.if = Ejecuta los bloques de dentro solo si se cumple la condición; si no, los de «si no». blk.help.if = Ejecuta los bloques de dentro solo si se cumple la condición; si no, los de «si no».
blk.help.repeat = Ejecuta varias veces los bloques de dentro. blk.help.repeat = Ejecuta varias veces los bloques de dentro.
blk.help.trigger = Activa un disparador (puede volver a saltar) o lo desactiva. blk.help.trigger = Activa un disparador (puede volver a saltar) o lo desactiva.
@@ -1407,6 +1413,7 @@ blk.v.or = o
blk.v.not = no blk.v.not = no
blk.v.bool = Sí/no blk.v.bool = Sí/no
blk.v.eventkind = El causante es un blk.v.eventkind = El causante es un
blk.v.weatheris = El clima en la superficie es
blk.w_at = en blk.w_at = en
blk.w_by = en blk.w_by = en
blk.w_in = en blk.w_in = en
@@ -1463,6 +1470,7 @@ blk.until = Repetir hasta
blk.dountil = Repetir y luego: hasta blk.dountil = Repetir y luego: hasta
blk.wait = Esperar blk.wait = Esperar
blk.w_seconds = segundos blk.w_seconds = segundos
blk.w_for = durante
blk.help.until = Ejecuta los bloques de dentro una y otra vez hasta que se cumpla la condición; se comprueba antes de cada vuelta, así que si ya se cumple no se ejecuta nada. blk.help.until = Ejecuta los bloques de dentro una y otra vez hasta que se cumpla la condición; se comprueba antes de cada vuelta, así que si ya se cumple no se ejecuta nada.
blk.help.dountil = Ejecuta los bloques de dentro, luego comprueba la condición y repite hasta que se cumpla; el contenido se ejecuta al menos una vez. blk.help.dountil = Ejecuta los bloques de dentro, luego comprueba la condición y repite hasta que se cumpla; el contenido se ejecuta al menos una vez.
blk.help.wait = Detiene este script unos segundos y luego sigue con el siguiente bloque (también dentro de bucles). blk.help.wait = Detiene este script unos segundos y luego sigue con el siguiente bloque (también dentro de bucles).
@@ -1693,3 +1701,70 @@ tut.slap = Dedotuerto: ¡Ja! ¿Lo has oído? Algo viene del este. Prepárate: el
tut.obj6 = Derrota a los dos caballeros. tut.obj6 = Derrota a los dos caballeros.
tut.knights = Dedotuerto: ¡Dos caballeros! Envía a tus criaturas con la bandera de ataque (Órdenes) o fulmínalos con el rayo. tut.knights = Dedotuerto: ¡Dos caballeros! Envía a tus criaturas con la bandera de ataque (Órdenes) o fulmínalos con el rayo.
tut.win = Dedotuerto: ¡Bien hecho, amo! Eso es todo. Bueno, casi. La campaña cuenta el resto. Voy a poner el agua a hervir. tut.win = Dedotuerto: ¡Bien hecho, amo! Eso es todo. Bueno, casi. La campaña cuenta el resto. Voy a poner el agua a hervir.
# master talents
cat.master = Maestro
talent.title = Talentos del maestro
talent.points = Puntos de talento libres: {0}
talent.next = {0}/{1} de experiencia hasta el siguiente punto
talent.all = Todos los talentos están aprendidos.
talent.builder = Constructor
talent.warlord = Caudillo
talent.warlock = Brujo
talent.merchant = Mercader
talent.builder.desc = Los diablillos cavan un {0}% más rápido y las salas cuestan un {1}% menos.
talent.warlord.desc = Las criaturas golpean un {0}% más fuerte y reciben un {1}% menos de daño.
talent.warlock.desc = El maná fluye un {0}% más rápido y los hechizos cuestan un {1}% menos.
talent.merchant.desc = Los sueldos son un {0}% más bajos y el tesoro entregado vale un {1}% más.
talent.none = Aún no aprendido.
talent.then = Siguiente nivel: {0}
msg.talent_point = ¡Un nuevo punto de talento! Gástalo en la ventana del maestro.
msg.talent_learned = {0}: nivel {1} aprendido.
# weather above ground
weather.fair = Despejado
weather.rain = Lluvia
weather.fog = Niebla
weather.storm = Tormenta
msg.weather_clear = El cielo se despeja en la superficie.
msg.weather_rain = Empieza a llover en la superficie: el fuego no prende.
msg.weather_fog = La niebla cubre la tierra: todos ven menos al aire libre.
msg.weather_storm = ¡Se avecina una tormenta en la superficie! Los rayos alcanzan unidades y árboles.
msg.lightning_unit = ¡Un rayo alcanza a {0}!
# creature names
names.imp = Mugrín, Mocoso, Cascarrín, Pringue, Garbancito, Roñito, Pelusa, Zurullo, Tizón, Chafardín, Escarbín, Migaja, Chispín, Lodito, Gusarapo, Rascón, Pocholo, Terrón, Pipo, Zarpín
names.kobold = Gruñín, Raspa, Mordisco, Chirlo, Trapo, Tarugo, Zarpazo, Rufo, Pellizco, Garfio, Cardo, Chuzo, Zorrino, Pícaro, Ratero, Mellado, Rasguño, Guijarro, Cuchi, Tiznado
names.spider = Patitas, Telarín, Hilacha, Cosquillas, Ocho, Sedita, Tejedora, Pelusín, Brujilla, Enredo, Ovillo, Agujita, Trampita, Colmilla, Lunares, Retales, Bordadora, Pinchito, Sombrita, Telita
names.mage = Bartolomé, Ignacio, Hermenegildo, Anacleto, Fulgencio, Eustaquio, Leopoldo, Baltasar, Melquíades, Teodoro, Segismundo, Florencio, Casimiro, Gumersindo, Nicanor, Aurelio, Rigoberto, Cipriano, Marcelino, Wenceslao
names.ogre = Pedrusco, Ceporro, Zoquete, Mamotreto, Tocho, Bruto, Mazacote, Bolo, Morlaco, Pancho, Trompo, Tronco, Cachalote, Pataca, Mendrugo, Bombo, Gordinflón, Porrazo, Garrulo, Borrico
names.jailer = Cerrojo, Candado, Grilletes, Ceñudo, Sombrío, Mazmorro, Rejas, Torvo, Hierro, Cadenas, Severo, Aldaba, Garrote, Cancerbero, Llavero, Lúgubre, Hosco, Calabozo, Tranca, Argolla
names.priestess = Brasa, Ascua, Llama, Pira, Centella, Ceniza, Bermeja, Hoguera, Fulgor, Lumbre, Morgana, Serafina, Escarlata, Chispa, Ignea, Rubí, Ámbar, Fénix, Candela, Pavesa
names.chainbreaker = Rompecadenas, Ulfgar, Almahierro, Brannok, Kragmor, Valdrek, Espino, Gorath, Indómito, Rurik, Torvaldo, Hagan, Durnok, Quebranto, Tizona, Wulfrido, Korrún, Arvak, Libertador, Halvardo
names.bat = Chillido, Aletas, Pío, Mordi, Eco, Batí, Ocaso, Zumbi, Chirri, Colmillito, Revolo, Murci, Polilla, Piquito, Picado, Colgandero, Terciopelo, Nocturno, Chirrín, Zigzag
names.gargoyle = Granito, Pizarra, Basalto, Adoquín, Mortero, Guijarro, Grava, Pedernal, Cascote, Escombro, Cuarzo, Pómez, Cornisa, Gárgola, Carapiedra, Sillar, Frontón, Cantero, Cincel, Peñasco
names.owl = Sabiondo, Ulular, Pluma, Buhíto, Minerva, Tobías, Lechuzo, Sapiencio, Plumón, Sabio, Cavilón, Anciano, Mochuelo, Noctua, Búho, Pergamino, Ocaso, Cárabo, Profesor, Erudito
names.wyrm = Vermitrax, Fauceniza, Chamusco, Calderón, Ignívoro, Ascuamor, Drakkar, Pirro, Obsidiano, Brasamor, Vulcanor, Abrasador, Malgrath, Tyrax, Infierno, Azufre, Kaldor, Fyrnak, Ragnor, Cindros
names.cogling = Tuerquita, Engranaje, Tornillo, Trinquete, Perno, Piñón, Chirimbolo, Cachivache, Llavecita, Muelle, Tintín, Chisme, Remache, Zumbido, Manivela, Arandela, Bisagra, Artilugio, Tictac, Resorte
names.revenant = Lamento, Pesar, Ceniciento, Quebranto, Hueco, Luto, Lívido, Pena, Espectro, Endecha, Túmulo, Penumbra, Cripta, Llanto, Vigilia, Mortaja, Sudario, Elegía, Ánima, Desolado
names.beast = Greñas, Melenudo, Bruno, Pelambre, Zampón, Musgoso, Gruñón, Osezno, Matorral, Crujido, Cascarón, Patudo, Cerdas, Moho, Retumbo, Zarza, Chasquido, Rezongón, Mole, Hocicón
names.human = Rodrigo, Gonzalo, Álvaro, Fernando, Tristán, Sancho, Bermudo, Lope, Pelayo, Hugo, Beltrán, Godofredo, Ordoño, Balduino, Ramiro, Gutierre, Nuño, Gilberto, Rufino, Diego
epithets.mid = Rompehuesos, Pisabarro, Muerderratas, Caramugre, Mascapiedras, Machacacráneos, Puñoapestoso, Nudillorroñoso, Revientapanzas, Pateaespinillas, Barbamoho, Garramugre, Lanzamocos, Masticaorejas, Parterrodillas, Comegusanos
epithets.high = Matarreyes, Manofunesta, Colmillopavor, Rompemundos, Azotehéroes, Puñotormenta, Terrornocturno, Segadalmas, Corazóndehierro, Derribacastillos, Matadragones, Andainfiernos, Señorsombra, Coronasangre, Mareafunesta, Quiebrastros
name.epithet = {0} {1}
msg.creature_fallen = {0} ha caído. Una piedra conmemorativa guarda su nombre.
tile.memorial = Piedra conmemorativa
tile.memorial_info = Aquí yace {0} ({1}, nivel {2}).
# hall of the fallen
hall.title = Salón de los caídos
hall.count = Criaturas caídas en el recuerdo: {0}
hall.empty = Aún no ha caído nadie. Que siga así.
hall.kills = Héroes abatidos: {0}
ov.fallen = Caídos ({0})
ov.living = Vivos
cause.slain = muerte a manos de: {0}
cause.lava = muerte en lava
cause.water = ahogamiento
cause.snare = muerte en un cepo
cause.lightning = muerte por rayo
cause.fell = caída en combate
+75
View File
@@ -264,6 +264,8 @@ val.medium = Keski
val.high = Korkea val.high = Korkea
val.normal = Normaali val.normal = Normaali
val.large = Suuri val.large = Suuri
val.small = Pieni
val.auto_plain = Automaattinen
# ---- key binding names # ---- key binding names
key.cam_forward = Kamera eteen key.cam_forward = Kamera eteen
@@ -1374,6 +1376,8 @@ blk.reveal = Paljasta
blk.camera = Näytä blk.camera = Näytä
blk.gold = Kulta + blk.gold = Kulta +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Sää maan päällä
blk.talentxp = Kykykokemus +
blk.if = Jos blk.if = Jos
blk.repeat = Toista blk.repeat = Toista
blk.trigger = Vaihda laukaisin blk.trigger = Vaihda laukaisin
@@ -1393,6 +1397,8 @@ blk.help.reveal = Poistaa sodan sumun alueen yltä.
blk.help.camera = Siirtää kameran alueelle. blk.help.camera = Siirtää kameran alueelle.
blk.help.gold = Antaa (tai ottaa) kultaa. blk.help.gold = Antaa (tai ottaa) kultaa.
blk.help.mana = Antaa (tai ottaa) manaa. blk.help.mana = Antaa (tai ottaa) manaa.
blk.help.weather = Asettaa sään maan päällä (selkeää, sade, sumu tai myrsky) niin moneksi sekunniksi; 0 pitää sen, kunnes toinen palikka muuttaa sitä.
blk.help.talentxp = Antaa pelaajalle kokemusta mestarin kykyihin.
blk.help.if = Suorittaa sisällä olevat lohkot vain, jos ehto pätee, muuten "muuten"-kohdan lohkot. blk.help.if = Suorittaa sisällä olevat lohkot vain, jos ehto pätee, muuten "muuten"-kohdan lohkot.
blk.help.repeat = Suorittaa sisällä olevat lohkot useita kertoja. blk.help.repeat = Suorittaa sisällä olevat lohkot useita kertoja.
blk.help.trigger = Kytkee laukaisimen päälle (se voi laueta uudelleen) tai pois. blk.help.trigger = Kytkee laukaisimen päälle (se voi laueta uudelleen) tai pois.
@@ -1415,6 +1421,7 @@ blk.v.or = tai
blk.v.not = ei blk.v.not = ei
blk.v.bool = Kyllä/ei blk.v.bool = Kyllä/ei
blk.v.eventkind = Laukaisija on laji blk.v.eventkind = Laukaisija on laji
blk.v.weatheris = Sää maan päällä on
blk.w_at = paikka blk.w_at = paikka
blk.w_by = määrä blk.w_by = määrä
blk.w_in = alue blk.w_in = alue
@@ -1463,6 +1470,7 @@ blk.until = Toista kunnes
blk.dountil = Toista, sitten tarkista: kunnes blk.dountil = Toista, sitten tarkista: kunnes
blk.wait = Odota blk.wait = Odota
blk.w_seconds = sekuntia blk.w_seconds = sekuntia
blk.w_for = kesto
blk.help.until = Suorittaa sisällä olevia lohkoja yhä uudelleen, kunnes ehto pätee - tarkistetaan ennen jokaista kierrosta, joten jos ehto jo pätee, sisältöä ei suoriteta lainkaan. blk.help.until = Suorittaa sisällä olevia lohkoja yhä uudelleen, kunnes ehto pätee - tarkistetaan ennen jokaista kierrosta, joten jos ehto jo pätee, sisältöä ei suoriteta lainkaan.
blk.help.dountil = Suorittaa sisällä olevat lohkot, tarkistaa sitten ehdon ja toistaa, kunnes se pätee - sisältö suoritetaan aina vähintään kerran. blk.help.dountil = Suorittaa sisällä olevat lohkot, tarkistaa sitten ehdon ja toistaa, kunnes se pätee - sisältö suoritetaan aina vähintään kerran.
blk.help.wait = Pysäyttää tämän skriptin muutamaksi sekunniksi, sitten se jatkaa seuraavasta lohkosta (myös silmukoiden sisällä). blk.help.wait = Pysäyttää tämän skriptin muutamaksi sekunniksi, sitten se jatkaa seuraavasta lohkosta (myös silmukoiden sisällä).
@@ -1693,3 +1701,70 @@ tut.slap = Crookfinger: Ha! Kuulitko? Idästä tulee jotain. Valmistaudu: salama
tut.obj6 = Kukista kaksi ritaria. tut.obj6 = Kukista kaksi ritaria.
tut.knights = Crookfinger: Kaksi ritaria! Lähetä olentosi hyökkäyslipulla (Käskyt) tai iske heitä salamalla. tut.knights = Crookfinger: Kaksi ritaria! Lähetä olentosi hyökkäyslipulla (Käskyt) tai iske heitä salamalla.
tut.win = Crookfinger: Hyvin tehty, mestari! Siinä kaikki. No - melkein. Kampanja kertoo loput. Panen teeveden kiehumaan. tut.win = Crookfinger: Hyvin tehty, mestari! Siinä kaikki. No - melkein. Kampanja kertoo loput. Panen teeveden kiehumaan.
# master talents
cat.master = Mestari
talent.title = Mestarin kyvyt
talent.points = Vapaita kykypisteitä: {0}
talent.next = {0}/{1} kokemusta seuraavaan pisteeseen
talent.all = Kaikki kyvyt on opittu.
talent.builder = Rakentaja
talent.warlord = Sotaherra
talent.warlock = Noita
talent.merchant = Kauppias
talent.builder.desc = Pirut kaivavat {0}% nopeammin, huoneet maksavat {1}% vähemmän.
talent.warlord.desc = Olennot lyövät {0}% kovempaa ja ottavat {1}% vähemmän vahinkoa.
talent.warlock.desc = Mana virtaa {0}% nopeammin, loitsut maksavat {1}% vähemmän.
talent.merchant.desc = Palkat ovat {0}% pienemmät, toimitettu aarre on {1}% arvokkaampi.
talent.none = Ei vielä opittu.
talent.then = Seuraava taso: {0}
msg.talent_point = Uusi kykypiste! Käytä se mestarin ikkunassa.
msg.talent_learned = {0}: taso {1} opittu.
# weather above ground
weather.fair = Selkeää
weather.rain = Sade
weather.fog = Sumu
weather.storm = Myrsky
msg.weather_clear = Maan päällä taivas selkenee.
msg.weather_rain = Maan päällä alkaa sataa - tuli ei syty.
msg.weather_fog = Sumu leviää maan ylle - ulkona kaikki näkevät vähemmän.
msg.weather_storm = Maan päällä nousee myrsky! Salamat iskevät yksiköihin ja puihin.
msg.lightning_unit = Salama iski: {0}!
# creature names
names.imp = Muru, Räkä, Kaivuri, Kura, Nöyhtä, Tönö, Myyrä, Lörppö, Nappula, Raapija, Möykky, Rapa, Sotku, Tuhru, Pikkis, Kuokka, Kärsä, Pökkelö, Läikkä, Moska
names.kobold = Räksy, Nipistäjä, Hammas, Rupi, Haukku, Ruoste, Vips, Purija, Kitas, Rotta, Riepu, Piikki, Kynsi, Rähinä, Kivinen, Kikatus, Tönäisy, Kolho, Nirso, Naarmu
names.spider = Kipitys, Villajalka, Hämis, Kutitus, Verkkonen, Kahdeksikko, Lanka, Kammotus, Näppis, Silkki, Kutoja, Hyppy, Nukkajalka, Tekla, Rulla, Näpsä, Kerä, Tahmis, Vipellys, Neulanen
names.mage = Bartolomeus, Ignatius, Leopold, Teobald, Adalbert, Hieronymus, Valdemar, Gottfrid, Kornelius, Ottokar, Florentin, Engelbert, Pankratius, Rupertus, Konstantin, Eusebius, Severin, Barnabas, Arkibald, Vitus
names.ogre = Möhkäle, Kökkö, Pläjäys, Mötikkä, Tömps, Tollo, Jöhkäle, Mörkö, Tallaaja, Puupää, Molskis, Pulla, Mahakas, Ähky, Nuija, Härkä, Rymy, Klöntti, Jytky, Rötkö
names.jailer = Synkkä, Salpa, Kahle, Lukko, Tyrmä, Rautaparta, Rengas, Pultti, Ankara, Kalteri, Äreä, Koukku, Silmukka, Avain, Kolkko, Kilinä, Vartija, Tyrmänvahti, Häkki, Karvas
names.priestess = Hehku, Liekki, Tuhka, Serafiina, Roihu, Tulia, Morwen, Brynhild, Ignia, Kipinä, Purppura, Säihky, Korppi, Lilith, Taula, Isolde, Palo, Nyx, Vesper, Loimu
names.chainbreaker = Kahlerikko, Ulfgar, Rautasielu, Brannok, Kragmor, Valdrek, Oka, Gorath, Kahleeton, Rurik, Torvald, Hagen, Durnok, Sirpale, Lieska, Ulfrik, Korrun, Arvak, Sigurd, Halvard
names.bat = Piip, Lepatus, Vinku, Suhina, Kaiku, Läpsy, Hämärä, Vihellys, Sirkutus, Hampaus, Pyörre, Lepakko, Koi, Piipero, Suhis, Roikku, Sametti, Livahdus, Kiljaisu, Salama
names.gargoyle = Graniitti, Liuske, Basaltti, Kivikasvo, Laasti, Pikkukivi, Sora, Piikivi, Sauma, Raunio, Kvartsi, Hohkakivi, Reunus, Känsä, Kiviparta, Nyrpeä, Pääty, Vesisylki, Taltta, Sokkeli
names.owl = Huuhkaja, Viisastelija, Sulkakynä, Bartoldus, Minerva, Tobias, Huhuu, Pöllölä, Sulkanen, Huokaus, Pohdiskelija, Vanhin, Lehtopöllö, Noctua, Pöllövaari, Pergamentti, Iltahämärä, Yöpöllö, Professori, Untuva
names.wyrm = Vermithrax, Tuhkakita, Kärventäjä, Calderon, Ignivar, Hehkusarvi, Drakkar, Pyrrhus, Obsidiaani, Hiilikita, Vulkanor, Polttohammas, Malgrath, Tyrax, Inferno, Rikki, Kaldor, Fafnir, Ragnor, Hiillos
names.cogling = Ratas, Hammasratas, Ruuvi, Räikkä, Pultti, Vaihde, Nikkari, Vempain, Avainen, Hammaspyörä, Kilkatus, Näpertäjä, Niitti, Surina, Kampi, Hyrrä, Laippa, Naksutin, Tiktak, Nippa
names.revenant = Aamunkoi, Kauna, Harmaa, Valitus, Ontto, Suru, Kalvas, Murhe, Varjo, Multa, Kalmo, Hämy, Krypta, Kaiho, Valvoja, Kumpu, Käärinliina, Elegia, Sakka, Autio
names.beast = Takku, Karvakasa, Bruno, Pörrö, Mussutus, Sammalselkä, Murina, Otso, Kontio, Rouske, Naksu, Kömpi, Harjas, Sammal, Jyrinä, Ohdake, Sirinä, Nurina, Nalle, Nuuskija
names.human = Gerhard, Roland, Konrad, Aleksander, Tristan, Eskil, Gawain, Lancelot, Paavali, Hugo, Bertil, Gottfrid, Eerik, Sigfrid, Ragnar, Vilhelm, Kustaa, Gilbert, Rasmus, Birger
epithets.mid = Luunmurskaaja, Mutajalka, Rotanpurija, Likanaama, Kivenjyrsijä, Kallonkopauttaja, Haisunyrkki, Ruostenivel, Mahanmätkijä, Säärenpotkija, Homeparta, Likakynsi, Räkälinko, Korvanjyrsijä, Polvenmurtaja, Madonsyöjä
epithets.high = Kuninkaansurma, Tuomionkoura, Kauhuhammas, Maailmanmurskaaja, Sankarinsurma, Myrskynyrkki, Yönkauhu, Sielunniittäjä, Rautasydän, Linnanmurskaaja, Lohikäärmeensurma, Helvetinkulkija, Varjoruhtinas, Verikruunu, Tuomiontulva, Tähdenmurskaaja
name.epithet = {0} {1}
msg.creature_fallen = {0} on kaatunut. Muistokivi säilyttää nimen.
tile.memorial = Muistokivi
tile.memorial_info = Tässä lepää {0} ({1}, taso {2}).
# hall of the fallen
hall.title = Kaatuneiden sali
hall.count = Muistettuja kaatuneita olentoja: {0}
hall.empty = Kukaan ei ole vielä kaatunut. Kauan näin jatkukoon.
hall.kills = Surmattuja sankareita: {0}
ov.fallen = Kaatuneet ({0})
ov.living = Elävät
cause.slain = surmaaja: {0}
cause.lava = paloi laavassa
cause.water = hukkui
cause.snare = astui ansaan
cause.lightning = salama iski
cause.fell = kaatui
+75
View File
@@ -264,6 +264,8 @@ val.medium = Srednje
val.high = Visoko val.high = Visoko
val.normal = Normalno val.normal = Normalno
val.large = Veliko val.large = Veliko
val.small = Malo
val.auto_plain = Automatski
# ---- key binding names # ---- key binding names
key.cam_forward = Kamera naprijed key.cam_forward = Kamera naprijed
@@ -1374,6 +1376,8 @@ blk.reveal = Otkrij
blk.camera = Pokaži blk.camera = Pokaži
blk.gold = Zlato + blk.gold = Zlato +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Vrijeme na površini
blk.talentxp = Iskustvo talenata +
blk.if = Ako blk.if = Ako
blk.repeat = Ponovi blk.repeat = Ponovi
blk.trigger = Prebaci okidač blk.trigger = Prebaci okidač
@@ -1393,6 +1397,8 @@ blk.help.reveal = Podiže maglu rata nad zonom.
blk.help.camera = Pomiče kameru na zonu. blk.help.camera = Pomiče kameru na zonu.
blk.help.gold = Daje (ili oduzima) zlato. blk.help.gold = Daje (ili oduzima) zlato.
blk.help.mana = Daje (ili oduzima) manu. blk.help.mana = Daje (ili oduzima) manu.
blk.help.weather = Postavlja vrijeme na površini (vedro, kiša, magla ili oluja) na toliko sekundi; 0 ga zadržava dok ga drugi blok ne promijeni.
blk.help.talentxp = Daje igraču iskustvo za gospodareve talente.
blk.help.if = Pokreće unutarnje blokove samo ako uvjet vrijedi, inače one pod "inače". blk.help.if = Pokreće unutarnje blokove samo ako uvjet vrijedi, inače one pod "inače".
blk.help.repeat = Pokreće unutarnje blokove nekoliko puta. blk.help.repeat = Pokreće unutarnje blokove nekoliko puta.
blk.help.trigger = Uključuje okidač (može se ponovo aktivirati) ili ga isključuje. blk.help.trigger = Uključuje okidač (može se ponovo aktivirati) ili ga isključuje.
@@ -1415,6 +1421,7 @@ blk.v.or = ili
blk.v.not = ne blk.v.not = ne
blk.v.bool = Da/ne blk.v.bool = Da/ne
blk.v.eventkind = Jedinica okidača je blk.v.eventkind = Jedinica okidača je
blk.v.weatheris = Vrijeme na površini je
blk.w_at = na blk.w_at = na
blk.w_by = za blk.w_by = za
blk.w_in = u blk.w_in = u
@@ -1463,6 +1470,7 @@ blk.until = Ponavljaj dok
blk.dountil = Ponovi, zatim provjeri: dok blk.dountil = Ponovi, zatim provjeri: dok
blk.wait = Čekaj blk.wait = Čekaj
blk.w_seconds = sekundi blk.w_seconds = sekundi
blk.w_for = na
blk.help.until = Pokreće unutarnje blokove iznova i iznova dok uvjet ne vrijedi - provjerava se prije svakog prolaza, pa ako već vrijedi, unutrašnjost se uopće ne pokreće. blk.help.until = Pokreće unutarnje blokove iznova i iznova dok uvjet ne vrijedi - provjerava se prije svakog prolaza, pa ako već vrijedi, unutrašnjost se uopće ne pokreće.
blk.help.dountil = Pokreće unutarnje blokove, zatim provjerava uvjet i ponavlja dok ne vrijedi - unutrašnjost se uvijek pokrene barem jednom. blk.help.dountil = Pokreće unutarnje blokove, zatim provjerava uvjet i ponavlja dok ne vrijedi - unutrašnjost se uvijek pokrene barem jednom.
blk.help.wait = Pauzira ovu skriptu nekoliko sekundi, a zatim nastavlja sljedećim blokom (i unutar petlji). blk.help.wait = Pauzira ovu skriptu nekoliko sekundi, a zatim nastavlja sljedećim blokom (i unutar petlji).
@@ -1693,3 +1701,70 @@ tut.slap = Krivoprst: Ha! Jesi li čuo? Nešto dolazi s istoka. Pripremi se: mun
tut.obj6 = Porazi dva viteza. tut.obj6 = Porazi dva viteza.
tut.knights = Krivoprst: Dva viteza! Pošalji stvorenja zastavom napada (Naredbe) ili ih udari munjom. tut.knights = Krivoprst: Dva viteza! Pošalji stvorenja zastavom napada (Naredbe) ili ih udari munjom.
tut.win = Krivoprst: Bravo, gospodaru! To je sve. Pa - skoro. Ostatak će ispričati kampanja. Idem staviti vodu za čaj. tut.win = Krivoprst: Bravo, gospodaru! To je sve. Pa - skoro. Ostatak će ispričati kampanja. Idem staviti vodu za čaj.
# master talents
cat.master = Gospodar
talent.title = Gospodarevi talenti
talent.points = Slobodni bodovi talenta: {0}
talent.next = {0}/{1} iskustva do sljedećeg boda
talent.all = Svi talenti su naučeni.
talent.builder = Graditelj
talent.warlord = Vojskovođa
talent.warlock = Vještac
talent.merchant = Trgovac
talent.builder.desc = Vražići kopaju {0}% brže, sobe koštaju {1}% manje.
talent.warlord.desc = Stvorenja udaraju {0}% jače i primaju {1}% manje štete.
talent.warlock.desc = Mana teče {0}% brže, čarolije koštaju {1}% manje.
talent.merchant.desc = Plaće su {0}% niže, dostavljeno blago vrijedi {1}% više.
talent.none = Još nije naučeno.
talent.then = Sljedeća razina: {0}
msg.talent_point = Novi bod talenta! Potroši ga u prozoru gospodara.
msg.talent_learned = {0}: naučena razina {1}.
# weather above ground
weather.fair = Vedro
weather.rain = Kiša
weather.fog = Magla
weather.storm = Oluja
msg.weather_clear = Na površini se razvedrava.
msg.weather_rain = Na površini počinje kiša - vatra se ne hvata.
msg.weather_fog = Magla se spušta nad zemlju - vani svi vide manje.
msg.weather_storm = Na površini se sprema oluja! Munje pogađaju jedinice i stabla.
msg.lightning_unit = Munja je pogodila: {0}!
# creature names
names.imp = Mrvica, Šmrkljo, Kopko, Blatko, Prljko, Čupko, Rovko, Gnjecko, Smrdko, Grebko, Kvrga, Brlja, Mazalo, Packo, Grudica, Lopatko, Njuško, Kopuša, Mrljko, Šljampa
names.kobold = Kevko, Štipko, Zubonja, Šugo, Grizo, Hrđo, Fićo, Ugrizić, Zjalo, Štakor, Krpo, Bodljo, Kandžo, Gnjavko, Korijen, Kikotko, Gurko, Zarez, Krcko, Ogrebo
names.spider = Gmizalica, Vunko, Paučić, Škakljo, Mrežica, Osmonoga, Nitkica, Jezica, Prčko, Svilenka, Tkalja, Skokić, Dlakavko, Tekla, Kalemko, Hvatko, Klupko, Ljepko, Vrtko, Iglica
names.mage = Bartolomej, Ignacije, Leopold, Teobald, Adalbert, Jeronim, Valdemar, Gotfrid, Kornelije, Otokar, Florijan, Engelbert, Pankracije, Ruprecht, Konstantin, Euzebije, Severin, Barnaba, Arčibald, Vitomir
names.ogre = Gromo, Klada, Bumbo, Grudo, Tupko, Glupko, Tresko, Batina, Gazilo, Tikvan, Pljus, Debeljko, Buhto, Trbonja, Toljaga, Volina, Tutnjo, Kvrgo, Bubanj, Gorbo
names.jailer = Mrki, Zasun, Okov, Lokot, Tamnica, Željezobrad, Karika, Svornjak, Strogi, Rešetko, Namrgođen, Kuka, Omča, Ključar, Mračni, Zvekan, Klada, Stražar, Kavez, Gorki
names.priestess = Žar, Plamena, Pepela, Serafina, Buktinja, Vatrena, Morana, Brunhilda, Ignija, Iskra, Grimizna, Žarka, Gavranka, Lilit, Trud, Izolda, Ognjena, Nyx, Večernja, Plamenka
names.chainbreaker = Lomilanac, Ulfgar, Željezduh, Branok, Kragmor, Valdrek, Trn, Gorath, Nesputani, Rurik, Torvald, Hagen, Durnok, Krhotina, Plamen, Vukoslav, Korun, Arvak, Oslobodilac, Halvard
names.bat = Pijuk, Lepršo, Ciko, Šušur, Jeka, Mahalica, Sumrak, Zviždo, Cvrčko, Zubić, Vrtlog, Šišmišić, Moljac, Pijukić, Šušo, Visko, Baršun, Šmugo, Ciča, Munja
names.gargoyle = Granit, Škriljac, Bazalt, Kamenko, Malter, Šljunko, Tucanik, Kremen, Fuga, Ruševina, Kvarc, Plovućac, Vijenac, Kvrgo, Kamenobrad, Mrzovolja, Zabat, Rigalo, Dlijeto, Postolje
names.owl = Ćuk, Mudrica, Pero, Bartold, Minerva, Tobija, Huhu, Sovica, Perić, Uzdah, Mislilac, Starješina, Jejina, Noctua, Sovoslav, Pergamena, Suton, Ušara, Profesor, Paperje
names.wyrm = Vermitraks, Pepeljavi, Palež, Kalderon, Ignivar, Žarorog, Drakar, Pir, Opsidijan, Žaroust, Vulkanor, Palizub, Malgrat, Tiraks, Inferno, Sumpor, Kaldor, Fafnir, Ragnor, Ugarak
names.cogling = Zupčanik, Kotačić, Vijak, Čegrtaljka, Zakovica, Prijenos, Majstorko, Spravica, Ključić, Pogon, Zveckalo, Petljo, Klinčić, Zujo, Ručica, Zvrk, Prirubnica, Škljoc, Tiktak, Navojko
names.revenant = Sjeta, Gorčina, Pepeljko, Jauk, Šupalj, Tuga, Blijedi, Jad, Sjena, Prah, Grobar, Sumrak, Kripta, Čežnja, Bdijenje, Humak, Pokrov, Elegija, Talog, Pustoš
names.beast = Čupavko, Medo, Bruno, Rundo, Mljacko, Mahovinko, Mrmljo, Medvjedić, Grmko, Hrsko, Škljocko, Gmizo, Čekinjo, Mahovina, Tutanj, Kupina, Cvrčo, Gunđalo, Medonja, Njuškalo
names.human = Gerard, Roland, Konrad, Albert, Tristan, Eckhart, Gavin, Lancelot, Parsifal, Hugo, Bertram, Gotfrid, Ortvin, Balduin, Rajnold, Vilfrid, Dietrich, Gilbert, Ruprecht, Dragutin
epithets.mid = Lomikost, Blatonog, Štakorogriz, Prljolica, Kamenožder, Lubanjolom, Smrdošaka, Hrđozglob, Trbuhoboj, Goljenoboj, Pljesnobrad, Prljokandža, Šmrkljobacač, Uhogriz, Koljenolom, Crvožder
epithets.high = Kraljoubica, Kobošaka, Strahozub, Svjetolom, Junakomor, Olujošaka, Noćnostrah, Dušožetelac, Željezosrce, Gradolom, Zmajoubica, Paklohod, Sjenovladar, Krvokrun, Kobnoplima, Zvjezdolom
name.epithet = {0} {1}
msg.creature_fallen = Palo je tvoje stvorenje: {0}. Spomen-kamen čuva ime.
tile.memorial = Spomen-kamen
tile.memorial_info = Ovdje počiva {0} ({1}, razina {2}).
# hall of the fallen
hall.title = Dvorana palih
hall.count = Palih stvorenja u sjećanju: {0}
hall.empty = Još nitko nije pao. Neka tako i ostane.
hall.kills = Ubijenih junaka: {0}
ov.fallen = Pali ({0})
ov.living = Živi
cause.slain = ubojica: {0}
cause.lava = smrt u lavi
cause.water = utapanje
cause.snare = smrt u zamci
cause.lightning = udar munje
cause.fell = pogibija u boju
+75
View File
@@ -264,6 +264,8 @@ val.medium = Közepes
val.high = Magas val.high = Magas
val.normal = Normál val.normal = Normál
val.large = Nagy val.large = Nagy
val.small = Kicsi
val.auto_plain = Automatikus
# ---- key binding names # ---- key binding names
key.cam_forward = Kamera előre key.cam_forward = Kamera előre
@@ -1374,6 +1376,8 @@ blk.reveal = Feltárás
blk.camera = Mutat blk.camera = Mutat
blk.gold = Arany + blk.gold = Arany +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Időjárás a felszínen
blk.talentxp = Tehetség-tapasztalat +
blk.if = Ha blk.if = Ha
blk.repeat = Ismétlés blk.repeat = Ismétlés
blk.trigger = Indító be/ki blk.trigger = Indító be/ki
@@ -1393,6 +1397,8 @@ blk.help.reveal = Eloszlatja a háború ködét egy zóna fölött.
blk.help.camera = A kamerát egy zónához mozgatja. blk.help.camera = A kamerát egy zónához mozgatja.
blk.help.gold = Aranyat ad (vagy vesz el). blk.help.gold = Aranyat ad (vagy vesz el).
blk.help.mana = Manát ad (vagy vesz el). blk.help.mana = Manát ad (vagy vesz el).
blk.help.weather = Beállítja az időjárást a felszínen (derült, eső, köd vagy vihar) ennyi másodpercre; 0 esetén megmarad, amíg egy másik blokk meg nem változtatja.
blk.help.talentxp = Tapasztalatot ad a játékosnak a mesteri tehetségekhez.
blk.help.if = A benne lévő blokkokat csak akkor futtatja, ha a feltétel teljesül, különben a "különben" alattiakat. blk.help.if = A benne lévő blokkokat csak akkor futtatja, ha a feltétel teljesül, különben a "különben" alattiakat.
blk.help.repeat = A benne lévő blokkokat többször lefuttatja. blk.help.repeat = A benne lévő blokkokat többször lefuttatja.
blk.help.trigger = Egy indítót bekapcsol (újra elsülhet) vagy kikapcsol. blk.help.trigger = Egy indítót bekapcsol (újra elsülhet) vagy kikapcsol.
@@ -1415,6 +1421,7 @@ blk.v.or = vagy
blk.v.not = nem blk.v.not = nem
blk.v.bool = Igen/nem blk.v.bool = Igen/nem
blk.v.eventkind = Indító egység fajtája blk.v.eventkind = Indító egység fajtája
blk.v.weatheris = Az időjárás a felszínen
blk.w_at = itt: blk.w_at = itt:
blk.w_by = ennyivel: blk.w_by = ennyivel:
blk.w_in = itt: blk.w_in = itt:
@@ -1463,6 +1470,7 @@ blk.until = Ismételd, amíg
blk.dountil = Ismételd, majd ellenőrizd: amíg blk.dountil = Ismételd, majd ellenőrizd: amíg
blk.wait = Várj blk.wait = Várj
blk.w_seconds = másodperc blk.w_seconds = másodperc
blk.w_for = időtartam:
blk.help.until = A benne lévő blokkokat újra meg újra lefuttatja, amíg a feltétel nem teljesül - minden futás előtt ellenőrzi, így ha már teljesül, a belseje egyszer sem fut le. blk.help.until = A benne lévő blokkokat újra meg újra lefuttatja, amíg a feltétel nem teljesül - minden futás előtt ellenőrzi, így ha már teljesül, a belseje egyszer sem fut le.
blk.help.dountil = Lefuttatja a benne lévő blokkokat, majd ellenőrzi a feltételt, és addig ismétel, amíg nem teljesül - a belseje legalább egyszer mindig lefut. blk.help.dountil = Lefuttatja a benne lévő blokkokat, majd ellenőrzi a feltételt, és addig ismétel, amíg nem teljesül - a belseje legalább egyszer mindig lefut.
blk.help.wait = Néhány másodpercre szünetelteti ezt a szkriptet, aztán a következő blokkal folytatja (ciklusokon belül is). blk.help.wait = Néhány másodpercre szünetelteti ezt a szkriptet, aztán a következő blokkal folytatja (ciklusokon belül is).
@@ -1693,3 +1701,70 @@ tut.slap = Crookfinger: Ha! Hallottad? Valami jön keletről. Készülj: a vill
tut.obj6 = Győzd le a két lovagot. tut.obj6 = Győzd le a két lovagot.
tut.knights = Crookfinger: Két lovag! Küldd rájuk a lényeidet a támadózászlóval (Parancs), vagy sújtsd őket villámmal. tut.knights = Crookfinger: Két lovag! Küldd rájuk a lényeidet a támadózászlóval (Parancs), vagy sújtsd őket villámmal.
tut.win = Crookfinger: Szép munka, mester! Ennyi az egész. Nos - majdnem. A többit elmondja a hadjárat. Felteszem a teavizet. tut.win = Crookfinger: Szép munka, mester! Ennyi az egész. Nos - majdnem. A többit elmondja a hadjárat. Felteszem a teavizet.
# master talents
cat.master = Mester
talent.title = Mesteri tehetségek
talent.points = Szabad tehetségpontok: {0}
talent.next = {0}/{1} tapasztalat a következő pontig
talent.all = Minden tehetség megtanulva.
talent.builder = Építész
talent.warlord = Hadúr
talent.warlock = Boszorkánymester
talent.merchant = Kereskedő
talent.builder.desc = A manók {0}%-kal gyorsabban ásnak, a szobák {1}%-kal olcsóbbak.
talent.warlord.desc = A lények {0}%-kal erősebben ütnek, és {1}%-kal kevesebb sebzést kapnak.
talent.warlock.desc = A mana {0}%-kal gyorsabban áramlik, a varázslatok {1}%-kal olcsóbbak.
talent.merchant.desc = A bérek {0}%-kal alacsonyabbak, a leadott kincs {1}%-kal többet ér.
talent.none = Még nincs megtanulva.
talent.then = Következő szint: {0}
msg.talent_point = Új tehetségpont! Költsd el a mester ablakában.
msg.talent_learned = {0}: {1}. szint megtanulva.
# weather above ground
weather.fair = Derült
weather.rain = Eső
weather.fog = Köd
weather.storm = Vihar
msg.weather_clear = A felszínen kitisztul az ég.
msg.weather_rain = A felszínen esni kezd - a tűz nem kap lángra.
msg.weather_fog = Köd ereszkedik a tájra - odakint mindenki kevesebbet lát.
msg.weather_storm = A felszínen vihar készül! Villámok csapnak egységekbe és fákba.
msg.lightning_unit = Villám sújtotta: {0}!
# creature names
names.imp = Morzsa, Takony, Ásóka, Sarasló, Kosz, Pöttöm, Turkáló, Maszat, Csipa, Kaparó, Rög, Pacni, Dagonya, Piszok, Göröngy, Lapátka, Szutyok, Mütyür, Paca, Kukac
names.kobold = Csahos, Csípő, Fogas, Rühes, Harapós, Rozsdás, Suhanc, Marcangó, Bámész, Patkány, Rongyos, Tüske, Karmos, Kötekedő, Gyökér, Vihogó, Lökdöső, Csorba, Reccs, Karcos
names.spider = Mászka, Gyapjúláb, Pókocska, Csiklandó, Hálóka, Nyolclábú, Fonalka, Borzi, Matató, Selyemke, Szövőke, Ugri, Bolyhos, Tekla, Orsóka, Kapkodó, Gombolyag, Ragacs, Pörgő, Tűcske
names.mage = Bertalan, Ignác, Lipót, Tivadar, Adalbert, Jeromos, Valdemár, Gotfrid, Kornél, Otokár, Flórián, Engelbert, Pongrác, Ruprecht, Konstantin, Özséb, Szilveszter, Barnabás, Archibald, Bonifác
names.ogre = Döbrögi, Tuskó, Bumm, Szikla, Tökfej, Fatuskó, Puffancs, Püfölő, Dübörgő, Fafej, Pottyan, Pocak, Bunkó, Hasas, Dorong, Ökör, Robaj, Darab, Dob, Gorbó
names.jailer = Komor, Retesz, Bilincs, Lakat, Tömlöc, Vasszakáll, Karika, Csavar, Szigorú, Rácsos, Mogorva, Kampó, Hurok, Kulcsár, Sötét, Csörgő, Kaloda, Porkoláb, Kalitka, Keserű
names.priestess = Parázs, Lángi, Hamvas, Szeráfina, Máglya, Tüzes, Morvenna, Brünhilda, Ignia, Szikra, Bíbor, Zsarátnok, Hollóka, Lilit, Tapló, Izolda, Lobogó, Nyx, Esthajnal, Lángocska
names.chainbreaker = Láncszaggató, Ulfgár, Vaslélek, Brannok, Kragmor, Valdrek, Tövis, Gorath, Kötetlen, Rurik, Torvald, Hágen, Durnok, Szilánk, Lángoló, Farkasvér, Korrun, Arvak, Szabadító, Halvard
names.bat = Cincogó, Rebbenő, Pitty, Suhogó, Visszhang, Csapkodó, Alkony, Fütyülő, Ciripelő, Fogacska, Örvény, Denevérke, Moly, Pittyegő, Suhi, Lógó, Bársony, Surranó, Visító, Villám
names.gargoyle = Gránit, Pala, Bazalt, Kőarc, Habarcs, Kavics, Murva, Kovakő, Fuga, Rom, Kvarc, Horzsakő, Párkány, Bütyök, Kőszakáll, Morcos, Orom, Vízköpő, Véső, Lábazat
names.owl = Uhu, Okoska, Lúdtoll, Bartold, Minerva, Tóbiás, Huhú, Baglyocska, Tollacska, Sóhaj, Tűnődő, Öregúr, Kuvik, Noctua, Bagolybert, Pergamen, Szürkület, Macskabagoly, Professzor, Pihe
names.wyrm = Vermithrax, Hamutorok, Perzselő, Kalderon, Ignivar, Izzószarv, Drakkar, Pürrhosz, Obszidián, Parázsszáj, Vulkanor, Égetőfog, Malgrath, Tyrax, Infernó, Kénkő, Kaldor, Fafnir, Ragnor, Zsarát
names.cogling = Fogaskerék, Kerekecske, Csavarka, Racsni, Szegecs, Áttétel, Ezermester, Kütyü, Kulcsocska, Lendkerék, Csilingelő, Piszmogó, Anyácska, Zümmögő, Forgattyú, Búgócsiga, Karima, Kattanó, Tiktak, Csavarhúzó
names.revenant = Bánat, Keserv, Hamuszürke, Jajszó, Üresség, Gyász, Sápadt, Búbánat, Árny, Por, Sírásó, Homály, Kripta, Sóvárgás, Virrasztó, Halom, Szemfedő, Elégia, Üledék, Pusztaság
names.beast = Bozont, Mackó, Brúnó, Csapzott, Csámcsogó, Mohahát, Morgó, Medvebocs, Bokor, Ropogó, Kattogó, Mászó, Sörte, Moha, Dörmögő, Szeder, Ciripelő, Zsörtölő, Brummogó, Szimatoló
names.human = Gellért, Roland, Konrád, Albert, Trisztán, Ekhárt, Gawain, Lancelot, Parszifál, Hugó, Bertrám, Gotfrid, Örs, Baldvin, Rajnald, Vilfrid, Detre, Gilbert, Rupert, Bulcsú
epithets.mid = Csonttörő, Sárlábú, Patkányrágó, Koszosképű, Kavicsrágó, Koponyapüfölő, Büdösöklű, Rozsdabütykű, Hasbavágó, Sípcsontrúgó, Penészszakáll, Koszkarmú, Takonyhajító, Fülrágó, Térdtörő, Kukacevő
epithets.high = Királyölő, Végzetkéz, Rémfog, Világromboló, Hősveszejtő, Viharököl, Éjrém, Lélekarató, Vasszív, Várromboló, Sárkányölő, Pokoljáró, Árnyúr, Vérkoronás, Vészár, Csillagtörő
name.epithet = {1} {0}
msg.creature_fallen = Elesett: {0}. Egy emlékkő őrzi a nevét.
tile.memorial = Emlékkő
tile.memorial_info = Itt nyugszik {0} ({1}, szint: {2}).
# hall of the fallen
hall.title = Az elesettek csarnoka
hall.count = {0} elesett lényre emlékezünk
hall.empty = Még senki sem esett el. Így maradjon sokáig.
hall.kills = Legyőzött hősök: {0}
ov.fallen = Elesettek ({0})
ov.living = Élők
cause.slain = megölte: {0}
cause.lava = lávában égett
cause.water = vízbe fulladt
cause.snare = csapdába lépett
cause.lightning = villám sújtotta
cause.fell = elesett
+75
View File
@@ -264,6 +264,8 @@ val.medium = Media
val.high = Alta val.high = Alta
val.normal = Normale val.normal = Normale
val.large = Grande val.large = Grande
val.small = Piccolo
val.auto_plain = Automatico
# ---- key binding names # ---- key binding names
key.cam_forward = Camera avanti key.cam_forward = Camera avanti
@@ -1366,6 +1368,8 @@ blk.reveal = Rivela
blk.camera = Mostra blk.camera = Mostra
blk.gold = Oro + blk.gold = Oro +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Meteo in superficie
blk.talentxp = Esperienza talenti +
blk.if = Se blk.if = Se
blk.repeat = Ripeti blk.repeat = Ripeti
blk.trigger = Commuta trigger blk.trigger = Commuta trigger
@@ -1385,6 +1389,8 @@ blk.help.reveal = Solleva la nebbia di guerra su una zona.
blk.help.camera = Sposta la telecamera su una zona. blk.help.camera = Sposta la telecamera su una zona.
blk.help.gold = Dà (o toglie) oro. blk.help.gold = Dà (o toglie) oro.
blk.help.mana = Dà (o toglie) mana. blk.help.mana = Dà (o toglie) mana.
blk.help.weather = Imposta il meteo in superficie (sereno, pioggia, nebbia o tempesta) per tanti secondi; 0 lo mantiene finché un altro blocco non lo cambia.
blk.help.talentxp = Dà al giocatore esperienza per i talenti del maestro.
blk.help.if = Esegue i blocchi interni solo se la condizione è vera, altrimenti quelli sotto "altrimenti". blk.help.if = Esegue i blocchi interni solo se la condizione è vera, altrimenti quelli sotto "altrimenti".
blk.help.repeat = Esegue più volte i blocchi interni. blk.help.repeat = Esegue più volte i blocchi interni.
blk.help.trigger = Attiva un trigger (può scattare di nuovo) o lo disattiva. blk.help.trigger = Attiva un trigger (può scattare di nuovo) o lo disattiva.
@@ -1407,6 +1413,7 @@ blk.v.or = o
blk.v.not = non blk.v.not = non
blk.v.bool = Sì/no blk.v.bool = Sì/no
blk.v.eventkind = L'unità innesco è blk.v.eventkind = L'unità innesco è
blk.v.weatheris = Il meteo in superficie è
blk.w_at = presso blk.w_at = presso
blk.w_by = di blk.w_by = di
blk.w_in = in blk.w_in = in
@@ -1463,6 +1470,7 @@ blk.until = Ripeti finché
blk.dountil = Ripeti, poi controlla: finché blk.dountil = Ripeti, poi controlla: finché
blk.wait = Attendi blk.wait = Attendi
blk.w_seconds = secondi blk.w_seconds = secondi
blk.w_for = per
blk.help.until = Esegue i blocchi interni più volte finché la condizione è vera; viene controllata prima di ogni giro, quindi se è già vera il contenuto non viene eseguito. blk.help.until = Esegue i blocchi interni più volte finché la condizione è vera; viene controllata prima di ogni giro, quindi se è già vera il contenuto non viene eseguito.
blk.help.dountil = Esegue i blocchi interni, poi controlla la condizione e ripete finché è vera; il contenuto viene eseguito almeno una volta. blk.help.dountil = Esegue i blocchi interni, poi controlla la condizione e ripete finché è vera; il contenuto viene eseguito almeno una volta.
blk.help.wait = Mette in pausa questo script per alcuni secondi, poi prosegue con il blocco successivo (anche nei cicli). blk.help.wait = Mette in pausa questo script per alcuni secondi, poi prosegue con il blocco successivo (anche nei cicli).
@@ -1693,3 +1701,70 @@ tut.slap = Ditastorte: Ah! Hai sentito? Qualcosa arriva da est. Preparati: il fu
tut.obj6 = Sconfiggi i due cavalieri. tut.obj6 = Sconfiggi i due cavalieri.
tut.knights = Ditastorte: Due cavalieri! Manda le tue creature con la bandiera d'attacco (Ordini) o colpiscili con il fulmine. tut.knights = Ditastorte: Due cavalieri! Manda le tue creature con la bandiera d'attacco (Ordini) o colpiscili con il fulmine.
tut.win = Ditastorte: Ben fatto, padrone! Tutto qui. Be', quasi. Il resto lo racconta la campagna. Metto su il bollitore. tut.win = Ditastorte: Ben fatto, padrone! Tutto qui. Be', quasi. Il resto lo racconta la campagna. Metto su il bollitore.
# master talents
cat.master = Maestro
talent.title = Talenti del maestro
talent.points = Punti talento liberi: {0}
talent.next = {0}/{1} esperienza al prossimo punto
talent.all = Tutti i talenti sono appresi.
talent.builder = Costruttore
talent.warlord = Condottiero
talent.warlock = Stregone
talent.merchant = Mercante
talent.builder.desc = Gli Imp scavano il {0}% più in fretta, le stanze costano il {1}% in meno.
talent.warlord.desc = Le creature colpiscono il {0}% più forte e subiscono il {1}% di danni in meno.
talent.warlock.desc = Il mana scorre il {0}% più in fretta, le magie costano il {1}% in meno.
talent.merchant.desc = I salari sono il {0}% più bassi, il tesoro consegnato vale il {1}% in più.
talent.none = Non ancora appreso.
talent.then = Livello successivo: {0}
msg.talent_point = Un nuovo punto talento! Spendilo nella finestra del maestro.
msg.talent_learned = {0}: livello {1} appreso.
# weather above ground
weather.fair = Sereno
weather.rain = Pioggia
weather.fog = Nebbia
weather.storm = Tempesta
msg.weather_clear = In superficie il cielo si rasserena.
msg.weather_rain = In superficie inizia a piovere: il fuoco non attecchisce.
msg.weather_fog = La nebbia avvolge la terra: all'aperto tutti vedono meno.
msg.weather_storm = In superficie si avvicina una tempesta! I fulmini colpiscono unità e alberi.
msg.lightning_unit = Un fulmine colpisce {0}!
# creature names
names.imp = Briciola, Moccolo, Scavino, Fanghetto, Pidocchio, Sudicino, Grattino, Zozzo, Pulcino, Ciottolo, Rospetto, Scricciolo, Mollica, Fuliggine, Tappo, Bricco, Zampetto, Bigolo, Frugolo, Pallino
names.kobold = Ringhio, Graffio, Morso, Straccio, Rusco, Zanna, Pizzico, Rognoso, Uncino, Cardo, Spillo, Furbetto, Tarlo, Sgraffa, Ladruncolo, Sdentato, Sassolino, Ruggine, Grinza, Malandrino
names.spider = Zampette, Ragnetto, Filuccia, Solletico, Ottopiedi, Setina, Tessitrice, Batuffolo, Streghina, Groviglio, Gomitolo, Aghetto, Trappolina, Zannina, Pois, Merletto, Ricamina, Pungino, Ombretta, Ragnatela
names.mage = Bartolomeo, Ignazio, Ermenegildo, Anacleto, Fulgenzio, Eustachio, Leopoldo, Baldassarre, Prospero, Teodoro, Sigismondo, Fiorenzo, Casimiro, Gualtiero, Pancrazio, Aurelio, Ruggero, Cipriano, Serafino, Venceslao
names.ogre = Macigno, Testone, Zuccone, Bestione, Mattone, Bruto, Mangione, Ciccione, Ceppo, Tontolone, Tronco, Pancione, Babbeo, Pataccone, Gnocco, Bombo, Ciocco, Sberla, Bamboccione, Somarone
names.jailer = Chiavistello, Lucchetto, Ceppi, Cupo, Torvo, Segreta, Sbarra, Bieco, Ferro, Catena, Severo, Paletto, Manetta, Cerbero, Chiavaio, Lugubre, Arcigno, Gattabuia, Spranga, Anello
names.priestess = Brace, Favilla, Fiamma, Pira, Scintilla, Cenere, Vermiglia, Falò, Fulgida, Vampa, Morgana, Serafina, Scarlatta, Fiammetta, Ignea, Rubina, Ambra, Fenice, Candela, Lucilla
names.chainbreaker = Spezzacatene, Ulfgar, Animaferro, Brannoc, Kragmor, Valdrek, Spino, Gorath, Indomito, Rurik, Torvaldo, Agone, Durnok, Frantumo, Brando, Gualfredo, Korrun, Arvak, Liberatore, Alvardo
names.bat = Squittio, Alucce, Squitto, Morsetto, Eco, Pipistrello, Crepuscolo, Zinzino, Cri, Dentino, Svolazzo, Notturno, Falenino, Svolazzino, Picchiata, Pendolino, Vellutino, Ombrino, Strillo, Zigzag
names.gargoyle = Granito, Ardesia, Basalto, Selciato, Calcina, Ciottolone, Ghiaia, Selce, Mattone, Maceria, Quarzo, Pomice, Cornicione, Grifone, Musone, Travertino, Frontone, Scalpellino, Scalpello, Macigno
names.owl = Saputello, Gufetto, Piuma, Barbagianni, Minerva, Tobia, Civetta, Sapienzio, Piumino, Savio, Meditabondo, Anziano, Assiolo, Noctua, Allocco, Pergamena, Imbrunire, Gufone, Professore, Erudito
names.wyrm = Vermitrax, Fauci, Bruciaterra, Calderone, Ignivoro, Fumarolo, Drakkar, Pirro, Ossidiana, Boccafuoco, Vulcanor, Ustione, Malgrath, Tyrax, Inferno, Zolfo, Kaldor, Fyrnak, Ragnor, Tizzone
names.cogling = Bulloncino, Ingranaggio, Vitina, Cricchetto, Perno, Pignone, Aggeggio, Arnese, Chiavetta, Molla, Tintinnio, Coso, Rivetto, Ronzio, Manovella, Rondella, Cardine, Marchingegno, Tictac, Molletta
names.revenant = Lamento, Pena, Cinereo, Strazio, Vuoto, Lutto, Livido, Dolore, Spettro, Nenia, Tumulo, Penombra, Cripta, Pianto, Veglia, Sudario, Esangue, Elegia, Anima, Desolato
names.beast = Arruffo, Criniera, Bruno, Pelone, Sgranocchio, Muschio, Brontolo, Orsetto, Cespuglio, Scrocchio, Guscio, Zampone, Setola, Muffa, Rombo, Rovo, Cricrì, Borbotto, Bestione, Annusino
names.human = Rinaldo, Orlando, Corrado, Goffredo, Tristano, Ugo, Galvano, Lancillotto, Astolfo, Tancredi, Bertoldo, Ruggiero, Oliviero, Baldovino, Rambaldo, Guglielmo, Ettore, Gilberto, Rodolfo, Brunetto
epithets.mid = Spaccaossa, Pestafango, Mordiratti, Faccialercia, Masticasassi, Picchiateschi, Pugnopuzzo, Nocchedruggine, Sventrapance, Calciastinchi, Barbamuffa, Artigliosozzo, Sparamocci, Mangiaorecchie, Spaccaginocchia, Mangiavermi
epithets.high = Regicida, Manofunesta, Zannaterrore, Spaccamondi, Flagelloeroi, Pugnotempesta, Terrornotturno, Mietianime, Cuordiferro, Sfondacastelli, Ammazzadraghi, Passainferi, Signordombra, Coronasangue, Mareafunesta, Spezzastelle
name.epithet = {0} {1}
msg.creature_fallen = {0} ci ha lasciati. Una lapide commemorativa ne custodisce il nome.
tile.memorial = Lapide commemorativa
tile.memorial_info = Qui giace {0} ({1}, livello {2}).
# hall of the fallen
hall.title = Sala dei caduti
hall.count = Creature cadute ricordate: {0}
hall.empty = Nessuno è ancora caduto. Che duri a lungo.
hall.kills = Eroi uccisi: {0}
ov.fallen = Caduti ({0})
ov.living = Vivi
cause.slain = morte per mano di: {0}
cause.lava = morte nella lava
cause.water = annegamento
cause.snare = morte in una tagliola
cause.lightning = colpo di fulmine
cause.fell = caduta in battaglia
+75
View File
@@ -264,6 +264,8 @@ val.medium = Middel
val.high = Hoog val.high = Hoog
val.normal = Normaal val.normal = Normaal
val.large = Groot val.large = Groot
val.small = Klein
val.auto_plain = Automatisch
# ---- key binding names # ---- key binding names
key.cam_forward = Camera vooruit key.cam_forward = Camera vooruit
@@ -1374,6 +1376,8 @@ blk.reveal = Onthullen
blk.camera = Tonen blk.camera = Tonen
blk.gold = Goud + blk.gold = Goud +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Weer bovengronds
blk.talentxp = Talentervaring +
blk.if = Als blk.if = Als
blk.repeat = Herhaal blk.repeat = Herhaal
blk.trigger = Trigger schakelen blk.trigger = Trigger schakelen
@@ -1393,6 +1397,8 @@ blk.help.reveal = Heft de oorlogsmist boven een zone op.
blk.help.camera = Verplaatst de camera naar een zone. blk.help.camera = Verplaatst de camera naar een zone.
blk.help.gold = Geeft (of neemt) goud. blk.help.gold = Geeft (of neemt) goud.
blk.help.mana = Geeft (of neemt) mana. blk.help.mana = Geeft (of neemt) mana.
blk.help.weather = Zet het weer bovengronds (helder, regen, mist of storm) voor zoveel seconden; 0 houdt het tot een ander blok het verandert.
blk.help.talentxp = Geeft de speler ervaring voor de meestertalenten.
blk.help.if = Voert de blokken erin alleen uit als de voorwaarde klopt, anders die onder "anders". blk.help.if = Voert de blokken erin alleen uit als de voorwaarde klopt, anders die onder "anders".
blk.help.repeat = Voert de blokken erin meerdere keren uit. blk.help.repeat = Voert de blokken erin meerdere keren uit.
blk.help.trigger = Zet een trigger aan (hij mag weer afgaan) of uit. blk.help.trigger = Zet een trigger aan (hij mag weer afgaan) of uit.
@@ -1415,6 +1421,7 @@ blk.v.or = of
blk.v.not = niet blk.v.not = niet
blk.v.bool = Ja/nee blk.v.bool = Ja/nee
blk.v.eventkind = Triggerunit is een blk.v.eventkind = Triggerunit is een
blk.v.weatheris = Weer bovengronds is
blk.w_at = op blk.w_at = op
blk.w_by = met blk.w_by = met
blk.w_in = in blk.w_in = in
@@ -1463,6 +1470,7 @@ blk.until = Herhaal tot
blk.dountil = Herhaal, dan controleer: tot blk.dountil = Herhaal, dan controleer: tot
blk.wait = Wacht blk.wait = Wacht
blk.w_seconds = seconden blk.w_seconds = seconden
blk.w_for = voor
blk.help.until = Voert de blokken erin steeds opnieuw uit tot de voorwaarde klopt - gecontroleerd voor elke ronde, dus als ze al klopt, wordt de inhoud helemaal niet uitgevoerd. blk.help.until = Voert de blokken erin steeds opnieuw uit tot de voorwaarde klopt - gecontroleerd voor elke ronde, dus als ze al klopt, wordt de inhoud helemaal niet uitgevoerd.
blk.help.dountil = Voert de blokken erin uit, controleert dan de voorwaarde en herhaalt tot ze klopt - de inhoud wordt altijd minstens een keer uitgevoerd. blk.help.dountil = Voert de blokken erin uit, controleert dan de voorwaarde en herhaalt tot ze klopt - de inhoud wordt altijd minstens een keer uitgevoerd.
blk.help.wait = Pauzeert dit script een aantal seconden en gaat dan verder met het volgende blok (ook binnen lussen). blk.help.wait = Pauzeert dit script een aantal seconden en gaat dan verder met het volgende blok (ook binnen lussen).
@@ -1693,3 +1701,70 @@ tut.slap = Krommevinger: Ha! Hoorde je dat? Er komt iets uit het oosten. Maak je
tut.obj6 = Versla de twee ridders. tut.obj6 = Versla de twee ridders.
tut.knights = Krommevinger: Twee ridders! Stuur je wezens met de aanvalsvlag (Bevelen) of tref ze met de bliksem. tut.knights = Krommevinger: Twee ridders! Stuur je wezens met de aanvalsvlag (Bevelen) of tref ze met de bliksem.
tut.win = Krommevinger: Goed gedaan, meester! Meer is het niet. Nou ja, bijna. De veldtocht vertelt de rest. Ik zet water op. tut.win = Krommevinger: Goed gedaan, meester! Meer is het niet. Nou ja, bijna. De veldtocht vertelt de rest. Ik zet water op.
# master talents
cat.master = Meester
talent.title = Meestertalenten
talent.points = Vrije talentpunten: {0}
talent.next = {0}/{1} ervaring tot het volgende punt
talent.all = Alle talenten zijn geleerd.
talent.builder = Bouwmeester
talent.warlord = Krijgsheer
talent.warlock = Heksenmeester
talent.merchant = Koopman
talent.builder.desc = Imps graven {0}% sneller, kamers kosten {1}% minder.
talent.warlord.desc = Wezens slaan {0}% harder en krijgen {1}% minder schade.
talent.warlock.desc = Mana stroomt {0}% sneller, spreuken kosten {1}% minder.
talent.merchant.desc = Lonen zijn {0}% lager, afgeleverde schatten zijn {1}% meer waard.
talent.none = Nog niet geleerd.
talent.then = Volgend niveau: {0}
msg.talent_point = Een nieuw talentpunt! Besteed het in het meestervenster.
msg.talent_learned = {0} niveau {1} geleerd.
# weather above ground
weather.fair = Helder
weather.rain = Regen
weather.fog = Mist
weather.storm = Storm
msg.weather_clear = Bovengronds klaart het op.
msg.weather_rain = Bovengronds begint het te regenen - vuur vat geen vlam.
msg.weather_fog = Mist trekt over het land - buiten ziet iedereen minder.
msg.weather_storm = Bovengronds steekt een storm op! Bliksem treft units en bomen.
msg.lightning_unit = Door de bliksem getroffen: {0}!
# creature names
names.imp = Kruimel, Snotje, Graafje, Modder, Pluis, Kleintje, Wroeter, Smoezel, Peuter, Krabber, Mopper, Brokje, Spartel, Slijkie, Priegel, Knobbel, Schepje, Snuiter, Woelie, Vlekje
names.kobold = Kef, Knijp, Grijptand, Schurft, Hap, Roestig, Zwiep, Bijter, Gapper, Rat, Flard, Stekel, Klauw, Stoker, Wortel, Gniffel, Duwer, Kartel, Knik, Schram
names.spider = Kriebel, Wolpootje, Spinnetje, Kietel, Netje, Achtpoot, Draadje, Griezeltje, Friemel, Zijdje, Webbie, Hupsel, Pluisbeen, Tekla, Spoeltje, Hapje, Garentje, Plakkie, Wiebel, Naaldje
names.mage = Bartholomeus, Ignatius, Leopold, Theobald, Adelbert, Hieronymus, Wendelinus, Godfried, Cornelis, Ottokar, Florentius, Engelbert, Pancratius, Rupertus, Constantijn, Eusebius, Servaas, Barnabas, Archibald, Willibrord
names.ogre = Knor, Klomp, Boem, Brok, Bonk, Dommel, Lomp, Klopper, Stamper, Houtkop, Plof, Mops, Bolle, Pens, Knuppel, Os, Rommel, Klont, Bonkie, Gorbo
names.jailer = Grim, Grendel, Ketting, Slot, Kerker, IJzerbaard, Boei, Bout, Streng, Tralie, Nurks, Haak, Strop, Sleutel, Duister, Rinkel, Stokman, Cipier, Kooi, Bitter
names.priestess = Gloed, Vlam, As, Serafien, Laaie, Vuurtje, Morwen, Brunhilde, Ignia, Sintel, Scharlaken, Vonkje, Raven, Lilith, Tondel, Isolde, Brandje, Nyx, Vesper, Gloria
names.chainbreaker = Ketenbreker, Ulfgar, IJzerziel, Brannok, Kragmor, Valdrek, Doorn, Gorath, Ongebonden, Rurik, Torvald, Hagen, Durnok, Splinter, Brand, Wolfert, Korrun, Arvak, Sigbert, Halvard
names.bat = Piepje, Fladder, Fiep, Zoef, Echo, Flap, Schemer, Sisje, Tjirp, Tandje, Draai, Vleermuisje, Motje, Piep, Suizer, Bungel, Fluweeltje, Hupsje, Kwiek, Flitsje
names.gargoyle = Graniet, Leisteen, Basalt, Grimbert, Mortel, Kiezel, Grind, Vuursteen, Voeg, Puin, Kwarts, Puimsteen, Lijst, Knoest, Steenbaard, Chagrijn, Gevel, Spuier, Beitel, Sokkel
names.owl = Oehoe, Wijsneus, Ganzenveer, Bartholdus, Minerva, Tobias, Hoehoe, Uilalia, Veertje, Zucht, Pieker, Oudste, Kauwtje, Noctua, Uilbert, Perkament, Schemer, Bosuil, Professor, Pluimpje
names.wyrm = Vermithrax, Asmuil, Schroeier, Calderon, Ignivar, Gloeihoorn, Drakkar, Pyrrhus, Obsidiaan, Gloedmuil, Vulkanor, Brandtand, Malgrath, Tyrax, Inferno, Zwavel, Kaldor, Fafnir, Ragnor, Sinteros
names.cogling = Radertje, Tandwiel, Schroefje, Ratel, Bout, Drijfwerk, Prutser, Dinges, Sleuteltje, Rondsel, Rammel, Friemel, Klinknagel, Zoem, Slinger, Draaitol, Flens, Klikklak, Tiktak, Nippel
names.revenant = Morgen, Wrok, Asgrauw, Klacht, Hol, Rouw, Bleek, Kommer, Schim, Vergaan, Grafman, Schemering, Grafkelder, Weemoed, Wake, Heuvel, Lijkwade, Elegie, Droesem, Woest
names.beast = Ruig, Brombeer, Bruno, Sloddervos, Smikkel, Mosrug, Brommer, Ursa, Knoert, Knaks, Klikker, Kruipert, Borstel, Mos, Gerommel, Braam, Tsjirp, Mopperaar, Lobbes, Snuffel
names.human = Gerard, Roeland, Koenraad, Aldert, Tristan, Ewout, Walewein, Lancelot, Ferguut, Hugo, Bertram, Godfried, Ogier, Boudewijn, Reinout, Willem, Diederik, Gijsbrecht, Floris, Dirk
epithets.mid = Botbreker, Modderpoot, Rattenbijter, Smoelvuil, Kiezelknager, Schedelklopper, Stinkvuist, Roestknokkel, Buikstomper, Schenenschopper, Schimmelbaard, Vuilklauw, Snotslinger, Orenknager, Knieknakker, Wormvreter
epithets.high = Koningsdoder, Onheilsvuist, Schriktand, Wereldbreker, Heldenvloek, Stormvuist, Nachtgruwel, Zielenmaaier, IJzerhart, Burchtbreker, Drakendoder, Hellegaander, Schaduwvorst, Bloedkroon, Grimmetij, Sterrenbreker
name.epithet = {0} {1}
msg.creature_fallen = {0} is gesneuveld. Een gedenksteen bewaart de naam.
tile.memorial = Gedenksteen
tile.memorial_info = Hier rust {0} ({1}, niveau {2}).
# hall of the fallen
hall.title = Hal der gevallenen
hall.count = Gevallen wezens in herinnering: {0}
hall.empty = Nog niemand is gevallen. Moge het zo blijven.
hall.kills = Verslagen helden: {0}
ov.fallen = Gevallen ({0})
ov.living = Levend
cause.slain = verslagen door: {0}
cause.lava = verbrand in lava
cause.water = verdronken
cause.snare = in een klem getrapt
cause.lightning = door de bliksem getroffen
cause.fell = gesneuveld
+75
View File
@@ -264,6 +264,8 @@ val.medium = Middels
val.high = Høy val.high = Høy
val.normal = Normal val.normal = Normal
val.large = Stor val.large = Stor
val.small = Liten
val.auto_plain = Automatisk
# ---- key binding names # ---- key binding names
key.cam_forward = Kamera fram key.cam_forward = Kamera fram
@@ -1373,6 +1375,8 @@ blk.reveal = Avslør
blk.camera = Vis blk.camera = Vis
blk.gold = Gull + blk.gold = Gull +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Været over jorden
blk.talentxp = Talenterfaring +
blk.if = Hvis blk.if = Hvis
blk.repeat = Gjenta blk.repeat = Gjenta
blk.trigger = Bytt utløser blk.trigger = Bytt utløser
@@ -1392,6 +1396,8 @@ blk.help.reveal = Løfter krigens tåke over en sone.
blk.help.camera = Flytter kameraet til en sone. blk.help.camera = Flytter kameraet til en sone.
blk.help.gold = Gir (eller tar) gull. blk.help.gold = Gir (eller tar) gull.
blk.help.mana = Gir (eller tar) mana. blk.help.mana = Gir (eller tar) mana.
blk.help.weather = Setter været over jorden (klart, regn, tåke eller storm) i så mange sekunder; 0 beholder det til en annen blokk endrer det.
blk.help.talentxp = Gir spilleren erfaring til mestertalentene.
blk.help.if = Kjører blokkene inni bare hvis betingelsen gjelder, ellers de under "ellers". blk.help.if = Kjører blokkene inni bare hvis betingelsen gjelder, ellers de under "ellers".
blk.help.repeat = Kjører blokkene inni flere ganger. blk.help.repeat = Kjører blokkene inni flere ganger.
blk.help.trigger = Slår en utløser på (den kan utløses igjen) eller av. blk.help.trigger = Slår en utløser på (den kan utløses igjen) eller av.
@@ -1414,6 +1420,7 @@ blk.v.or = eller
blk.v.not = ikke blk.v.not = ikke
blk.v.bool = Ja/nei blk.v.bool = Ja/nei
blk.v.eventkind = Utløserenhet er en blk.v.eventkind = Utløserenhet er en
blk.v.weatheris = Været over jorden er
blk.w_at = ved blk.w_at = ved
blk.w_by = med blk.w_by = med
blk.w_in = i blk.w_in = i
@@ -1462,6 +1469,7 @@ blk.until = Gjenta til
blk.dountil = Gjenta, sjekk så: til blk.dountil = Gjenta, sjekk så: til
blk.wait = Vent blk.wait = Vent
blk.w_seconds = sekunder blk.w_seconds = sekunder
blk.w_for = i
blk.help.until = Kjører blokkene inni igjen og igjen til betingelsen gjelder - sjekkes før hver kjøring, så hvis den allerede gjelder, kjører innholdet ikke i det hele tatt. blk.help.until = Kjører blokkene inni igjen og igjen til betingelsen gjelder - sjekkes før hver kjøring, så hvis den allerede gjelder, kjører innholdet ikke i det hele tatt.
blk.help.dountil = Kjører blokkene inni, sjekker så betingelsen og gjentar til den gjelder - innholdet kjører alltid minst én gang. blk.help.dountil = Kjører blokkene inni, sjekker så betingelsen og gjentar til den gjelder - innholdet kjører alltid minst én gang.
blk.help.wait = Setter dette skriptet på pause i noen sekunder, så fortsetter det med neste blokk (også inni løkker). blk.help.wait = Setter dette skriptet på pause i noen sekunder, så fortsetter det med neste blokk (også inni løkker).
@@ -1692,3 +1700,70 @@ tut.slap = Krokfinger: Ha! Hørte du det? Noe kommer fra øst. Gjør deg klar: l
tut.obj6 = Beseir de to ridderne. tut.obj6 = Beseir de to ridderne.
tut.knights = Krokfinger: To riddere! Send skapningene dine med angrepsflagget (Ordrer), eller slå dem med lynet. tut.knights = Krokfinger: To riddere! Send skapningene dine med angrepsflagget (Ordrer), eller slå dem med lynet.
tut.win = Krokfinger: Godt gjort, mester! Det var det hele. Vel – nesten. Kampanjen forteller resten. Jeg setter på kjelen. tut.win = Krokfinger: Godt gjort, mester! Det var det hele. Vel – nesten. Kampanjen forteller resten. Jeg setter på kjelen.
# master talents
cat.master = Mester
talent.title = Mestertalenter
talent.points = Ledige talentpoeng: {0}
talent.next = {0}/{1} erfaring til neste poeng
talent.all = Alle talenter er lært.
talent.builder = Byggmester
talent.warlord = Krigsherre
talent.warlock = Heksemester
talent.merchant = Kjøpmann
talent.builder.desc = Imper graver {0}% raskere, rom koster {1}% mindre.
talent.warlord.desc = Skapninger slår {0}% hardere og tar {1}% mindre skade.
talent.warlock.desc = Mana flyter {0}% raskere, formler koster {1}% mindre.
talent.merchant.desc = Lønningene er {0}% lavere, levert skatt er verdt {1}% mer.
talent.none = Ikke lært ennå.
talent.then = Neste nivå: {0}
msg.talent_point = Et nytt talentpoeng! Bruk det i mestervinduet.
msg.talent_learned = {0} nivå {1} lært.
# weather above ground
weather.fair = Klart
weather.rain = Regn
weather.fog = Tåke
weather.storm = Storm
msg.weather_clear = Over jorden klarner det opp.
msg.weather_rain = Over jorden begynner det å regne - ild tar ikke fyr.
msg.weather_fog = Tåke driver inn over landet - ute ser alle mindre.
msg.weather_storm = Over jorden trekker et uvær opp! Lynet treffer enheter og trær.
msg.lightning_unit = Truffet av lynet: {0}!
# creature names
names.imp = Smule, Snørr, Graver, Søle, Lo, Pjokk, Rotemus, Grisen, Busemann, Kloren, Bøtte, Klatt, Gjørme, Pirk, Knott, Spade, Snute, Pusling, Flekk, Grums
names.kobold = Bjeff, Klyp, Gripetann, Skabb, Happ, Rusten, Svupp, Biter, Glefs, Rotta, Fille, Pigg, Klo, Bråk, Rask, Fnis, Dytter, Hakk, Knekk, Skramme
names.spider = Krabbe, Ullbein, Kongle, Kile, Nettie, Åttefot, Tråden, Grøssel, Fikle, Silke, Veversken, Hoppsa, Lobein, Tekla, Spole, Napp, Garnnøste, Klister, Vimsa, Nåla
names.mage = Bartholomeus, Ignatius, Leopold, Theobald, Adalbert, Hieronymus, Valdemar, Gotfred, Cornelius, Ottokar, Florentin, Engelbrekt, Pankratius, Rupertus, Konstantin, Eusebius, Severin, Barnabas, Archibald, Vitus
names.ogre = Brumm, Kloss, Bums, Klump, Dunk, Tomsing, Tufs, Klasker, Tramp, Trehode, Plump, Mops, Bolle, Vom, Klubbe, Okse, Rumle, Klatt, Hump, Gorbo
names.jailer = Grim, Slå, Lenke, Lås, Fangehull, Jernskjegg, Bøyle, Bolt, Streng, Sprinkel, Grinete, Krok, Snøre, Nøkkel, Dyster, Klirr, Stokkmann, Vokter, Bur, Bitter
names.priestess = Glød, Flamme, Aske, Serafina, Lue, Ildine, Morwen, Brynhild, Ignia, Gnist, Skarlagen, Funke, Ravna, Lilith, Knusk, Isolde, Brannhild, Nyx, Vesper, Glimt
names.chainbreaker = Lenkebryter, Ulfgar, Jernsjel, Brannok, Kragmor, Valdrek, Torn, Gorath, Ubundet, Rurik, Torvald, Hagen, Durnok, Splint, Brand, Ulfrik, Korrun, Arvak, Sigurd, Halvard
names.bat = Pip, Flagre, Pipa, Susle, Ekko, Flaks, Skumring, Hvisle, Kvitre, Tannsen, Virvel, Flaggermus, Møll, Pippe, Suse, Dingle, Fløyel, Smutt, Hvin, Lynet
names.gargoyle = Granitt, Skifer, Basalt, Grimbert, Mørtel, Kisel, Pukk, Flint, Fuge, Steinrøys, Kvarts, Pimpstein, Gesims, Knort, Steinskjegg, Gretten, Gavl, Vannspyer, Meisel, Sokkel
names.owl = Hubro, Klokhue, Fjærpenn, Bartholdus, Minerva, Tobias, Huhu, Uglalia, Fjærdrik, Sukk, Grubler, Eldste, Kattugle, Noctua, Uglebert, Pergament, Skumring, Natugle, Professor, Dun
names.wyrm = Vermithrax, Askegap, Svie, Calderon, Ignivar, Glødehorn, Drakkar, Pyrrhus, Obsidian, Glødgap, Vulkanor, Brenntann, Malgrath, Tyrax, Inferno, Svovel, Kaldor, Fåvne, Ragnor, Sinderos
names.cogling = Tannhjul, Hjulet, Skrue, Skralle, Bolt, Gir, Fikser, Dings, Nøkkelen, Drev, Klirre, Fikle, Nagle, Snurre, Sveiv, Snurrebass, Flens, Klikklakk, Tikktakk, Nippel
names.revenant = Morgen, Nag, Askegrå, Klage, Hul, Sorg, Blek, Kummer, Skygge, Mold, Gravmann, Tussmørke, Krypt, Vemod, Våke, Haug, Likklede, Elegi, Berme, Øde
names.beast = Lurv, Brummbjørn, Bruno, Raggete, Mumle, Moserygg, Brummer, Ursa, Knort, Knask, Klikker, Kryper, Bust, Mose, Rumling, Tornebusk, Knirk, Gnålepott, Bamse, Snuse
names.human = Gerhard, Roland, Konrad, Albrekt, Tristan, Eskil, Gavin, Lancelot, Pål, Håkon, Bertram, Gotfred, Eirik, Sverre, Ragnar, Vilfred, Ditlev, Gilbert, Rasmus, Kjell
epithets.mid = Beinknuser, Gjørmefot, Rottebiter, Skittfjes, Steingnager, Skallebanker, Stinkeneve, Rustknoke, Magestøter, Leggsparker, Muggskjegg, Skittklo, Snørrslynge, Øregnager, Kneknekker, Markeeter
epithets.high = Kongedreper, Undergangshånd, Redselstann, Verdensknuser, Heltebane, Stormneve, Nattskrekk, Sjelehøster, Jernhjerte, Borgknuser, Drageskrekk, Helvetesvandrer, Skyggefyrste, Blodkrone, Dommeflod, Stjerneknuser
name.epithet = {0} {1}
msg.creature_fallen = {0} har falt. En minnestein bevarer navnet.
tile.memorial = Minnestein
tile.memorial_info = Her hviler {0} ({1}, nivå {2}).
# hall of the fallen
hall.title = De falnes hall
hall.count = Falne skapninger i minnet: {0}
hall.empty = Ingen har falt ennå. Lenge må det vare.
hall.kills = Drepte helter: {0}
ov.fallen = Falne ({0})
ov.living = Levende
cause.slain = drept av: {0}
cause.lava = brent i lava
cause.water = druknet
cause.snare = tråkket i en saks
cause.lightning = truffet av lynet
cause.fell = falt
+75
View File
@@ -264,6 +264,8 @@ val.medium = Médio
val.high = Alto val.high = Alto
val.normal = Normal val.normal = Normal
val.large = Grande val.large = Grande
val.small = Pequeno
val.auto_plain = Automático
# ---- key binding names # ---- key binding names
key.cam_forward = Câmara p/ frente key.cam_forward = Câmara p/ frente
@@ -1374,6 +1376,8 @@ blk.reveal = Revelar
blk.camera = Mostrar blk.camera = Mostrar
blk.gold = Ouro + blk.gold = Ouro +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Tempo na superfície
blk.talentxp = Experiência de talentos +
blk.if = Se blk.if = Se
blk.repeat = Repetir blk.repeat = Repetir
blk.trigger = Ligar gatilho blk.trigger = Ligar gatilho
@@ -1393,6 +1397,8 @@ blk.help.reveal = Levanta o nevoeiro de guerra sobre uma zona.
blk.help.camera = Move a câmara para uma zona. blk.help.camera = Move a câmara para uma zona.
blk.help.gold = Dá (ou tira) ouro. blk.help.gold = Dá (ou tira) ouro.
blk.help.mana = Dá (ou tira) mana. blk.help.mana = Dá (ou tira) mana.
blk.help.weather = Define o tempo na superfície (limpo, chuva, nevoeiro ou temporal) durante tantos segundos; 0 mantém-no até outro bloco o mudar.
blk.help.talentxp = Dá ao jogador experiência para os talentos do mestre.
blk.help.if = Executa os blocos de dentro só se a condição se verificar; caso contrário, os de "senão". blk.help.if = Executa os blocos de dentro só se a condição se verificar; caso contrário, os de "senão".
blk.help.repeat = Executa os blocos de dentro várias vezes. blk.help.repeat = Executa os blocos de dentro várias vezes.
blk.help.trigger = Liga um gatilho (pode disparar de novo) ou desliga-o. blk.help.trigger = Liga um gatilho (pode disparar de novo) ou desliga-o.
@@ -1415,6 +1421,7 @@ blk.v.or = ou
blk.v.not = não blk.v.not = não
blk.v.bool = Sim/não blk.v.bool = Sim/não
blk.v.eventkind = Unidade do gatilho é blk.v.eventkind = Unidade do gatilho é
blk.v.weatheris = Tempo na superfície é
blk.w_at = em blk.w_at = em
blk.w_by = por blk.w_by = por
blk.w_in = em blk.w_in = em
@@ -1463,6 +1470,7 @@ blk.until = Repetir até
blk.dountil = Repetir, depois verificar: até blk.dountil = Repetir, depois verificar: até
blk.wait = Esperar blk.wait = Esperar
blk.w_seconds = segundos blk.w_seconds = segundos
blk.w_for = durante
blk.help.until = Executa os blocos de dentro uma e outra vez até a condição se verificar - verificada antes de cada execução, por isso, se já se verificar, o interior nem chega a correr. blk.help.until = Executa os blocos de dentro uma e outra vez até a condição se verificar - verificada antes de cada execução, por isso, se já se verificar, o interior nem chega a correr.
blk.help.dountil = Executa os blocos de dentro, depois verifica a condição e repete até ela se verificar - o interior corre sempre pelo menos uma vez. blk.help.dountil = Executa os blocos de dentro, depois verifica a condição e repete até ela se verificar - o interior corre sempre pelo menos uma vez.
blk.help.wait = Pausa este script durante alguns segundos e depois continua com o bloco seguinte (também dentro de ciclos). blk.help.wait = Pausa este script durante alguns segundos e depois continua com o bloco seguinte (também dentro de ciclos).
@@ -1693,3 +1701,70 @@ tut.slap = Dedotorto: Ah! Ouviste aquilo? Vem aí qualquer coisa do leste. Prepa
tut.obj6 = Derrota os dois cavaleiros. tut.obj6 = Derrota os dois cavaleiros.
tut.knights = Dedotorto: Dois cavaleiros! Envia as tuas criaturas com a bandeira de ataque (Ordens) ou atinge-os com o relâmpago. tut.knights = Dedotorto: Dois cavaleiros! Envia as tuas criaturas com a bandeira de ataque (Ordens) ou atinge-os com o relâmpago.
tut.win = Dedotorto: Muito bem, mestre! É tudo. Bem, quase. A campanha conta o resto. Vou pôr a chaleira ao lume. tut.win = Dedotorto: Muito bem, mestre! É tudo. Bem, quase. A campanha conta o resto. Vou pôr a chaleira ao lume.
# master talents
cat.master = Mestre
talent.title = Talentos do mestre
talent.points = Pontos de talento livres: {0}
talent.next = {0}/{1} de experiência até ao próximo ponto
talent.all = Todos os talentos foram aprendidos.
talent.builder = Construtor
talent.warlord = Senhor da Guerra
talent.warlock = Bruxo
talent.merchant = Mercador
talent.builder.desc = Os diabretes cavam {0}% mais depressa e as salas custam {1}% menos.
talent.warlord.desc = As criaturas batem {0}% mais forte e sofrem {1}% menos dano.
talent.warlock.desc = A mana flui {0}% mais depressa e os feitiços custam {1}% menos.
talent.merchant.desc = Os salários são {0}% mais baixos e o tesouro entregue vale {1}% mais.
talent.none = Ainda não aprendido.
talent.then = Próximo nível: {0}
msg.talent_point = Um novo ponto de talento! Gasta-o na janela do mestre.
msg.talent_learned = {0}: nível {1} aprendido.
# weather above ground
weather.fair = Limpo
weather.rain = Chuva
weather.fog = Nevoeiro
weather.storm = Temporal
msg.weather_clear = O céu limpa à superfície.
msg.weather_rain = Começa a chover à superfície - o fogo não pega.
msg.weather_fog = O nevoeiro cobre a terra - lá fora todos veem menos.
msg.weather_storm = Aproxima-se uma tempestade à superfície! Os raios atingem unidades e árvores.
msg.lightning_unit = Um raio atinge {0}!
# creature names
names.imp = Ranhoso, Migalha, Cavuco, Lamacento, Piolho, Sujinho, Coçadinho, Porcalhão, Pintinho, Seixinho, Sapinho, Fuligem, Rolha, Tampinha, Patinha, Fuçador, Bolinha, Remelas, Grudinho, Tiquinho
names.kobold = Rosnado, Arranhão, Mordida, Trapo, Ranzinza, Presa, Beliscão, Sarnento, Gancho, Cardo, Alfinete, Espertalhão, Caruncho, Unhaca, Gatuno, Banguela, Pedrinha, Ferrugem, Ruga, Malandro
names.spider = Perninhas, Aranhinha, Fiozinho, Cosquinha, Oitopés, Sedinha, Tecedeira, Fofura, Bruxinha, Embaraço, Novelo, Agulhinha, Armadilha, Dentinha, Bolinhas, Rendinha, Bordadeira, Picadinha, Sombrinha, Teiazinha
names.mage = Bartolomeu, Inácio, Hermenegildo, Anacleto, Fulgêncio, Eustáquio, Leopoldo, Baltasar, Próspero, Teodoro, Segismundo, Florêncio, Casimiro, Gualtério, Pancrácio, Aurélio, Rogério, Cipriano, Serafim, Venceslau
names.ogre = Pedregulho, Cabeção, Toco, Brutamontes, Tijolo, Bruto, Comilão, Barrigudo, Cepo, Tontão, Tronco, Pançudo, Pateta, Bolão, Nhoque, Bumbo, Torrão, Tabefe, Mamute, Jumento
names.jailer = Ferrolho, Cadeado, Grilhão, Sombrio, Torvo, Masmorra, Grade, Carrancudo, Ferro, Corrente, Severo, Tranca, Algema, Cérbero, Chaveiro, Lúgubre, Rabugento, Calabouço, Trinco, Argola
names.priestess = Brasa, Fagulha, Chama, Pira, Centelha, Cinza, Rubra, Fogueira, Fulgor, Labareda, Morgana, Serafina, Escarlate, Chispa, Ígnea, Rubi, Âmbar, Fênix, Candeia, Lumina
names.chainbreaker = Quebragrilho, Ulfgar, Almaferro, Brannoc, Kragmor, Valdrek, Espinho, Gorath, Indomável, Rurik, Torvaldo, Hagan, Durnok, Estilhaço, Brando, Wulfrico, Korrun, Arvak, Libertador, Halvardo
names.bat = Guincho, Asinhas, Pio, Mordisco, Eco, Morceguito, Crepúsculo, Zunzum, Chiadinho, Dentinho, Revoada, Noturno, Mariposa, Bicudinho, Rasante, Penduricalho, Veludo, Sombrio, Grito, Ziguezague
names.gargoyle = Granito, Ardósia, Basalto, Pedrão, Argamassa, Calhau, Cascalho, Pederneira, Tijolão, Entulho, Quartzo, Pomes, Cornija, Grifo, Carrancão, Cantaria, Frontão, Canteiro, Cinzel, Penedo
names.owl = Sabichão, Corujinha, Pena, Mocho, Minerva, Tobias, Coruja, Sapiêncio, Penugem, Sábio, Pensativo, Ancião, Caburé, Noctua, Bufo, Pergaminho, Ocaso, Murucututu, Professor, Erudito
names.wyrm = Vermitrax, Bocarra, Chamusco, Caldeirão, Ignívoro, Fumarola, Drakkar, Pirro, Obsidiana, Boquifogo, Vulcanor, Abrasador, Malgrath, Tyrax, Inferno, Enxofre, Kaldor, Fyrnak, Ragnor, Tição
names.cogling = Parafusinho, Engrenagem, Porquinha, Catraca, Pino, Pinhão, Geringonça, Treco, Chavinha, Mola, Tlintlim, Bugiganga, Rebite, Zumbido, Manivela, Arruela, Dobradiça, Engenhoca, Tiquetaque, Molinha
names.revenant = Lamento, Pesar, Cinzento, Quebranto, Vazio, Luto, Lívido, Pena, Espectro, Nênia, Túmulo, Penumbra, Cripta, Pranto, Vigília, Mortalha, Sudário, Elegia, Alma, Desolado
names.beast = Desgrenhado, Jubão, Bruno, Peludão, Mastigão, Musgoso, Resmungão, Ursinho, Moita, Crocante, Casca, Patudo, Cerdas, Mofo, Ronco, Espinheiro, Estalido, Rezingão, Mamutão, Fungador
names.human = Rodrigo, Gonçalo, Álvaro, Fernão, Tristão, Nuno, Galvão, Lançarote, Pelágio, Henrique, Beltrão, Godofredo, Afonso, Balduíno, Ramiro, Martim, Egas, Gilberto, Vasco, Duarte
epithets.mid = Quebra-Ossos, Pé-de-Lama, Morde-Ratos, Cara-Suja, Rói-Pedras, Racha-Crânios, Punho-Fedido, Nó-Ferrugem, Arromba-Panças, Chuta-Canelas, Barba-Mofo, Garra-Suja, Atira-Ranho, Masca-Orelhas, Parte-Joelhos, Come-Minhocas
epithets.high = Mata-Reis, Mão-Funesta, Presa-Terror, Quebra-Mundos, Flagelo-Heróis, Punho-Tormenta, Terror-Noturno, Ceifa-Almas, Coração-Ferro, Derruba-Castelos, Mata-Dragões, Anda-Infernos, Senhor-Sombra, Coroa-Sangue, Maré-Funesta, Parte-Estrelas
name.epithet = {0} {1}
msg.creature_fallen = {0} tombou. Uma pedra memorial guarda o seu nome.
tile.memorial = Pedra memorial
tile.memorial_info = Aqui jaz {0} ({1}, nível {2}).
# hall of the fallen
hall.title = Salão dos caídos
hall.count = Criaturas caídas recordadas: {0}
hall.empty = Ainda ninguém caiu. Que assim continue.
hall.kills = Heróis abatidos: {0}
ov.fallen = Caídos ({0})
ov.living = Vivos
cause.slain = morte às mãos de: {0}
cause.lava = morte na lava
cause.water = afogamento
cause.snare = morte numa armadilha
cause.lightning = morte por raio
cause.fell = queda em combate
+75
View File
@@ -264,6 +264,8 @@ val.medium = Mediu
val.high = Înalt val.high = Înalt
val.normal = Normal val.normal = Normal
val.large = Mare val.large = Mare
val.small = Mic
val.auto_plain = Automat
# ---- key binding names # ---- key binding names
key.cam_forward = Cameră înainte key.cam_forward = Cameră înainte
@@ -1374,6 +1376,8 @@ blk.reveal = Dezvăluie
blk.camera = Arată blk.camera = Arată
blk.gold = Aur + blk.gold = Aur +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Vremea la suprafață
blk.talentxp = Experiență pentru talente +
blk.if = Dacă blk.if = Dacă
blk.repeat = Repetă blk.repeat = Repetă
blk.trigger = Comută declanșator blk.trigger = Comută declanșator
@@ -1393,6 +1397,8 @@ blk.help.reveal = Ridică ceața războiului de pe o zonă.
blk.help.camera = Mută camera la o zonă. blk.help.camera = Mută camera la o zonă.
blk.help.gold = Dă (sau ia) aur. blk.help.gold = Dă (sau ia) aur.
blk.help.mana = Dă (sau ia) mana. blk.help.mana = Dă (sau ia) mana.
blk.help.weather = Setează vremea la suprafață (senin, ploaie, ceață sau furtună) pentru atâtea secunde; 0 o păstrează până când alt bloc o schimbă.
blk.help.talentxp = Dă jucătorului experiență pentru talentele stăpânului.
blk.help.if = Rulează blocurile dinăuntru doar dacă condiția e adevărată, altfel pe cele de sub "altfel". blk.help.if = Rulează blocurile dinăuntru doar dacă condiția e adevărată, altfel pe cele de sub "altfel".
blk.help.repeat = Rulează blocurile dinăuntru de mai multe ori. blk.help.repeat = Rulează blocurile dinăuntru de mai multe ori.
blk.help.trigger = Pornește un declanșator (poate acționa din nou) sau îl oprește. blk.help.trigger = Pornește un declanșator (poate acționa din nou) sau îl oprește.
@@ -1415,6 +1421,7 @@ blk.v.or = sau
blk.v.not = nu blk.v.not = nu
blk.v.bool = Da/nu blk.v.bool = Da/nu
blk.v.eventkind = Unitatea declanșatoare e un blk.v.eventkind = Unitatea declanșatoare e un
blk.v.weatheris = Vremea la suprafață e
blk.w_at = la blk.w_at = la
blk.w_by = cu blk.w_by = cu
blk.w_in = în blk.w_in = în
@@ -1463,6 +1470,7 @@ blk.until = Repetă până când
blk.dountil = Repetă, apoi verifică: până când blk.dountil = Repetă, apoi verifică: până când
blk.wait = Așteaptă blk.wait = Așteaptă
blk.w_seconds = secunde blk.w_seconds = secunde
blk.w_for = pentru
blk.help.until = Rulează blocurile dinăuntru iar și iar până când condiția e adevărată - verificată înaintea fiecărei rulări, deci dacă e deja adevărată, interiorul nu rulează deloc. blk.help.until = Rulează blocurile dinăuntru iar și iar până când condiția e adevărată - verificată înaintea fiecărei rulări, deci dacă e deja adevărată, interiorul nu rulează deloc.
blk.help.dountil = Rulează blocurile dinăuntru, apoi verifică condiția și repetă până e adevărată - interiorul rulează mereu cel puțin o dată. blk.help.dountil = Rulează blocurile dinăuntru, apoi verifică condiția și repetă până e adevărată - interiorul rulează mereu cel puțin o dată.
blk.help.wait = Oprește acest script câteva secunde, apoi continuă cu blocul următor (și în bucle). blk.help.wait = Oprește acest script câteva secunde, apoi continuă cu blocul următor (și în bucle).
@@ -1693,3 +1701,70 @@ tut.slap = Crookfinger: Ha! Ai auzit? Ceva vine dinspre est. Pregătește-te: fu
tut.obj6 = Învinge cei doi cavaleri. tut.obj6 = Învinge cei doi cavaleri.
tut.knights = Crookfinger: Doi cavaleri! Trimite-ți creaturile cu steagul de atac (Ordine) sau lovește-i cu fulgerul. tut.knights = Crookfinger: Doi cavaleri! Trimite-ți creaturile cu steagul de atac (Ordine) sau lovește-i cu fulgerul.
tut.win = Crookfinger: Bravo, stăpâne! Asta e tot. Ei - aproape. Campania îți spune restul. Pun de un ceai. tut.win = Crookfinger: Bravo, stăpâne! Asta e tot. Ei - aproape. Campania îți spune restul. Pun de un ceai.
# master talents
cat.master = Stăpân
talent.title = Talentele stăpânului
talent.points = Puncte de talent libere: {0}
talent.next = {0}/{1} experiență până la următorul punct
talent.all = Toate talentele sunt învățate.
talent.builder = Constructor
talent.warlord = Căpetenie
talent.warlock = Vrăjitor
talent.merchant = Negustor
talent.builder.desc = Drăcușorii sapă cu {0}% mai repede, camerele costă cu {1}% mai puțin.
talent.warlord.desc = Creaturile lovesc cu {0}% mai tare și primesc cu {1}% mai puține daune.
talent.warlock.desc = Mana curge cu {0}% mai repede, vrăjile costă cu {1}% mai puțin.
talent.merchant.desc = Simbriile sunt cu {0}% mai mici, comorile livrate valorează cu {1}% mai mult.
talent.none = Încă neînvățat.
talent.then = Nivelul următor: {0}
msg.talent_point = Un nou punct de talent! Folosește-l în fereastra stăpânului.
msg.talent_learned = {0}: nivelul {1} învățat.
# weather above ground
weather.fair = Senin
weather.rain = Ploaie
weather.fog = Ceață
weather.storm = Furtună
msg.weather_clear = La suprafață cerul se înseninează.
msg.weather_rain = La suprafață începe să plouă - focul nu se aprinde.
msg.weather_fog = Ceața se lasă peste ținut - afară toată lumea vede mai puțin.
msg.weather_storm = La suprafață se pregătește o furtună! Fulgerele lovesc unități și copaci.
msg.lightning_unit = Fulgerul a lovit: {0}!
# creature names
names.imp = Firimiță, Mucos, Săpăcel, Noroiaș, Scamă, Pițigoi, Scormon, Mânjitu, Murdărel, Zgârie, Bulgăraș, Pată, Glodaș, Pârlitu, Cocoloș, Lopățică, Râmățel, Mărunțel, Pătuță, Cârtiță
names.kobold = Lătrău, Ciupitu, Colțan, Râios, Mușcătoru, Rugină, Șmecheru, Rozătoru, Căscatu, Șobolan, Zdrențe, Spin, Gheară, Cârcotaș, Rădăcină, Chicotitu, Îmbrâncitu, Știrbu, Pârâitu, Zgârietură
names.spider = Picioruș, Lânos, Păiănjel, Gâdilici, Plăsuță, Optpicioare, Firicel, Fiorică, Bâjbâilă, Mătăsica, Țesătoarea, Săltăreț, Pufoșel, Tecla, Mosorel, Prinzătoru, Ghemuleț, Lipicioasa, Sfârlează, Acușor
names.mage = Bartolomeu, Ignațiu, Leopold, Teobald, Adalbert, Ieronim, Valdemar, Gotfrid, Corneliu, Otocar, Florentin, Engelbert, Pancrațiu, Ruprecht, Constantin, Eusebiu, Severin, Barnaba, Archibald, Bonifaciu
names.ogre = Bolovan, Butuc, Huidumă, Stâncă, Nătăfleț, Mătăhală, Bufnitu, Ciomag, Tropăitu, Căpățână, Pleosc, Burtosu, Ciolan, Pântecosu, Măciucă, Bou, Bubuitu, Hălcos, Toba, Gorbo
names.jailer = Posac, Zăvor, Lanț, Lacăt, Temniță, Barbăfier, Belciug, Șurub, Aspru, Gratie, Morocănos, Cârlig, Ștreang, Chelar, Întunecatu, Zornăitu, Butucar, Paznic, Cușcă, Amar
names.priestess = Jăratic, Flacăra, Cenușa, Serafina, Văpaia, Focoasa, Morgana, Brunhilda, Ignia, Scânteia, Purpura, Jarița, Corbița, Lilith, Iasca, Isolda, Pârjolia, Nyx, Vesperia, Flăcăruia
names.chainbreaker = Rupelanțuri, Ulfgar, Sufletfier, Brannoc, Kragmor, Valdrek, Spinu, Gorath, Neînfrânt, Rurik, Torvald, Hagen, Durnok, Țandără, Pârjol, Luposlav, Korrun, Arvak, Eliberatoru, Halvard
names.bat = Chițăilă, Fâlfâitu, Piu, Foșnet, Ecou, Fluturel, Amurg, Fluierici, Țârâitu, Colțișor, Vârtej, Liliecel, Molia, Piuitu, Vâjâitu, Atârnatu, Catifea, Strecuratu, Țipătu, Fulger
names.gargoyle = Granit, Ardezie, Bazalt, Chipdepiatră, Mortar, Pietricel, Prundiș, Cremene, Rost, Dărâmătură, Cuarț, Ponce, Cornișă, Nod, Pietroi, Bosumflatu, Fronton, Burlan, Dăltiță, Soclu
names.owl = Buhă, Deșteptu, Pană, Bartold, Minerva, Tobias, Uhu, Bufnițica, Penuța, Oftat, Gânditoru, Bătrânu, Cucuvea, Noctua, Huhurez, Pergament, Amurgel, Ciuf, Profesoru, Puf
names.wyrm = Vermithrax, Gurăcenușă, Pârjolitu, Calderon, Ignivar, Cornjar, Drakkar, Pyrrhus, Obsidian, Gurădefoc, Vulcanor, Colțjar, Malgrath, Tyrax, Infernu, Pucioasă, Kaldor, Fafnir, Ragnor, Tăciune
names.cogling = Rotiță, Angrenaj, Șurubel, Clichet, Nituț, Pinion, Meșteraș, Drăcovenie, Cheiță, Arcuș, Clinchet, Butonaș, Piuliță, Bâzâitu, Manivelă, Titirez, Flanșă, Clic, Tictac, Arculeț
names.revenant = Jale, Amărăciune, Cenușiu, Geamăt, Gol, Doliu, Palid, Durere, Umbră, Țărână, Gropar, Asfințit, Criptă, Dor, Priveghi, Movilă, Giulgiu, Elegie, Drojdie, Pustiu
names.beast = Zbârlitu, Moșu, Bruno, Ciufulitu, Plescăitu, Mușchiosu, Mormăitu, Ursuleț, Tufiș, Ronțăitu, Țăcănitu, Târâtu, Țepos, Mușchi, Bubuit, Mur, Cârâitu, Bombănitu, Martin, Adulmecătoru
names.human = Gerard, Roland, Conrad, Albert, Tristan, Eckhart, Gawain, Lancelot, Parsifal, Hugo, Bertram, Godefroy, Ortwin, Balduin, Reinhold, Wilfrid, Ditrich, Gilbert, Ruprecht, Radu
epithets.mid = Sparge-Oase, Picior-de-Noroi, Roade-Șobolani, Mutră-Murdară, Mestecă-Pietre, Sparge-Țeste, Pumn-Puturos, Os-Ruginit, Spintecă-Burți, Bate-Fluiere, Barbă-Mucegăită, Gheară-Murdară, Aruncă-Muci, Roade-Urechi, Rupe-Genunchi, Mănâncă-Viermi
epithets.high = Omoară-Regi, Mână-Blestemată, Colț-de-Groază, Sfarmă-Lumi, Pierzania-Eroilor, Pumn-de-Furtună, Groaza-Nopții, Secerător-de-Suflete, Inimă-de-Fier, Dărâmă-Cetăți, Omoară-Dragoni, Umblă-prin-Iad, Stăpânul-Umbrelor, Coroană-de-Sânge, Val-de-Osândă, Sfarmă-Stele
name.epithet = {0} {1}
msg.creature_fallen = {0} a căzut. O piatră comemorativă păstrează numele.
tile.memorial = Piatră comemorativă
tile.memorial_info = Aici se odihnește {0} ({1}, nivelul {2}).
# hall of the fallen
hall.title = Sala celor căzuți
hall.count = Creaturi căzute amintite: {0}
hall.empty = Încă n-a căzut nimeni. Să rămână așa.
hall.kills = Eroi uciși: {0}
ov.fallen = Căzuți ({0})
ov.living = Vii
cause.slain = ucigaș: {0}
cause.lava = moarte în lavă
cause.water = înec
cause.snare = moarte într-o capcană
cause.lightning = lovitură de fulger
cause.fell = moarte în luptă
+75
View File
@@ -264,6 +264,8 @@ val.medium = Средне
val.high = Высоко val.high = Высоко
val.normal = Обычный val.normal = Обычный
val.large = Большой val.large = Большой
val.small = Маленький
val.auto_plain = Авто
# ---- key binding names # ---- key binding names
key.cam_forward = Камера вперёд key.cam_forward = Камера вперёд
@@ -1366,6 +1368,8 @@ blk.reveal = Открыть
blk.camera = Показать blk.camera = Показать
blk.gold = Золото + blk.gold = Золото +
blk.mana = Мана + blk.mana = Мана +
blk.weather = Погода на поверхности
blk.talentxp = Опыт талантов +
blk.if = Если blk.if = Если
blk.repeat = Повторить blk.repeat = Повторить
blk.trigger = Переключить триггер blk.trigger = Переключить триггер
@@ -1385,6 +1389,8 @@ blk.help.reveal = Снимает туман войны с зоны.
blk.help.camera = Переносит камеру к зоне. blk.help.camera = Переносит камеру к зоне.
blk.help.gold = Даёт (или забирает) золото. blk.help.gold = Даёт (или забирает) золото.
blk.help.mana = Даёт (или забирает) ману. blk.help.mana = Даёт (или забирает) ману.
blk.help.weather = Задаёт погоду на поверхности (ясно, дождь, туман или гроза) на столько секунд; 0 оставляет её, пока другой блок её не изменит.
blk.help.talentxp = Даёт игроку опыт для талантов мастера.
blk.help.if = Выполняет вложенные блоки, только если условие верно, иначе - блоки под «иначе». blk.help.if = Выполняет вложенные блоки, только если условие верно, иначе - блоки под «иначе».
blk.help.repeat = Выполняет вложенные блоки несколько раз. blk.help.repeat = Выполняет вложенные блоки несколько раз.
blk.help.trigger = Включает триггер (он может сработать снова) или выключает его. blk.help.trigger = Включает триггер (он может сработать снова) или выключает его.
@@ -1407,6 +1413,7 @@ blk.v.or = или
blk.v.not = не blk.v.not = не
blk.v.bool = Да/нет blk.v.bool = Да/нет
blk.v.eventkind = Боец триггера: blk.v.eventkind = Боец триггера:
blk.v.weatheris = Погода на поверхности:
blk.w_at = где: blk.w_at = где:
blk.w_by = на blk.w_by = на
blk.w_in = в blk.w_in = в
@@ -1463,6 +1470,7 @@ blk.until = Повторять, пока не
blk.dountil = Повторять, затем проверить: пока не blk.dountil = Повторять, затем проверить: пока не
blk.wait = Ждать blk.wait = Ждать
blk.w_seconds = сек. blk.w_seconds = сек.
blk.w_for = на
blk.help.until = Снова и снова выполняет блоки внутри, пока условие не станет верным; проверка перед каждым проходом, поэтому если оно уже верно, блоки не выполняются. blk.help.until = Снова и снова выполняет блоки внутри, пока условие не станет верным; проверка перед каждым проходом, поэтому если оно уже верно, блоки не выполняются.
blk.help.dountil = Выполняет блоки внутри, затем проверяет условие и повторяет, пока оно не станет верным; блоки выполняются хотя бы один раз. blk.help.dountil = Выполняет блоки внутри, затем проверяет условие и повторяет, пока оно не станет верным; блоки выполняются хотя бы один раз.
blk.help.wait = Приостанавливает этот скрипт на несколько секунд, затем выполняется следующий блок (и внутри циклов). blk.help.wait = Приостанавливает этот скрипт на несколько секунд, затем выполняется следующий блок (и внутри циклов).
@@ -1693,3 +1701,70 @@ tut.slap = Кривопал: Ха! Слышал? С востока что-то
tut.obj6 = Победи двух рыцарей. tut.obj6 = Победи двух рыцарей.
tut.knights = Кривопал: Два рыцаря! Пошли своих существ флагом атаки (Приказы) или порази их молнией. tut.knights = Кривопал: Два рыцаря! Пошли своих существ флагом атаки (Приказы) или порази их молнией.
tut.win = Кривопал: Молодец, хозяин! Вот и всё. Ну — почти. Остальное расскажет кампания. Пойду поставлю чайник. tut.win = Кривопал: Молодец, хозяин! Вот и всё. Ну — почти. Остальное расскажет кампания. Пойду поставлю чайник.
# master talents
cat.master = Мастер
talent.title = Таланты мастера
talent.points = Свободные очки талантов: {0}
talent.next = {0}/{1} опыта до следующего очка
talent.all = Все таланты изучены.
talent.builder = Строитель
talent.warlord = Полководец
talent.warlock = Чернокнижник
talent.merchant = Торговец
talent.builder.desc = Бесята копают на {0}% быстрее, комнаты стоят на {1}% меньше.
talent.warlord.desc = Существа бьют на {0}% сильнее и получают на {1}% меньше урона.
talent.warlock.desc = Мана течёт на {0}% быстрее, чары стоят на {1}% меньше.
talent.merchant.desc = Жалованье на {0}% ниже, доставленные сокровища ценятся на {1}% выше.
talent.none = Ещё не изучено.
talent.then = Следующий уровень: {0}
msg.talent_point = Новое очко таланта! Потрать его в окне мастера.
msg.talent_learned = {0}: изучен уровень {1}.
# weather above ground
weather.fair = Ясно
weather.rain = Дождь
weather.fog = Туман
weather.storm = Гроза
msg.weather_clear = Наверху проясняется.
msg.weather_rain = Наверху начинается дождь - огонь не разгорается.
msg.weather_fog = Туман стелется над землёй - снаружи все видят хуже.
msg.weather_storm = Наверху надвигается гроза! Молнии бьют по бойцам и деревьям.
msg.lightning_unit = Удар молнии: {0}!
# creature names
names.imp = Крошка, Сопля, Копуша, Грязнуля, Чумазик, Шкет, Рылька, Замарашка, Козявка, Царапка, Комок, Хрюндель, Плюх, Пачкун, Лопатка, Шмыга, Нюхля, Мелочь, Ковырь, Бултых
names.kobold = Тявка, Щипун, Зубило, Паршивец, Хвать, Ржавчик, Шмыг, Кусака, Разиня, Крыс, Лохмотик, Колючка, Коготь, Склочник, Корешок, Хихикс, Пихало, Зазубрина, Хрясь, Царапыч
names.spider = Шуршик, Мохнолап, Паучок, Щекотун, Сеточка, Восьминог, Ниточка, Жуткоша, Перебирка, Шёлковка, Пряха, Прыгуля, Пушистик, Текла, Катушка, Хватайка, Клубочек, Липучка, Вертушка, Иголочка
names.mage = Варфоломей, Игнатий, Леопольд, Феофан, Евлампий, Иероним, Аполлинарий, Пафнутий, Корнилий, Онуфрий, Флорентий, Модест, Панкратий, Евстафий, Константин, Евсевий, Северин, Варнава, Акакий, Ферапонт
names.ogre = Громила, Чурбан, Бумбум, Валун, Тупица, Дубина, Бухтя, Колотун, Топтыга, Пень, Плюх, Брюхан, Балда, Пузан, Дубинка, Бугай, Грохот, Глыба, Барабан, Горбо
names.jailer = Хмурый, Засов, Кандальник, Замок, Темница, Железнобород, Скоба, Болт, Суровый, Решёткин, Насупленный, Крюк, Петля, Ключник, Мрачный, Звякун, Колодник, Стражник, Клеть, Горький
names.priestess = Жара, Пламя, Пепелина, Серафима, Полымя, Огнеслава, Морана, Брунгильда, Игния, Искра, Багряна, Жарка, Ворона, Лилит, Лучина, Изольда, Огнеяра, Никта, Вечерня, Купава
names.chainbreaker = Цепелом, Ульфгар, Железнодух, Браннок, Крагмор, Вальдрек, Терн, Горат, Несломленный, Рюрик, Торвальд, Хаген, Дурнок, Осколок, Пламень, Волкослав, Коррун, Арвак, Освободитель, Хальвард
names.bat = Писк, Порх, Пипа, Шорох, Эхо, Махалка, Сумрак, Свистун, Цвирк, Зубик, Вихрь, Нетопырь, Моль, Пискля, Шурша, Висюлька, Бархат, Шмыгун, Визгля, Молния
names.gargoyle = Гранит, Сланец, Базальт, Каменюка, Раствор, Галька, Щебень, Кремень, Шов, Руина, Кварц, Пемза, Карниз, Нарост, Каменобород, Угрюм, Фронтон, Водосток, Долото, Цоколь
names.owl = Филин, Умник, Пёрышко, Бартольд, Минерва, Товий, Ухух, Совушка, Пухлик, Вздох, Мыслитель, Старейшина, Сыч, Ноктуа, Совуслав, Пергамент, Сумеречник, Неясыть, Профессор, Пушок
names.wyrm = Вермитракс, Пеплопасть, Палёный, Кальдерон, Игнивар, Жаророг, Драккар, Пирр, Обсидиан, Жаропасть, Вулканор, Палезуб, Малграт, Тиракс, Инферно, Сера, Кальдор, Фафнир, Рагнор, Уголёк
names.cogling = Шестерёнка, Колёсико, Винтик, Трещотка, Заклёпка, Привод, Мастерок, Штуковина, Ключик, Шпиндель, Звякалка, Ковыряшка, Гаечка, Жужжа, Рычажок, Юла, Фланец, Щёлк, Тиктак, Шурупчик
names.revenant = Тоска, Горечь, Пепельный, Стон, Пустота, Скорбь, Бледный, Кручина, Тень, Прах, Могильщик, Сумрак, Склеп, Печаль, Бдение, Курган, Саван, Элегия, Осадок, Пустошь
names.beast = Лохмач, Мишка, Бруно, Косматый, Чавка, Мохоспин, Ворчун, Медвежонок, Кустик, Хрум, Щёлкун, Ползун, Щетина, Мох, Рокот, Ежевика, Стрекот, Бурчун, Топтыгин, Нюхач
names.human = Жерар, Роланд, Конрад, Альберт, Тристан, Экхарт, Гавейн, Ланселот, Парцифаль, Гуго, Бертрам, Готфрид, Ортвин, Балдуин, Рейнольд, Вильфрид, Дитрих, Жильбер, Руперт, Ратибор
epithets.mid = Костолом, Грязнопят, Крысогрыз, Чумазорыл, Камнежуй, Черепокол, Смердокулак, Ржавокост, Брюхобой, Лягун, Плеснебород, Грязекоготь, Соплемёт, Ухогрыз, Коленолом, Червеед
epithets.high = Цареубийца, Рокорук, Ужасозуб, Мирокрушитель, Героебой, Бурекулак, Ночестрах, Душежнец, Железнодлань, Градолом, Драконобой, Адоход, Теневладыка, Крововенец, Рокопад, Звездолом
name.epithet = {0} {1}
msg.creature_fallen = Пал твой боец: {0}. Памятный камень хранит имя.
tile.memorial = Памятный камень
tile.memorial_info = Здесь покоится {0} ({1}, уровень {2}).
# hall of the fallen
hall.title = Зал павших
hall.count = Павших существ в памяти: {0}
hall.empty = Пока никто не пал. Пусть так и будет.
hall.kills = Убито героев: {0}
ov.fallen = Павшие ({0})
ov.living = Живые
cause.slain = убийца: {0}
cause.lava = гибель в лаве
cause.water = утопление
cause.snare = гибель в капкане
cause.lightning = удар молнии
cause.fell = гибель в бою
+75
View File
@@ -264,6 +264,8 @@ val.medium = Stredná
val.high = Vysoká val.high = Vysoká
val.normal = Normálna val.normal = Normálna
val.large = Veľká val.large = Veľká
val.small = Malá
val.auto_plain = Automaticky
# ---- key binding names # ---- key binding names
key.cam_forward = Kamera dopredu key.cam_forward = Kamera dopredu
@@ -1374,6 +1376,8 @@ blk.reveal = Odhaliť
blk.camera = Ukázať blk.camera = Ukázať
blk.gold = Zlato + blk.gold = Zlato +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Počasie na povrchu
blk.talentxp = Skúsenosti talentov +
blk.if = Ak blk.if = Ak
blk.repeat = Opakovať blk.repeat = Opakovať
blk.trigger = Prepnúť spúšťač blk.trigger = Prepnúť spúšťač
@@ -1393,6 +1397,8 @@ blk.help.reveal = Odhalí hmlu vojny nad zónou.
blk.help.camera = Presunie kameru na zónu. blk.help.camera = Presunie kameru na zónu.
blk.help.gold = Dá (alebo vezme) zlato. blk.help.gold = Dá (alebo vezme) zlato.
blk.help.mana = Dá (alebo vezme) manu. blk.help.mana = Dá (alebo vezme) manu.
blk.help.weather = Nastaví počasie na povrchu (jasno, dážď, hmla alebo búrka) na toľko sekúnd; 0 ho ponechá, kým ho iný blok nezmení.
blk.help.talentxp = Dá hráčovi skúsenosti pre pánove talenty.
blk.help.if = Spustí vnútorné bloky, len ak platí podmienka, inak tie pod "inak". blk.help.if = Spustí vnútorné bloky, len ak platí podmienka, inak tie pod "inak".
blk.help.repeat = Spustí vnútorné bloky niekoľkokrát. blk.help.repeat = Spustí vnútorné bloky niekoľkokrát.
blk.help.trigger = Zapne spúšťač (môže sa spustiť znova) alebo ho vypne. blk.help.trigger = Zapne spúšťač (môže sa spustiť znova) alebo ho vypne.
@@ -1415,6 +1421,7 @@ blk.v.or = alebo
blk.v.not = nie blk.v.not = nie
blk.v.bool = Áno/nie blk.v.bool = Áno/nie
blk.v.eventkind = Spúšťacia jednotka je blk.v.eventkind = Spúšťacia jednotka je
blk.v.weatheris = Počasie na povrchu je
blk.w_at = na blk.w_at = na
blk.w_by = o blk.w_by = o
blk.w_in = v blk.w_in = v
@@ -1463,6 +1470,7 @@ blk.until = Opakovať, kým
blk.dountil = Opakovať, potom overiť: kým blk.dountil = Opakovať, potom overiť: kým
blk.wait = Čakať blk.wait = Čakať
blk.w_seconds = sekúnd blk.w_seconds = sekúnd
blk.w_for = na
blk.help.until = Spúšťa vnútorné bloky znova a znova, kým neplatí podmienka - overuje sa pred každým behom, takže ak už platí, vnútro sa nespustí vôbec. blk.help.until = Spúšťa vnútorné bloky znova a znova, kým neplatí podmienka - overuje sa pred každým behom, takže ak už platí, vnútro sa nespustí vôbec.
blk.help.dountil = Spustí vnútorné bloky, potom overí podmienku a opakuje, kým neplatí - vnútro prebehne vždy aspoň raz. blk.help.dountil = Spustí vnútorné bloky, potom overí podmienku a opakuje, kým neplatí - vnútro prebehne vždy aspoň raz.
blk.help.wait = Pozastaví tento skript na niekoľko sekúnd, potom pokračuje ďalším blokom (aj vnútri slučiek). blk.help.wait = Pozastaví tento skript na niekoľko sekúnd, potom pokračuje ďalším blokom (aj vnútri slučiek).
@@ -1693,3 +1701,70 @@ tut.slap = Krivoprst: Ha! Počul si? Niečo prichádza z východu. Priprav sa: b
tut.obj6 = Poraz dvoch rytierov. tut.obj6 = Poraz dvoch rytierov.
tut.knights = Krivoprst: Dvaja rytieri! Pošli na nich tvory útočnou vlajkou (Rozkazy) alebo ich zasiahni bleskom. tut.knights = Krivoprst: Dvaja rytieri! Pošli na nich tvory útočnou vlajkou (Rozkazy) alebo ich zasiahni bleskom.
tut.win = Krivoprst: Výborne, pane! To je všetko. No - takmer. Zvyšok povie kampaň. Pristavím vodu na čaj. tut.win = Krivoprst: Výborne, pane! To je všetko. No - takmer. Zvyšok povie kampaň. Pristavím vodu na čaj.
# master talents
cat.master = Pán
talent.title = Pánove talenty
talent.points = Voľné body talentov: {0}
talent.next = {0}/{1} skúseností do ďalšieho bodu
talent.all = Všetky talenty sú naučené.
talent.builder = Staviteľ
talent.warlord = Vojvodca
talent.warlock = Černokňažník
talent.merchant = Obchodník
talent.builder.desc = Čertíci kopú o {0}% rýchlejšie, miestnosti stoja o {1}% menej.
talent.warlord.desc = Tvory udierajú o {0}% silnejšie a dostávajú o {1}% menej poškodenia.
talent.warlock.desc = Mana prúdi o {0}% rýchlejšie, kúzla stoja o {1}% menej.
talent.merchant.desc = Mzdy sú o {0}% nižšie, doručené poklady majú o {1}% vyššiu hodnotu.
talent.none = Zatiaľ nenaučené.
talent.then = Ďalšia úroveň: {0}
msg.talent_point = Nový bod talentu! Použi ho v okne pána.
msg.talent_learned = {0}: úroveň {1} naučená.
# weather above ground
weather.fair = Jasno
weather.rain = Dážď
weather.fog = Hmla
weather.storm = Búrka
msg.weather_clear = Na povrchu sa vyjasňuje.
msg.weather_rain = Na povrchu začína pršať - oheň sa nechytí.
msg.weather_fog = Nad krajinou sa valí hmla - vonku každý vidí menej.
msg.weather_storm = Na povrchu sa blíži búrka! Blesky zasahujú jednotky a stromy.
msg.lightning_unit = Zásah bleskom: {0}!
# creature names
names.imp = Omrvinka, Sopliak, Hrabko, Blatko, Špinďúr, Prcek, Rýpal, Čuňo, Smradík, Škrabko, Hruda, Čľapka, Mazúr, Fľakáč, Hrudka, Lopatka, Ňufák, Drobček, Šmudlo, Čmudo
names.kobold = Ňafko, Štípal, Zubáč, Prašivec, Hryzko, Hrdzák, Šmyk, Kúsok, Čumil, Potkan, Handrár, Bodliak, Pazúrik, Rýpavec, Korienok, Chichot, Strkal, Zúbok, Chrup, Škrabák
names.spider = Lezúň, Chlpka, Pavúčik, Štekliak, Sieťka, Osemnôžka, Niťka, Strašidielko, Šmatlík, Hodvábka, Tkáčka, Skokan, Chlpáčik, Tekla, Cievka, Chňapka, Klbko, Lepka, Vrtko, Ihlička
names.mage = Bartolomej, Ignác, Leopold, Teobald, Vojtech, Hieronym, Valdemar, Gotfríd, Kornel, Otokar, Florián, Engelbert, Pankrác, Ruprecht, Konštantín, Euzébius, Servác, Barnabáš, Archibald, Bonifác
names.ogre = Hromotĺk, Peň, Bum, Balvan, Tupko, Klát, Buchal, Mlátič, Dupák, Drevák, Čľup, Bachor, Kváder, Bruchoš, Kyjak, Vôl, Rachot, Kus, Bubon, Gorbo
names.jailer = Zamračenec, Závora, Okov, Zámok, Žalár, Železobrad, Krúžok, Skrutka, Prísny, Mrežník, Mrzút, Hák, Slučka, Kľučiar, Temný, Rinčal, Klada, Strážnik, Klietka, Horký
names.priestess = Žeravá, Plamena, Popolica, Serafína, Vatra, Ohnivka, Morena, Brunhilda, Ignia, Iskra, Šarlátka, Žiarka, Havranka, Lilit, Trúd, Izolda, Planúca, Nyx, Večernica, Plamenná
names.chainbreaker = Reťazolam, Ulfgar, Železoduch, Brannok, Kragmor, Valdrek, Tŕň, Gorath, Nespútaný, Rurik, Torvald, Hagen, Durnok, Črep, Plameň, Vlkoslav, Korrun, Arvak, Osloboditeľ, Halvard
names.bat = Pišťal, Trepotal, Pípa, Šuchot, Ozvena, Mávalka, Súmrak, Pišťalka, Cvrlík, Zúbik, Vír, Netopierik, Moľa, Pípaľ, Šumko, Visáčik, Zamat, Šmyk, Kvičal, Blesk
names.gargoyle = Žula, Bridlica, Čadič, Kamenár, Malta, Okruhliak, Štrk, Kremeň, Škára, Ruina, Kremenec, Pemza, Rímsa, Hrča, Kamenobrad, Bručúň, Štít, Chrlič, Dláto, Sokel
names.owl = Výr, Múdrik, Brko, Bartold, Minerva, Tobiáš, Húhú, Sovička, Pierko, Povzdych, Mysliteľ, Starešina, Kuvik, Noctua, Sovoslav, Pergamen, Šero, Plamienka, Profesor, Páperie
names.wyrm = Vermitrax, Popolochrtan, Spáleniak, Kalderon, Ignivar, Žeroroh, Drakkar, Pyrrhos, Obsidián, Žeravochrtan, Vulkanor, Pálizub, Malgrath, Tyrax, Inferno, Síra, Kaldor, Fafnir, Ragnor, Uhlík
names.cogling = Ozubák, Koliesko, Skrutôčka, Rapkáč, Nitík, Prevod, Kutil, Vynálezík, Kľúčik, Pastorok, Cinkal, Šťúral, Matička, Bzučák, Kľučka, Vrtuľka, Príruba, Cvak, Tiktak, Vrut
names.revenant = Clivota, Horkosť, Popolavý, Ston, Prázdno, Žiaľ, Bledý, Trud, Tieň, Prach, Hrobár, Súmrak, Krypta, Túžba, Bdenie, Mohyla, Rubáš, Elégia, Kal, Pustatina
names.beast = Chlpáč, Maco, Bruno, Huňáč, Mľaskal, Machoň, Bručko, Medvedík, Krovie, Chrúm, Cvakal, Lezec, Štetinka, Mach, Burácal, Ostružina, Cvrkal, Brblal, Brumko, Ňuchal
names.human = Gerhard, Roland, Konrád, Albrecht, Tristan, Eckhart, Gawain, Lancelot, Parsifal, Hugo, Bertram, Gotfríd, Ortvín, Balduin, Rajnold, Vilfríd, Dietrich, Gilbert, Ruprecht, Bystrík
epithets.mid = Kostilam, Blatonoh, Potkanohryz, Špinotvár, Kamenožrút, Lebkolam, Smradopäsť, Hrdzokĺb, Bruchobij, Holeňokop, Plesňobrad, Špinopazúr, Soplomet, Uchohryz, Kolenolam, Červojed
epithets.high = Kráľovrah, Skazopäsť, Hrôzozub, Svetoborec, Hrdinobij, Búrkopäsť, Morovládca, Dušovrah, Železosrdce, Hradoborec, Drakobijec, Pekelník, Tieňovládca, Krvokorunec, Skazotok, Hviezdolam
name.epithet = {0} {1}
msg.creature_fallen = Padol tvoj tvor: {0}. Pamätný kameň uchová meno.
tile.memorial = Pamätný kameň
tile.memorial_info = Tu odpočíva {0} ({1}, úroveň {2}).
# hall of the fallen
hall.title = Sieň padlých
hall.count = Padlé tvory v pamäti: {0}
hall.empty = Zatiaľ nikto nepadol. Nech to tak vydrží.
hall.kills = Zabití hrdinovia: {0}
ov.fallen = Padlí ({0})
ov.living = Živí
cause.slain = vrah: {0}
cause.lava = smrť v láve
cause.water = utopenie
cause.snare = smrť v pasci
cause.lightning = zásah bleskom
cause.fell = smrť v boji
+75
View File
@@ -264,6 +264,8 @@ val.medium = Средње
val.high = Високо val.high = Високо
val.normal = Нормално val.normal = Нормално
val.large = Велико val.large = Велико
val.small = Мало
val.auto_plain = Аутоматски
# ---- key binding names # ---- key binding names
key.cam_forward = Камера напред key.cam_forward = Камера напред
@@ -1374,6 +1376,8 @@ blk.reveal = Откриј
blk.camera = Покажи blk.camera = Покажи
blk.gold = Злато + blk.gold = Злато +
blk.mana = Мана + blk.mana = Мана +
blk.weather = Време на површини
blk.talentxp = Искуство талената +
blk.if = Ако blk.if = Ако
blk.repeat = Понови blk.repeat = Понови
blk.trigger = Пребаци окидач blk.trigger = Пребаци окидач
@@ -1393,6 +1397,8 @@ blk.help.reveal = Подиже маглу рата над зоном.
blk.help.camera = Помера камеру на зону. blk.help.camera = Помера камеру на зону.
blk.help.gold = Даје (или одузима) злато. blk.help.gold = Даје (или одузима) злато.
blk.help.mana = Даје (или одузима) ману. blk.help.mana = Даје (или одузима) ману.
blk.help.weather = Поставља време на површини (ведро, киша, магла или олуја) на толико секунди; 0 га задржава док га други блок не промени.
blk.help.talentxp = Даје играчу искуство за господареве таленте.
blk.help.if = Покреће унутрашње блокове само ако услов важи, иначе оне под «иначе». blk.help.if = Покреће унутрашње блокове само ако услов важи, иначе оне под «иначе».
blk.help.repeat = Покреће унутрашње блокове неколико пута. blk.help.repeat = Покреће унутрашње блокове неколико пута.
blk.help.trigger = Укључује окидач (може поново да се активира) или га искључује. blk.help.trigger = Укључује окидач (може поново да се активира) или га искључује.
@@ -1415,6 +1421,7 @@ blk.v.or = или
blk.v.not = не blk.v.not = не
blk.v.bool = Да/не blk.v.bool = Да/не
blk.v.eventkind = Јединица окидача је blk.v.eventkind = Јединица окидача је
blk.v.weatheris = Време на површини је
blk.w_at = на blk.w_at = на
blk.w_by = за blk.w_by = за
blk.w_in = у blk.w_in = у
@@ -1463,6 +1470,7 @@ blk.until = Понављај док
blk.dountil = Понови, затим провери: док blk.dountil = Понови, затим провери: док
blk.wait = Чекај blk.wait = Чекај
blk.w_seconds = секунди blk.w_seconds = секунди
blk.w_for = на
blk.help.until = Покреће унутрашње блокове изнова и изнова док услов не почне да важи - проверава се пре сваког пролаза, па ако већ важи, унутрашњост се уопште не покреће. blk.help.until = Покреће унутрашње блокове изнова и изнова док услов не почне да важи - проверава се пре сваког пролаза, па ако већ важи, унутрашњост се уопште не покреће.
blk.help.dountil = Покреће унутрашње блокове, затим проверава услов и понавља док не почне да важи - унутрашњост се увек покрене бар једном. blk.help.dountil = Покреће унутрашње блокове, затим проверава услов и понавља док не почне да важи - унутрашњост се увек покрене бар једном.
blk.help.wait = Паузира ову скрипту неколико секунди, а затим наставља следећим блоком (и унутар петљи). blk.help.wait = Паузира ову скрипту неколико секунди, а затим наставља следећим блоком (и унутар петљи).
@@ -1693,3 +1701,70 @@ tut.slap = Кривопрст: Ха! Јеси ли чуо? Нешто долаз
tut.obj6 = Порази два витеза. tut.obj6 = Порази два витеза.
tut.knights = Кривопрст: Два витеза! Пошаљи створења заставом напада (Наредбе) или их удари муњом. tut.knights = Кривопрст: Два витеза! Пошаљи створења заставом напада (Наредбе) или их удари муњом.
tut.win = Кривопрст: Браво, господару! То је све. Па - скоро. Остатак ће испричати кампања. Идем да ставим воду за чај. tut.win = Кривопрст: Браво, господару! То је све. Па - скоро. Остатак ће испричати кампања. Идем да ставим воду за чај.
# master talents
cat.master = Господар
talent.title = Господареви таленти
talent.points = Слободни бодови талента: {0}
talent.next = {0}/{1} искуства до следећег бода
talent.all = Сви таленти су научени.
talent.builder = Градитељ
talent.warlord = Војсковођа
talent.warlock = Вештац
talent.merchant = Трговац
talent.builder.desc = Ђаволци копају {0}% брже, собе коштају {1}% мање.
talent.warlord.desc = Створења ударају {0}% јаче и примају {1}% мање штете.
talent.warlock.desc = Мана тече {0}% брже, чини коштају {1}% мање.
talent.merchant.desc = Плате су {0}% ниже, достављено благо вреди {1}% више.
talent.none = Још није научено.
talent.then = Следећи ниво: {0}
msg.talent_point = Нови бод талента! Потроши га у прозору господара.
msg.talent_learned = {0}: научен ниво {1}.
# weather above ground
weather.fair = Ведро
weather.rain = Киша
weather.fog = Магла
weather.storm = Олуја
msg.weather_clear = На површини се разведрава.
msg.weather_rain = На површини почиње киша - ватра се не хвата.
msg.weather_fog = Магла се спушта над земљу - напољу сви виде мање.
msg.weather_storm = На површини се спрема олуја! Муње погађају јединице и стабла.
msg.lightning_unit = Муња је погодила: {0}!
# creature names
names.imp = Мрвица, Шмркљо, Копко, Блатко, Прљко, Чупко, Ровко, Гњецко, Смрдко, Гребко, Квргица, Брља, Мазало, Пацко, Грудвица, Лопатко, Њушко, Копуша, Мрљко, Шљампа
names.kobold = Кевко, Штипко, Зубоња, Шуго, Гризо, Рђо, Фића, Угризић, Зјало, Пацов, Крпо, Бодљо, Канџо, Гњаватор, Корен, Кикотко, Гурко, Зарез, Крцко, Огребо
names.spider = Гмизалица, Вунко, Паучић, Шкакљо, Мрежица, Осмонога, Нитица, Језица, Прчко, Свиленка, Ткаља, Скокић, Длакавко, Текла, Калемко, Хватко, Клупко, Лепко, Вртко, Иглица
names.mage = Вартоломеј, Игњатије, Леополд, Теобалд, Адалберт, Јероним, Валдемар, Готфрид, Корнелије, Отокар, Флоријан, Енгелберт, Панкратије, Рупрехт, Константин, Јевсевије, Северин, Варнава, Арчибалд, Витомир
names.ogre = Громо, Клада, Бумбо, Грудо, Тупко, Глупко, Треско, Батина, Газило, Тикван, Пљус, Дебељко, Бухто, Трбоња, Тољага, Волина, Тутњо, Кврго, Бубањ, Горбо
names.jailer = Мрки, Засун, Оков, Катанац, Тамница, Гвозденбрад, Карика, Сворњак, Строги, Решетко, Намргођен, Кука, Омча, Кључар, Мрачни, Звекан, Букагија, Стражар, Кавез, Горки
names.priestess = Жар, Пламена, Пепела, Серафина, Буктиња, Ватрена, Морана, Брунхилда, Игнија, Искра, Гримизна, Жарка, Гавранка, Лилит, Труд, Изолда, Огњена, Никс, Вечерња, Пламенка
names.chainbreaker = Ломиланац, Улфгар, Гвоздендух, Бранок, Крагмор, Валдрек, Трн, Горат, Неспутани, Рурик, Торвалд, Хаген, Дурнок, Крхотина, Пламен, Вукослав, Корун, Арвак, Ослободилац, Халвард
names.bat = Пијук, Лепршо, Цико, Шушур, Јека, Махалица, Сумрак, Звиждо, Цврчко, Зубић, Вртлог, Слепишић, Мољац, Пијукић, Шушо, Виско, Сомот, Шмуго, Цича, Муња
names.gargoyle = Гранит, Шкриљац, Базалт, Каменко, Малтер, Шљунко, Туцаник, Кремен, Фуга, Рушевина, Кварц, Пловућац, Венац, Квргоња, Каменобрад, Мрзовоља, Забат, Ригало, Длето, Постоље
names.owl = Ћук, Мудрица, Перце, Бартолд, Минерва, Товија, Хуху, Совица, Перић, Уздах, Мислилац, Старешина, Јејина, Ноктуа, Совослав, Пергамент, Сутон, Ушара, Професор, Паперје
names.wyrm = Вермитракс, Пепељави, Палеж, Калдерон, Игнивар, Жаророг, Дракар, Пир, Опсидијан, Жароуст, Вулканор, Пализуб, Малграт, Тиракс, Инферно, Сумпор, Калдор, Фафнир, Рагнор, Угарак
names.cogling = Зупчаник, Точкић, Шрафић, Чегртаљка, Закивак, Пренос, Мајсторко, Справица, Кључић, Погон, Звецкало, Петљо, Клинчић, Зујо, Ручица, Чигра, Прирубница, Шкљоц, Тиктак, Навојко
names.revenant = Сета, Горчина, Пепељко, Јаук, Шупаљ, Туга, Бледи, Јад, Сенка, Прах, Гробар, Смркнуће, Крипта, Чежња, Бдење, Хумка, Покров, Елегија, Талог, Пустош
names.beast = Чупавко, Медо, Бруно, Рундо, Мљацко, Маховинко, Мрмљо, Медведић, Грмко, Хрско, Шкљоцко, Гмизо, Чекињо, Маховина, Тутањ, Купина, Цврчо, Гунђало, Медоња, Њушкало
names.human = Жерар, Роланд, Конрад, Алберт, Тристан, Екхарт, Гавин, Ланселот, Парсифал, Хуго, Бертрам, Готфрид, Ортвин, Балдуин, Рајнолд, Вилфрид, Дитрих, Жилбер, Рупрехт, Драгутин
epithets.mid = Ломикост, Блатоног, Пацовогриз, Прљолица, Каменождер, Лобањолом, Смрдошака, Рђозглоб, Трбухобој, Голенобој, Буђобрад, Прљоканџа, Шмркљобацач, Ухогриз, Коленолом, Црвождер
epithets.high = Краљоубица, Кобошака, Страхозуб, Светолом, Јунакомор, Олујошака, Ноћнострах, Душожетелац, Гвожђесрце, Градолом, Змајоубица, Пакленоход, Сеновладар, Крвокрун, Кобноплима, Звездолом
name.epithet = {0} {1}
msg.creature_fallen = Пало је твоје створење: {0}. Спомен-камен чува име.
tile.memorial = Спомен-камен
tile.memorial_info = Овде почива {0} ({1}, ниво {2}).
# hall of the fallen
hall.title = Дворана палих
hall.count = Палих створења у сећању: {0}
hall.empty = Још нико није пао. Нека тако и остане.
hall.kills = Убијених хероја: {0}
ov.fallen = Пали ({0})
ov.living = Живи
cause.slain = убица: {0}
cause.lava = смрт у лави
cause.water = утапање
cause.snare = смрт у замци
cause.lightning = удар муње
cause.fell = погибија у боју
+75
View File
@@ -264,6 +264,8 @@ val.medium = Medel
val.high = Hög val.high = Hög
val.normal = Normal val.normal = Normal
val.large = Stor val.large = Stor
val.small = Liten
val.auto_plain = Automatisk
# ---- key binding names # ---- key binding names
key.cam_forward = Kamera framåt key.cam_forward = Kamera framåt
@@ -1374,6 +1376,8 @@ blk.reveal = Avslöja
blk.camera = Visa blk.camera = Visa
blk.gold = Guld + blk.gold = Guld +
blk.mana = Mana + blk.mana = Mana +
blk.weather = Vädret ovan jord
blk.talentxp = Talangerfarenhet +
blk.if = Om blk.if = Om
blk.repeat = Upprepa blk.repeat = Upprepa
blk.trigger = Växla trigger blk.trigger = Växla trigger
@@ -1393,6 +1397,8 @@ blk.help.reveal = Lyfter krigsdimman över en zon.
blk.help.camera = Flyttar kameran till en zon. blk.help.camera = Flyttar kameran till en zon.
blk.help.gold = Ger (eller tar) guld. blk.help.gold = Ger (eller tar) guld.
blk.help.mana = Ger (eller tar) mana. blk.help.mana = Ger (eller tar) mana.
blk.help.weather = Sätter vädret ovan jord (klart, regn, dimma eller storm) i så många sekunder; 0 behåller det tills ett annat block ändrar det.
blk.help.talentxp = Ger spelaren erfarenhet till mästartalangerna.
blk.help.if = Kör blocken inuti bara om villkoret gäller, annars de under "annars". blk.help.if = Kör blocken inuti bara om villkoret gäller, annars de under "annars".
blk.help.repeat = Kör blocken inuti flera gånger. blk.help.repeat = Kör blocken inuti flera gånger.
blk.help.trigger = Slår på en trigger (den får utlösas igen) eller av. blk.help.trigger = Slår på en trigger (den får utlösas igen) eller av.
@@ -1415,6 +1421,7 @@ blk.v.or = eller
blk.v.not = inte blk.v.not = inte
blk.v.bool = Ja/nej blk.v.bool = Ja/nej
blk.v.eventkind = Triggerenheten är en blk.v.eventkind = Triggerenheten är en
blk.v.weatheris = Vädret ovan jord är
blk.w_at = vid blk.w_at = vid
blk.w_by = med blk.w_by = med
blk.w_in = i blk.w_in = i
@@ -1463,6 +1470,7 @@ blk.until = Upprepa tills
blk.dountil = Upprepa, kolla sedan: tills blk.dountil = Upprepa, kolla sedan: tills
blk.wait = Vänta blk.wait = Vänta
blk.w_seconds = sekunder blk.w_seconds = sekunder
blk.w_for = i
blk.help.until = Kör blocken inuti om och om igen tills villkoret gäller - kontrolleras före varje varv, så om det redan gäller körs insidan inte alls. blk.help.until = Kör blocken inuti om och om igen tills villkoret gäller - kontrolleras före varje varv, så om det redan gäller körs insidan inte alls.
blk.help.dountil = Kör blocken inuti, kontrollerar sedan villkoret och upprepar tills det gäller - insidan körs alltid minst en gång. blk.help.dountil = Kör blocken inuti, kontrollerar sedan villkoret och upprepar tills det gäller - insidan körs alltid minst en gång.
blk.help.wait = Pausar det här skriptet några sekunder, sedan fortsätter det med nästa block (även inuti loopar). blk.help.wait = Pausar det här skriptet några sekunder, sedan fortsätter det med nästa block (även inuti loopar).
@@ -1693,3 +1701,70 @@ tut.slap = Crookfinger: Ha! Hörde du det? Något kommer från öster. Gör dig
tut.obj6 = Besegra de två riddarna. tut.obj6 = Besegra de två riddarna.
tut.knights = Crookfinger: Två riddare! Skicka dina varelser med anfallsflaggan (Order) eller träffa dem med blixten. tut.knights = Crookfinger: Två riddare! Skicka dina varelser med anfallsflaggan (Order) eller träffa dem med blixten.
tut.win = Crookfinger: Bra gjort, mästare! Det är allt. Nåja – nästan. Kampanjen berättar resten. Jag sätter på tevattnet. tut.win = Crookfinger: Bra gjort, mästare! Det är allt. Nåja – nästan. Kampanjen berättar resten. Jag sätter på tevattnet.
# master talents
cat.master = Mästare
talent.title = Mästartalanger
talent.points = Lediga talangpoäng: {0}
talent.next = {0}/{1} erfarenhet till nästa poäng
talent.all = Alla talanger är inlärda.
talent.builder = Byggmästare
talent.warlord = Krigsherre
talent.warlock = Häxmästare
talent.merchant = Köpman
talent.builder.desc = Hobar gräver {0}% snabbare, rum kostar {1}% mindre.
talent.warlord.desc = Varelser slår {0}% hårdare och tar {1}% mindre skada.
talent.warlock.desc = Mana flödar {0}% snabbare, besvärjelser kostar {1}% mindre.
talent.merchant.desc = Lönerna är {0}% lägre, levererade skatter är värda {1}% mer.
talent.none = Inte inlärd än.
talent.then = Nästa nivå: {0}
msg.talent_point = En ny talangpoäng! Använd den i mästarfönstret.
msg.talent_learned = {0} nivå {1} inlärd.
# weather above ground
weather.fair = Klart
weather.rain = Regn
weather.fog = Dimma
weather.storm = Storm
msg.weather_clear = Ovan jord klarnar det upp.
msg.weather_rain = Ovan jord börjar det regna - eld tar inte fäste.
msg.weather_fog = Dimma drar in över landet - ute ser alla sämre.
msg.weather_storm = Ovan jord drar en storm in! Blixten slår ner i enheter och träd.
msg.lightning_unit = Blixten träffade {0}!
# creature names
names.imp = Smula, Snorre, Grävis, Sörja, Ludd, Pyttan, Rotfrid, Snusk, Busen, Klösis, Smulan, Kladd, Gyttja, Pillis, Knölen, Spaden, Nosen, Pysen, Fläcken, Grums
names.kobold = Glefs, Nyp, Griptand, Skabb, Hugg, Rostig, Svisch, Bitis, Gapis, Råttan, Trasa, Tagg, Klon, Bråk, Rask, Fniss, Knuffis, Hack, Knäck, Skråma
names.spider = Krypis, Ullben, Spindeln, Kittla, Nätta, Åttafot, Tråden, Ryslig, Pillan, Silke, Väverskan, Hoppsan, Luddben, Tekla, Spolen, Nappa, Garnnystan, Klister, Vimsan, Nålen
names.mage = Bartholomeus, Ignatius, Leopold, Theobald, Adalbert, Hieronymus, Valdemar, Gottfrid, Cornelius, Ottokar, Florentin, Engelbrekt, Pankratius, Rupertus, Konstantin, Eusebius, Severin, Barnabas, Archibald, Vitus
names.ogre = Brumm, Kloss, Bums, Klump, Dunk, Tönt, Tjock, Klampen, Tramp, Träskalle, Plums, Mops, Bulle, Isterbuk, Klubba, Oxen, Rumla, Klabb, Hump, Gorbo
names.jailer = Grym, Regel, Länken, Lås, Fängelse, Järnskägg, Bygel, Bult, Sträng, Galler, Grinig, Krok, Snara, Nyckel, Dyster, Klirr, Stockman, Väktare, Bur, Bitter
names.priestess = Glöd, Flamma, Aska, Serafina, Låga, Eldine, Morwen, Brynhild, Ignia, Gnista, Scharlakan, Funka, Ravna, Lilith, Fnöske, Isolde, Brandhild, Nyx, Vesper, Glimt
names.chainbreaker = Kedjebrytare, Ulfgar, Järnsjäl, Brannok, Kragmor, Valdrek, Torn, Gorath, Obunden, Rurik, Torvald, Hagen, Durnok, Splitter, Brand, Ulfrik, Korrun, Arvak, Sigurd, Halvard
names.bat = Pip, Fladder, Pipsan, Susen, Eko, Flaxa, Skymning, Vissla, Kvitter, Tandis, Virvel, Fladdermus, Mal, Pipis, Susa, Dingla, Sammet, Smyg, Pipet, Blixten
names.gargoyle = Granit, Skiffer, Basalt, Grimbert, Murbruk, Kisel, Grus, Flinta, Fog, Rasmassa, Kvarts, Pimpsten, Gesims, Knöl, Stenskägg, Sur, Gavel, Vattenspyare, Mejsel, Sockel
names.owl = Uven, Viskis, Fjäderpenna, Bartholdus, Minerva, Tobias, Huhu, Ugglalia, Fjäderik, Suck, Grubblis, Äldste, Kattuggla, Noctua, Ugglebert, Pergament, Skymning, Nattuggla, Professorn, Dun
names.wyrm = Vermithrax, Askgap, Sveda, Calderon, Ignivar, Glödhorn, Drakkar, Pyrrhus, Obsidian, Glödgap, Vulkanor, Brandtand, Malgrath, Tyrax, Inferno, Svavel, Kaldor, Fafner, Ragnor, Sinderos
names.cogling = Kugghjul, Hjulet, Skruven, Spärrhake, Bult, Växel, Fixaren, Grejen, Nyckeln, Drev, Klirra, Pillaren, Nita, Snurra, Vev, Snurrebass, Fläns, Klickklack, Ticktack, Nippel
names.revenant = Morgon, Agg, Askgrå, Klagan, Ihålig, Sorg, Blek, Kummer, Skugga, Mylla, Gravman, Skymningen, Krypta, Vemod, Vaka, Hög, Liksvepning, Elegi, Drägg, Öde
names.beast = Lurv, Brummbjörn, Bruno, Raggis, Mumsa, Mossrygg, Brummer, Ursa, Knorr, Knaster, Klickare, Krypare, Borst, Mossa, Mullret, Törnsnår, Knirr, Gnällis, Nalle, Nosis
names.human = Gerhard, Roland, Konrad, Albrekt, Tristan, Eskil, Gavin, Lancelot, Pål, Håkan, Bertil, Gottfrid, Erik, Sverker, Ragnar, Vilfred, Ditlev, Gilbert, Rasmus, Birger
epithets.mid = Benkrossare, Gyttjefot, Råttbitare, Smutsfejs, Stengnagare, Skallbankare, Stinknäve, Rostknoge, Magboxare, Smalbensspark, Mögelskägg, Smutsklo, Snorslunga, Örongnagare, Knäknäckare, Maskätare
epithets.high = Kungadråpare, Ödeshand, Skräcktand, Världskrossare, Hjältebane, Stormnäve, Nattskräck, Själaskördare, Järnhjärta, Borgkrossare, Drakdräpare, Helvetesvandrare, Skuggfurste, Blodkrona, Dödsflod, Stjärnkrossare
name.epithet = {0} {1}
msg.creature_fallen = {0} har fallit. En minnessten bevarar namnet.
tile.memorial = Minnessten
tile.memorial_info = Här vilar {0} ({1}, nivå {2}).
# hall of the fallen
hall.title = De stupades sal
hall.count = Stupade varelser i minnet: {0}
hall.empty = Ingen har stupat än. Må det länge förbli så.
hall.kills = Dräpta hjältar: {0}
ov.fallen = Stupade ({0})
ov.living = Levande
cause.slain = dräpt av: {0}
cause.lava = brann i lava
cause.water = drunknade
cause.snare = klev i en fälla
cause.lightning = träffad av blixten
cause.fell = stupade
+7
View File
@@ -4,6 +4,13 @@ server {
index index.html; index index.html;
# test build: always reload fresh files # test build: always reload fresh files
add_header Cache-Control "no-cache"; add_header Cache-Control "no-cache";
# compressed downloads: the big files are packed once at build time (*.gz, see Dockerfile),
# the rest on the fly (the wasm goes from about 8.8 MB to 2.6 MB)
gzip_static on;
gzip on;
gzip_types application/wasm application/javascript text/css application/json application/manifest+json image/svg+xml;
gzip_min_length 1024;
gzip_vary on;
location / { location / {
try_files $uri $uri/ =404; try_files $uri $uri/ =404;
} }
+28 -1
View File
@@ -63,8 +63,13 @@ pub struct Profile {
pub totals: BTreeMap<String, f64>, pub totals: BTreeMap<String, f64>,
#[serde(default)] #[serde(default)]
pub unlocked: BTreeMap<String, u64>, // achievement id ("heroes.2", "flawless") -> unix time pub unlocked: BTreeMap<String, u64>, // achievement id ("heroes.2", "flawless") -> unix time
#[serde(default)]
pub hall: Vec<crate::names::Fallen>, // the hall of the fallen over all games (newest last)
} }
// the hall of the fallen keeps this many
pub const HALL_MAX: usize = 200;
impl Profile { impl Profile {
pub fn total(&self, id: &str) -> f64 { pub fn total(&self, id: &str) -> f64 {
self.totals.get(id).copied().unwrap_or(0.0) self.totals.get(id).copied().unwrap_or(0.0)
@@ -80,6 +85,15 @@ impl Profile {
let e = self.unlocked.entry(k.clone()).or_insert(*t); let e = self.unlocked.entry(k.clone()).or_insert(*t);
*e = (*e).min(*t); *e = (*e).min(*t);
} }
// the fallen of both, once each, in the order they fell
for f in &other.hall {
if !self.hall.iter().any(|h| h.name == f.name && h.kind == f.kind && h.when == f.when) {
self.hall.push(f.clone());
}
}
self.hall.sort_by_key(|h| h.when);
let extra = self.hall.len().saturating_sub(HALL_MAX);
self.hall.drain(..extra);
} }
// unlocks what the totals have reached; returns the new ones // unlocks what the totals have reached; returns the new ones
@@ -117,6 +131,7 @@ pub struct Context {
pub struct Tracker { pub struct Tracker {
pub profile: Profile, pub profile: Profile,
flushed: Vec<f32>, flushed: Vec<f32>,
hall_seen: usize, // entries of the game's hall already in the profile
last_time: f32, last_time: f32,
ended: bool, ended: bool,
t: f32, t: f32,
@@ -126,12 +141,13 @@ pub struct Tracker {
impl Tracker { impl Tracker {
pub fn new(profile: Profile) -> Tracker { pub fn new(profile: Profile) -> Tracker {
Tracker { profile, flushed: vec![0.0; k::COUNTERS], last_time: 0.0, ended: false, t: 0.0, dirty: false, upload: false } Tracker { profile, flushed: vec![0.0; k::COUNTERS], hall_seen: 0, last_time: 0.0, ended: false, t: 0.0, dirty: false, upload: false }
} }
// a different game started (new, loaded): nothing of it has been counted yet // a different game started (new, loaded): nothing of it has been counted yet
fn restart(&mut self, g: &Game) { fn restart(&mut self, g: &Game) {
self.flushed = (0..k::COUNTERS).map(|i| g.stats.get(i)).collect(); self.flushed = (0..k::COUNTERS).map(|i| g.stats.get(i)).collect();
self.hall_seen = g.hall.len();
self.ended = g.state != 0; self.ended = g.state != 0;
} }
@@ -154,6 +170,17 @@ impl Tracker {
let played = self.t; let played = self.t;
self.t = 0.0; self.t = 0.0;
let mut new = Vec::new(); let mut new = Vec::new();
// the newly fallen go into the profile's hall
if g.hall.len() > self.hall_seen {
for f in &g.hall[self.hall_seen..] {
self.profile.hall.push(crate::names::Fallen { when: now, ..f.clone() });
}
self.hall_seen = g.hall.len();
let extra = self.profile.hall.len().saturating_sub(HALL_MAX);
self.profile.hall.drain(..extra);
self.dirty = true;
self.upload = true;
}
// the counters of the game // the counters of the game
for i in 0..k::COUNTERS { for i in 0..k::COUNTERS {
let cur = g.stats.get(i); let cur = g.stats.get(i);
+91 -12
View File
@@ -14,8 +14,9 @@ const THINK: [f32; 3] = [5.2, 3.25, 1.95]; // seconds between two thoughts (a ca
const MAX_RADIUS: [i32; 3] = [11, 15, 19]; // how far the dungeon grows around the heart const MAX_RADIUS: [i32; 3] = [11, 15, 19]; // how far the dungeon grows around the heart
const MAX_IMPS: [usize; 3] = [5, 7, 9]; const MAX_IMPS: [usize; 3] = [5, 7, 9];
const FIRST_ATTACK: [f32; 3] = [940.0, 625.0, 430.0]; // no attack before (seconds) const FIRST_ATTACK: [f32; 3] = [940.0, 625.0, 430.0]; // no attack before (seconds)
const BOLDNESS: [f32; 3] = [1.5, 1.15, 0.9]; // own army / enemy army needed to attack const BOLDNESS: [f32; 3] = [1.0, 1.2, 1.35]; // own army / enemy army needed to attack (easy is reckless, hard waits)
const MARK_REACH: i32 = 5; // an AI master marks only this close to its own ground const MARK_REACH: i32 = 5; // an AI master marks only this close to its own ground
const CREATURE_TARGET: [usize; 3] = [24, 50, 80]; // beds an AI master builds up to
const MAX_TOWERS: [usize; 3] = [1, 2, 4]; const MAX_TOWERS: [usize; 3] = [1, 2, 4];
const START_GOLD: [f32; 3] = [1500.0, 2500.0, 3500.0]; const START_GOLD: [f32; 3] = [1500.0, 2500.0, 3500.0];
@@ -51,9 +52,11 @@ impl Game {
self.ai_imps(d, cx, cy); self.ai_imps(d, cx, cy);
self.ai_dig(k, d, cx, cy); self.ai_dig(k, d, cx, cy);
self.ai_gold(cx, cy); self.ai_gold(cx, cy);
self.ai_rooms(cx, cy); self.ai_rooms(d, cx, cy);
self.ai_towers(d, cx, cy); self.ai_towers(d, cx, cy);
self.ai_fight(k, d, side, cx, cy); self.ai_fight(k, d, side, cx, cy);
let focus = self.rivals[k].focus;
self.ai_talents(focus);
} }
// the tiles an AI master may mark: within MARK_REACH tiles of its own ground // the tiles an AI master may mark: within MARK_REACH tiles of its own ground
@@ -160,18 +163,23 @@ impl Game {
// gold running low: a tunnel to the nearest vein that nobody works on yet // gold running low: a tunnel to the nearest vein that nobody works on yet
fn ai_gold(&mut self, cx: i32, cy: i32) { fn ai_gold(&mut self, cx: i32, cy: i32) {
if self.gold > 1500.0 { // gold for one and a half paydays and a little more: enough
if self.gold > self.next_payday_cost() * 1.5 + 1000.0 {
return; return;
} }
let me = self.me; let me = self.me;
// a marked vein the imps can reach (next to own ground) is being worked already // marked veins the imps can reach (next to own ground) are being worked already; one vein
let busy = (0..self.map.t.len()).any(|i| { // for every three imps at a time
let t = &self.map.t[i]; let side = me.side().min(7);
let (x, y) = xy(i); let busy = (0..self.map.t.len())
t.marked && t.vein().is_some() && DIR4.iter().any(|&(dx, dy)| self.map.get(x + dx, y + dy).map_or(false, |n| n.owner == me && n.walkable())) .filter(|&i| {
}); let t = &self.map.t[i];
if busy { let (x, y) = xy(i);
return; // a vein is being worked already t.marked && t.mark_by == side && t.vein().is_some() && DIR4.iter().any(|&(dx, dy)| self.map.get(x + dx, y + dy).map_or(false, |n| n.owner == me && n.walkable()))
})
.count();
if busy >= (self.worker_count() / 3).max(1) {
return;
} }
self.ai_mark_tunnel(cx, cy, |g: &Game, i: usize| { self.ai_mark_tunnel(cx, cy, |g: &Game, i: usize| {
let t = &g.map.t[i]; let t = &g.map.t[i];
@@ -219,7 +227,7 @@ impl Game {
} }
// the next room of the build order, where there is free own floor for it // the next room of the build order, where there is free own floor for it
fn ai_rooms(&mut self, cx: i32, cy: i32) { fn ai_rooms(&mut self, d: usize, cx: i32, cy: i32) {
for &(room, w, h, n) in BUILD.iter() { for &(room, w, h, n) in BUILD.iter() {
// only what this master has researched (the same rules as the player) // only what this master has researched (the same rules as the player)
if !self.ai_knows(crate::ui::Tool::Room(room)) { if !self.ai_knows(crate::ui::Tool::Room(room)) {
@@ -238,6 +246,29 @@ impl Game {
} }
return; return;
} }
// the base is done: a bigger treasury when it is nearly full
if self.gold > self.gold_cap() * 0.85 {
let cost = ROOM_COST[0] as f32 * 9.0;
if self.gold >= cost + 400.0 {
if let Some((x0, y0)) = self.ai_free_rect(cx, cy, 3, 3) {
self.build_rect(0, x0, y0, x0 + 2, y0 + 2);
return;
}
}
}
// more beds and food while the army may still grow (by difficulty)
if self.counts[1] >= CREATURE_TARGET[d] || self.free_beds() > 2 {
return;
}
let room = if self.counts[2] * 10 < self.counts[1] * 6 { 2 } else { 1 };
let (w, h) = (4, 3);
let cost = ROOM_COST[room] as f32 * (w * h) as f32;
if self.gold < cost + 400.0 {
return;
}
if let Some((x0, y0)) = self.ai_free_rect(cx, cy, w, h) {
self.build_rect(room, x0, y0, x0 + w - 1, y0 + h - 1);
}
} }
// has this master researched the tool (rooms, towers)? // has this master researched the tool (rooms, towers)?
@@ -294,6 +325,11 @@ impl Game {
self.place_mark(cx + 1, cy + 3, false); self.place_mark(cx + 1, cy + 3, false);
} }
let army = self.side_army(side); let army = self.side_army(side);
// heroes or enemy units in the dungeon: everybody goes for them (the rally flag) and the
// master casts lightning on them and heals his hurt creatures
if !self.is_partner(side) && self.ai_defend(k) {
return;
}
// co-op: go where the allies go // co-op: go where the allies go
if self.is_partner(side) { if self.is_partner(side) {
let lead = self.master_sides().into_iter().find(|&s| s != side && !self.is_out(s) && self.allied(side, s)); let lead = self.master_sides().into_iter().find(|&s| s != side && !self.is_out(s) && self.allied(side, s));
@@ -362,6 +398,49 @@ impl Game {
} }
} }
// the defence of the dungeon; true while there are intruders
fn ai_defend(&mut self, k: usize) -> bool {
let me = self.me;
let intruders: Vec<(f32, f32)> = self
.ents
.iter()
.filter(|e| e.alive && !e.held && e.captive == 0 && self.is_hostile(e) && self.map.get(e.x as i32, e.y as i32).map_or(false, |t| t.owner == me))
.map(|e| (e.x, e.y))
.collect();
if intruders.is_empty() {
if self.rivals[k].defending {
self.rivals[k].defending = false;
self.flag = None;
}
return false;
}
// the middle of the intruders, on walkable ground
let n = intruders.len() as f32;
let (mx, my) = intruders.iter().fold((0.0, 0.0), |a, p| (a.0 + p.0 / n, a.1 + p.1 / n));
let near = intruders.iter().min_by(|a, b| ((a.0 - mx).powi(2) + (a.1 - my).powi(2)).partial_cmp(&((b.0 - mx).powi(2) + (b.1 - my).powi(2))).unwrap()).copied().unwrap();
let tile = idx(near.0 as i32, near.1 as i32);
if self.rivals[k].attacking {
self.rivals[k].attacking = false; // home first
}
self.rivals[k].defending = true;
if self.map.t[tile].walkable() && self.flag != Some(tile) {
self.flag = Some(tile);
}
// lightning on the thickest of them, healing for the hurt
let bolt = 50.0 * self.talents.spell_cost();
if self.mana >= bolt + 40.0 && self.known(crate::game::LIGHTNING_TOOL) {
self.lightning(near.0, near.1);
}
let heal = 60.0 * self.talents.spell_cost();
let hurt = self.ents.iter().filter(|e| e.alive && e.creature() && e.hp < e.max_hp * 0.5 && (e.x - near.0).powi(2) + (e.y - near.1).powi(2) < 36.0).map(|e| (e.x, e.y)).next();
if let Some((hx, hy)) = hurt {
if self.mana >= heal + 40.0 && self.known(crate::game::HEAL_TOOL) {
self.heal(hx, hy);
}
}
true
}
// is an enemy heart near tile f? // is an enemy heart near tile f?
fn ai_enemy_heart_near(&self, side: u8, f: usize) -> bool { fn ai_enemy_heart_near(&self, side: u8, f: usize) -> bool {
let (fx, fy) = xy(f); let (fx, fy) = xy(f);
+105 -1
View File
@@ -1,6 +1,7 @@
// Audio engine: music (XM players with crossfades) and synthesised sound effects. // Audio engine: music (XM players with crossfades) and synthesised sound effects.
// The browser pulls samples through dc_audio_render() from our JS plugin (www/dc_plugin.js), // The browser pulls samples through dc_audio_render() from our JS plugin (www/dc_plugin.js),
// which starts the AudioContext on the first user interaction. // which starts the AudioContext on the first user interaction. Desktop builds play through
// the system's sound output (module `native` below).
use crate::sfx; use crate::sfx;
use crate::tracks::TRACKS; use crate::tracks::TRACKS;
@@ -244,6 +245,109 @@ pub extern "C" fn dc_audio_render(frames: u32) -> *const f32 {
}) })
} }
// ------------------------------------------------------------------ desktop sound output
// The game renders a little ahead into a queue each frame; the sound card's thread takes from it.
// The engine itself stays on the game thread, like in the browser.
#[cfg(not(target_arch = "wasm32"))]
pub mod native {
use super::STATE;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
// seconds rendered ahead: covers a slow frame or two
const AHEAD: f32 = 0.07;
struct Out {
_stream: cpal::Stream,
queue: Arc<Mutex<VecDeque<f32>>>, // interleaved stereo
rate: f32,
}
thread_local! {
static OUT: RefCell<Option<Out>> = const { RefCell::new(None) };
}
// opens the default output device and starts the engine; without one the game stays silent
pub fn start() {
match open() {
Ok(out) => {
super::dc_audio_init(out.rate);
OUT.with(|o| *o.borrow_mut() = Some(out));
}
Err(e) => eprintln!("DungeonCraft: no sound ({})", e),
}
}
fn open() -> Result<Out, String> {
let dev = cpal::default_host().default_output_device().ok_or("no output device")?;
let cfg = dev.default_output_config().map_err(|e| e.to_string())?;
let rate = cfg.sample_rate() as f32;
let channels = cfg.channels() as usize;
let queue = Arc::new(Mutex::new(VecDeque::new()));
let sc: cpal::StreamConfig = cfg.config();
use cpal::SampleFormat as F;
let stream = match cfg.sample_format() {
F::F32 => build::<f32>(&dev, &sc, channels, queue.clone()),
F::I16 => build::<i16>(&dev, &sc, channels, queue.clone()),
F::U16 => build::<u16>(&dev, &sc, channels, queue.clone()),
F::I32 => build::<i32>(&dev, &sc, channels, queue.clone()),
f => Err(format!("sample format {:?}", f)),
}?;
stream.play().map_err(|e| e.to_string())?;
Ok(Out { _stream: stream, queue, rate })
}
fn build<T: cpal::SizedSample + cpal::FromSample<f32>>(dev: &cpal::Device, sc: &cpal::StreamConfig, channels: usize, queue: Arc<Mutex<VecDeque<f32>>>) -> Result<cpal::Stream, String> {
let channels = channels.max(1);
dev.build_output_stream(
sc.clone(),
move |out: &mut [T], _: &cpal::OutputCallbackInfo| {
let mut q = queue.lock().unwrap_or_else(|e| e.into_inner());
for frame in out.chunks_mut(channels) {
// an empty queue (a stalled frame) plays silence
let (l, r) = if q.len() >= 2 { (q.pop_front().unwrap_or(0.0), q.pop_front().unwrap_or(0.0)) } else { (0.0, 0.0) };
for (c, s) in frame.iter_mut().enumerate() {
let v = match (channels, c) {
(1, _) => (l + r) * 0.5,
(_, 0) => l,
(_, 1) => r,
_ => 0.0,
};
*s = T::from_sample(v);
}
}
},
|e| eprintln!("DungeonCraft: sound output: {}", e),
None,
)
.map_err(|e| e.to_string())
}
// called once per frame: tops the queue up to AHEAD seconds
pub fn pump() {
OUT.with(|o| {
let o = o.borrow();
let Some(o) = o.as_ref() else { return };
let want = (o.rate * AHEAD) as usize;
let have = o.queue.lock().unwrap_or_else(|e| e.into_inner()).len() / 2;
if have >= want {
return;
}
STATE.with(|s| {
let mut s = s.borrow_mut();
let (mv, sv, muted) = (s.music_vol, s.sfx_vol, s.muted);
if let Some(e) = s.engine.as_mut() {
e.render(want - have, mv, sv, muted);
o.queue.lock().unwrap_or_else(|e| e.into_inner()).extend(e.buf.iter().copied());
}
});
});
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+249
View File
@@ -0,0 +1,249 @@
// Automated balancing: whole games played by the AI brain on every side, headless and
// deterministic, many seeds each. Run with
// cargo test --release balance_ -- --ignored --nocapture --test-threads=4
// and read the tables (docs/BALANCE.md keeps the findings).
#![cfg(test)]
use crate::game::*;
use crate::rival::{Rival, PARTNER_GIFT_EVERY, RIVAL_CORE_HP};
const STEP: f32 = 0.05;
// the master on side 0 gets the same brain as the rivals
fn pilot(g: &mut Game, difficulty: u8, focus: Option<u8>) {
let core = g.map.core;
// like every AI master: the 9x9 starting area and a portal of its own beside it
g.carve_master_area(0, core);
g.add_master_portal(0, core);
g.rivals.push(Rival { side: 0, core, hp: RIVAL_CORE_HP, alive: true, radius: 5, grow_t: 0.0, spawn_t: 0.0, attack_t: 0.0, difficulty, power: 0.0, gift_t: PARTNER_GIFT_EVERY, think_t: 1.0, attacking: false, army0: 0.0, focus, defending: false });
}
// the other corners of a square map for up to three more masters (centre of the core)
fn corners(w: i32, h: i32) -> [(i32, i32); 3] {
[(w - 29, 13), (w - 29, h - 15), (15, h - 15)]
}
#[derive(Clone, Debug, Default)]
struct Outcome {
out_at: Vec<(u8, f32)>, // side, minute it fell
score: Vec<(u8, f32)>, // side, strength at the end (alive ones)
winner: Option<u8>,
minutes: f32,
}
fn alive(g: &Game, side: u8) -> bool {
g.core_hp_of(side) > 0.0 && g.rivals.iter().any(|r| r.side == side && r.alive)
}
fn score(g: &Game, side: u8) -> f32 {
g.core_hp_of(side) * 2.0 + g.side_army(side) + g.gold_of(side) * 0.2
}
// plays until one side is left or the time is up; side 0 falling does not stop it
fn play(g: &mut Game, sides: &[u8], max_min: f32) -> Outcome {
let mut o = Outcome::default();
let steps = (max_min * 60.0 / STEP) as usize;
for k in 0..steps {
g.update(STEP);
if g.state != 0 {
g.state = 0; // the harness decides the end
}
if k % 20 == 0 {
for &s in sides {
if !o.out_at.iter().any(|x| x.0 == s) && !alive(g, s) {
o.out_at.push((s, g.time / 60.0));
}
}
let left: Vec<u8> = sides.iter().copied().filter(|&s| !o.out_at.iter().any(|x| x.0 == s)).collect();
if left.len() <= 1 {
o.winner = left.first().copied();
break;
}
}
}
o.minutes = g.time / 60.0;
o.score = sides.iter().copied().filter(|&s| !o.out_at.iter().any(|x| x.0 == s)).map(|s| (s, score(g, s))).collect();
if o.winner.is_none() {
o.winner = o.score.iter().max_by(|a, b| a.1.partial_cmp(&b.1).unwrap()).map(|x| x.0);
}
o
}
fn arena(seed: u32, size: usize) -> Game {
let mut g = Game::new(seed, size, size);
g.gate_on = false; // no hero waves: masters against each other
g.ai_difficulty = 1;
g
}
fn seeds() -> Vec<u32> {
let n: u32 = std::env::var("BALANCE_SEEDS").ok().and_then(|s| s.parse().ok()).unwrap_or(6);
(1..=n).map(|k| 1000 + k * 37).collect()
}
// ---------------------------------------------------------------- difficulty duels
#[test]
#[ignore]
fn balance_difficulty() {
println!("\n== difficulty duels (2 masters, 112x112, no waves, 45 min) ==");
println!("{:<14} {:>5} {:>5} {:>5} {:>8}", "pair", "A", "B", "draw", "min");
// BALANCE_PAIR=a,b runs one pairing only
let pairs: Vec<(u8, u8)> = match std::env::var("BALANCE_PAIR").ok().and_then(|p| p.split_once(',').map(|(a, b)| (a.parse().ok(), b.parse().ok()))) {
Some((Some(a), Some(b))) => vec![(a, b)],
_ => vec![(0, 1), (1, 2), (0, 2), (1, 1)],
};
for (a, b) in pairs {
let (mut wa, mut wb, mut dr, mut mins) = (0, 0, 0, 0.0);
for (k, seed) in seeds().into_iter().enumerate() {
let mut g = arena(seed, 112);
// swap who plays which side every other seed (side 0 has the map's own start)
let (d0, d2) = if k % 2 == 0 { (a, b) } else { (b, a) };
pilot(&mut g, d0, None);
let (x, y) = corners(112, 112)[k % 3];
g.add_rival(2, x, y, d2);
let o = play(&mut g, &[0, 2], 45.0);
mins += o.minutes;
let a_side = if k % 2 == 0 { 0 } else { 2 };
let decisive = o.out_at.len() == 1;
match o.winner {
Some(w) if decisive && w == a_side => wa += 1,
Some(_) if decisive => wb += 1,
_ => dr += 1,
}
}
let n = seeds().len() as f32;
println!("{:<14} {:>5} {:>5} {:>5} {:>8.1}", format!("{} vs {}", a, b), wa, wb, dr, mins / n);
}
}
// ---------------------------------------------------------------- talent branches
#[test]
#[ignore]
fn balance_talents() {
use crate::talents::*;
println!("\n== talent tournament (4 masters, medium, 128x128, no waves, 50 min) ==");
let names = ["builder", "warlord", "warlock", "merchant"];
let mut wins = [0usize; 4];
let mut place_sum = [0.0f32; 4];
let mut lv_sum = [0.0f32; 4];
let sides = [0u8, 2, 3, 4];
for (k, seed) in seeds().into_iter().enumerate() {
let mut g = arena(seed, 128);
// the branches move round the four starts from seed to seed
let branch_of = |s: usize| ((s + k) % 4) as u8;
pilot(&mut g, 1, Some(branch_of(0)));
for (n, &(x, y)) in corners(128, 128).iter().enumerate() {
g.add_rival(sides[n + 1], x, y, 1);
g.rivals.last_mut().unwrap().focus = Some(branch_of(n + 1));
}
let o = play(&mut g, &sides, 50.0);
// placement: the winner 1st, the survivors by score, the fallen by how long they lasted
let mut order: Vec<u8> = Vec::new();
let mut alive_sorted = o.score.clone();
alive_sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
order.extend(alive_sorted.iter().map(|x| x.0));
let mut fallen = o.out_at.clone();
fallen.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
order.extend(fallen.iter().map(|x| x.0));
for (place, &s) in order.iter().enumerate() {
let n = sides.iter().position(|&x| x == s).unwrap();
let b = branch_of(n) as usize;
place_sum[b] += place as f32 + 1.0;
if place == 0 {
wins[b] += 1;
}
lv_sum[b] += g.talents_of(s).lv[b] as f32;
}
let _ = BRANCHES;
}
let n = seeds().len() as f32;
println!("{:<10} {:>5} {:>10} {:>10}", "branch", "wins", "avg place", "avg level");
for b in 0..4 {
println!("{:<10} {:>5} {:>10.2} {:>10.1}", names[b], wins[b], place_sum[b] / n, lv_sum[b] / n);
}
}
// ---------------------------------------------------------------- one master against the waves
#[test]
#[ignore]
fn balance_waves() {
println!("\n== one AI master against the hero waves (96x96, 40 min) ==");
println!("{:<6} {:>8} {:>6} {:>7} {:>7} {:>7} {:>6}", "diff", "fell@min", "wave", "heroes", "lost", "gold", "army");
for d in 0..3u8 {
for seed in seeds() {
let mut g = Game::new(seed, 96, 96);
g.ai_difficulty = d;
pilot(&mut g, d, None);
let mut fell = None;
for k in 0..((40.0 * 60.0 / STEP) as usize) {
g.update(STEP);
if g.state != 0 {
if g.core_hp <= 0.0 {
fell = Some(g.time / 60.0);
break;
}
g.state = 0;
}
if std::env::var("BALANCE_TRACE").is_ok() && k % (120.0 / STEP) as usize == 0 {
let rooms: usize = g.counts.iter().sum();
let portal = (0..g.map.portals.len()).any(|p| g.map.portal_owner(p) == crate::map::Owner::Player);
println!(" seed {} d{} min {:>4.1}: creatures {:>3} imps {:>2} gold {:>6} rooms {:>3} lair {:>3} farm {:>3} wave {:>2} core {:>5} portal {} heroes near {}",
seed, d, g.time / 60.0, g.creature_count(), g.worker_count(), g.gold as i32, rooms, g.counts[1], g.counts[2], g.wave, g.core_hp as i32, portal,
g.ents.iter().filter(|e| e.alive && e.active_hero()).count());
}
}
println!(
"{:<6} {:>8} {:>6} {:>7} {:>7} {:>7} {:>6}",
d,
fell.map_or("-".to_string(), |m| format!("{:.1}", m)),
g.wave,
g.stats.get(crate::stats::HEROES) as i32,
g.stats.get(crate::stats::LOST) as i32,
g.gold as i32,
g.side_army(0) as i32
);
}
}
}
// where the heroes of a wave really break in, against the marked entry points
#[test]
#[ignore]
fn balance_entry_points() {
use crate::map::*;
println!("\n== planned entry vs. where the heroes step onto the master's ground ==");
for seed in seeds() {
let mut g = Game::new(seed, 96, 96);
pilot(&mut g, 1, None);
let mut planned: Vec<(i32, i32)> = Vec::new();
let mut first_in: Option<(i32, i32)> = None;
let mut wave = 0;
for _ in 0..((20.0 * 60.0 / STEP) as usize) {
g.update(STEP);
if g.state != 0 {
break;
}
if !g.planned_entries.is_empty() {
planned = g.planned_entries.clone();
}
if g.wave != wave {
wave = g.wave;
first_in = None;
}
if first_in.is_none() {
if let Some(e) = g.ents.iter().find(|e| e.alive && e.active_hero() && g.map.get(e.x as i32, e.y as i32).map_or(false, |t| t.owner == Owner::Player)) {
first_in = Some((e.x as i32, e.y as i32));
let d = planned.iter().map(|&(px, py)| (((px - e.x as i32).pow(2) + (py - e.y as i32).pow(2)) as f32).sqrt()).fold(f32::MAX, f32::min);
let wp = g.ents.iter().filter(|e| e.alive && e.active_hero()).filter(|e| e.waypoint.is_some()).count();
let heroes = g.ents.iter().filter(|e| e.alive && e.active_hero()).count();
let job = format!("{:?}", e.job);
println!("seed {} wave {}: planned {:?} first in at {:?} -> {:.1} tiles off; heroes with waypoint {}/{}; its job {}", seed, wave, planned, first_in.unwrap(), d, wp, heroes, job);
}
}
}
}
}
+26 -5
View File
@@ -16,7 +16,7 @@ pub const KINDS: [&str; 30] = [
]; ];
// ground a Terrain block can make // ground a Terrain block can make
pub const TERRAIN: [&str; 6] = ["floor", "earth", "rock", "water", "lava", "gold"]; pub const TERRAIN: [&str; 7] = ["floor", "earth", "rock", "water", "lava", "gold", "grove"];
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "b")] #[serde(tag = "b")]
@@ -42,6 +42,8 @@ pub enum Block {
Terrain { zone: String, kind: String }, Terrain { zone: String, kind: String },
Kill { zone: String, side: i32 }, Kill { zone: String, side: i32 },
Trigger { name: String, on: bool }, Trigger { name: String, on: bool },
Weather { kind: String, secs: Val }, // the weather above ground (secs 0: until changed again)
TalentXp { amount: Val }, // experience for the player's talents
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -70,6 +72,7 @@ pub enum Val {
Or { a: Box<Val>, b: Box<Val> }, Or { a: Box<Val>, b: Box<Val> },
Not { a: Box<Val> }, Not { a: Box<Val> },
EventKind { kind: String }, // the unit that set off the trigger is of this kind EventKind { kind: String }, // the unit that set off the trigger is of this kind
WeatherIs { kind: String }, // the weather above ground is ...
} }
pub const MATH_OPS: [&str; 4] = ["+", "-", "*", "/"]; pub const MATH_OPS: [&str; 4] = ["+", "-", "*", "/"];
@@ -89,7 +92,7 @@ impl Val {
pub fn ty(&self) -> Ty { pub fn ty(&self) -> Ty {
match self { match self {
Val::Bool { .. } | Val::Cmp { .. } | Val::And { .. } | Val::Or { .. } | Val::Not { .. } | Val::EventKind { .. } => Ty::Bool, Val::Bool { .. } | Val::Cmp { .. } | Val::And { .. } | Val::Or { .. } | Val::Not { .. } | Val::EventKind { .. } | Val::WeatherIs { .. } => Ty::Bool,
_ => Ty::Num, _ => Ty::Num,
} }
} }
@@ -113,6 +116,7 @@ impl Val {
"or" => Val::Or { a: Box::new(Val::Bool { b: true }), b: Box::new(Val::Bool { b: false }) }, "or" => Val::Or { a: Box::new(Val::Bool { b: true }), b: Box::new(Val::Bool { b: false }) },
"not" => Val::Not { a: Box::new(Val::Bool { b: true }) }, "not" => Val::Not { a: Box::new(Val::Bool { b: true }) },
"eventkind" => Val::EventKind { kind: "Knight".into() }, "eventkind" => Val::EventKind { kind: "Knight".into() },
"weatheris" => Val::WeatherIs { kind: "rain".into() },
_ => return None, _ => return None,
}) })
} }
@@ -135,16 +139,20 @@ impl Val {
Val::Or { .. } => "or", Val::Or { .. } => "or",
Val::Not { .. } => "not", Val::Not { .. } => "not",
Val::EventKind { .. } => "eventkind", Val::EventKind { .. } => "eventkind",
Val::WeatherIs { .. } => "weatheris",
} }
} }
} }
// value kinds by type, for the value picker // value kinds by type, for the value picker
pub const NUM_VALUES: [&str; 9] = ["num", "var", "gold", "mana", "time", "creatures", "inzone", "random", "math"]; pub const NUM_VALUES: [&str; 9] = ["num", "var", "gold", "mana", "time", "creatures", "inzone", "random", "math"];
pub const BOOL_VALUES: [&str; 6] = ["cmp", "and", "or", "not", "bool", "eventkind"]; pub const BOOL_VALUES: [&str; 7] = ["cmp", "and", "or", "not", "bool", "eventkind", "weatheris"];
// the weather a block can set or ask for (weather.rs: FAIR, RAIN, FOG, STORM)
pub const WEATHER: [&str; 4] = ["fair", "rain", "fog", "storm"];
// statement blocks in the palette: (category, kind) // statement blocks in the palette: (category, kind)
pub const PALETTE: [(&str, &str); 21] = [ pub const PALETTE: [(&str, &str); 23] = [
("say", "message"), ("say", "message"),
("say", "objective"), ("say", "objective"),
("say", "hint"), ("say", "hint"),
@@ -156,6 +164,8 @@ pub const PALETTE: [(&str, &str); 21] = [
("world", "camera"), ("world", "camera"),
("world", "gold"), ("world", "gold"),
("world", "mana"), ("world", "mana"),
("world", "weather"),
("world", "talentxp"),
("logic", "if"), ("logic", "if"),
("logic", "repeat"), ("logic", "repeat"),
("logic", "until"), ("logic", "until"),
@@ -185,6 +195,8 @@ impl Block {
"camera" => Block::Camera { zone: String::new() }, "camera" => Block::Camera { zone: String::new() },
"gold" => Block::Gold { amount: n(500.0) }, "gold" => Block::Gold { amount: n(500.0) },
"mana" => Block::Mana { amount: n(100.0) }, "mana" => Block::Mana { amount: n(100.0) },
"weather" => Block::Weather { kind: "storm".into(), secs: n(0.0) },
"talentxp" => Block::TalentXp { amount: n(300.0) },
"if" => Block::If { cond: Val::template("cmp").unwrap(), then: Vec::new(), els: Vec::new() }, "if" => Block::If { cond: Val::template("cmp").unwrap(), then: Vec::new(), els: Vec::new() },
"repeat" => Block::Repeat { times: n(3.0), body: Vec::new() }, "repeat" => Block::Repeat { times: n(3.0), body: Vec::new() },
"until" => Block::Until { cond: Val::template("cmp").unwrap(), body: Vec::new() }, "until" => Block::Until { cond: Val::template("cmp").unwrap(), body: Vec::new() },
@@ -212,6 +224,8 @@ impl Block {
Block::Camera { .. } => "camera", Block::Camera { .. } => "camera",
Block::Gold { .. } => "gold", Block::Gold { .. } => "gold",
Block::Mana { .. } => "mana", Block::Mana { .. } => "mana",
Block::Weather { .. } => "weather",
Block::TalentXp { .. } => "talentxp",
Block::If { .. } => "if", Block::If { .. } => "if",
Block::Repeat { .. } => "repeat", Block::Repeat { .. } => "repeat",
Block::Until { .. } => "until", Block::Until { .. } => "until",
@@ -258,6 +272,7 @@ fn check_val(v: &Val, want: Ty, n: &Names, path: &[usize], out: &mut Vec<Problem
Val::Creatures { kind, .. } if !kind.is_empty() && !KINDS.contains(&kind.as_str()) => out.push(p("blk.err_kind")), Val::Creatures { kind, .. } if !kind.is_empty() && !KINDS.contains(&kind.as_str()) => out.push(p("blk.err_kind")),
Val::InZone { zone, .. } if !n.zones.iter().any(|z| z == zone) => out.push(p("blk.err_zone")), Val::InZone { zone, .. } if !n.zones.iter().any(|z| z == zone) => out.push(p("blk.err_zone")),
Val::EventKind { kind } if !KINDS.contains(&kind.as_str()) => out.push(p("blk.err_kind")), Val::EventKind { kind } if !KINDS.contains(&kind.as_str()) => out.push(p("blk.err_kind")),
Val::WeatherIs { kind } if !WEATHER.contains(&kind.as_str()) => out.push(p("blk.err_weather")),
Val::Random { a, b } | Val::Math { a, b, .. } => { Val::Random { a, b } | Val::Math { a, b, .. } => {
check_val(a, Ty::Num, n, path, out); check_val(a, Ty::Num, n, path, out);
check_val(b, Ty::Num, n, path, out); check_val(b, Ty::Num, n, path, out);
@@ -295,7 +310,13 @@ fn check_list(list: &[Block], n: &Names, path: &mut Vec<usize>, out: &mut Vec<Pr
} }
} }
Block::Wave { count } => check_val(count, Ty::Num, n, path, out), Block::Wave { count } => check_val(count, Ty::Num, n, path, out),
Block::Gold { amount } | Block::Mana { amount } => check_val(amount, Ty::Num, n, path, out), Block::Gold { amount } | Block::Mana { amount } | Block::TalentXp { amount } => check_val(amount, Ty::Num, n, path, out),
Block::Weather { kind, secs } => {
if !WEATHER.contains(&kind.as_str()) {
out.push(p("blk.err_weather", path));
}
check_val(secs, Ty::Num, n, path, out);
}
Block::SetVar { name, value } | Block::AddVar { name, value } => { Block::SetVar { name, value } | Block::AddVar { name, value } => {
if !valid_var(name) { if !valid_var(name) {
out.push(p("blk.err_var", path)); out.push(p("blk.err_var", path));
+2 -1
View File
@@ -56,7 +56,8 @@ impl Game {
return; return;
} }
let i = idx(tx, ty); let i = idx(tx, ty);
if !self.wooden_bridge(i) || self.burning(i) { // nothing catches fire in the rain
if !self.wooden_bridge(i) || self.burning(i) || self.rain_at(i) {
return; return;
} }
self.bridgework.burning.push((i, 0.0)); self.bridgework.burning.push((i, 0.0));
+6 -2
View File
@@ -84,6 +84,8 @@ pub struct Carry {
pub rune_free: u32, // unbuilt rune forge towers from chests pub rune_free: u32, // unbuilt rune forge towers from chests
#[serde(default)] #[serde(default)]
pub rune_final: u32, // unbuilt or sold rune forge towers that were already given back once pub rune_final: u32, // unbuilt or sold rune forge towers that were already given back once
#[serde(default)]
pub talents: crate::talents::Talents, // the master's talents go along from mission to mission
} }
pub const CARRY_CREATURES: usize = 5; pub const CARRY_CREATURES: usize = 5;
@@ -580,6 +582,7 @@ fn start_mission_def(d: &MissionDef, i: usize, carry: Option<&Carry>, coop: bool
g.gold += c.gold; g.gold += c.gold;
g.rune_free += c.rune_free; g.rune_free += c.rune_free;
g.rune_final += c.rune_final; g.rune_final += c.rune_final;
g.talents = c.talents.clone();
} }
for (k, &(kind, level, xp)) in joiners.iter().enumerate() { for (k, &(kind, level, xp)) in joiners.iter().enumerate() {
let a = k as f32 * 0.9; let a = k as f32 * 0.9;
@@ -796,7 +799,7 @@ pub fn carry_from(g: &Game) -> Carry {
let mut c: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && e.creature()).collect(); let mut c: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && e.creature()).collect();
let power = |e: &Ent| e.max_hp * e.atk() / st(e.kind).interval; let power = |e: &Ent| e.max_hp * e.atk() / st(e.kind).interval;
c.sort_by(|a, b| power(b).partial_cmp(&power(a)).unwrap_or(std::cmp::Ordering::Equal)); c.sort_by(|a, b| power(b).partial_cmp(&power(a)).unwrap_or(std::cmp::Ordering::Equal));
Carry { creatures: c.iter().take(CARRY_CREATURES).map(|e| (e.kind, e.level, e.xp)).collect(), gold: (g.gold * CARRY_GOLD).floor(), rune_free: g.rune_free, rune_final: g.rune_final + g.rune_next } Carry { creatures: c.iter().take(CARRY_CREATURES).map(|e| (e.kind, e.level, e.xp)).collect(), gold: (g.gold * CARRY_GOLD).floor(), rune_free: g.rune_free, rune_final: g.rune_final + g.rune_next, talents: g.talents.clone() }
} }
// a co-op match of an editor campaign: what every master takes into the next mission - his // a co-op match of an editor campaign: what every master takes into the next mission - his
@@ -810,7 +813,7 @@ pub fn coop_carry_from(g: &Game) -> Vec<(u8, Carry)> {
let mut c: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && !e.hero && e.side == side && e.kind != EK::Worker && !e.chained && e.captive == 0).collect(); let mut c: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && !e.hero && e.side == side && e.kind != EK::Worker && !e.chained && e.captive == 0).collect();
c.sort_by(|a, b| power(b).partial_cmp(&power(a)).unwrap_or(std::cmp::Ordering::Equal).then(a.id.cmp(&b.id))); c.sort_by(|a, b| power(b).partial_cmp(&power(a)).unwrap_or(std::cmp::Ordering::Equal).then(a.id.cmp(&b.id)));
let creatures = c.iter().take(CARRY_CREATURES).map(|e| (e.kind, e.level, e.xp)).collect(); let creatures = c.iter().take(CARRY_CREATURES).map(|e| (e.kind, e.level, e.xp)).collect();
(side, Carry { creatures, gold: (g.gold_of(side).max(0.0) * CARRY_GOLD).floor(), rune_free: 0, rune_final: 0 }) (side, Carry { creatures, gold: (g.gold_of(side).max(0.0) * CARRY_GOLD).floor(), rune_free: 0, rune_final: 0, talents: g.talents_of(side) })
}) })
.collect() .collect()
} }
@@ -824,6 +827,7 @@ pub fn apply_coop_carry(g: &mut Game, carry: &[(u8, Carry)]) {
} }
g.switch_to(*side); g.switch_to(*side);
g.gold += c.gold.max(0.0); g.gold += c.gold.max(0.0);
g.talents = c.talents.clone();
let (cx, cy) = g.map.core; let (cx, cy) = g.map.core;
for (k, &(kind, level, xp)) in c.creatures.iter().take(CARRY_CREATURES).enumerate() { for (k, &(kind, level, xp)) in c.creatures.iter().take(CARRY_CREATURES).enumerate() {
let a = k as f32 * 1.1; let a = k as f32 * 1.1;
+14 -6
View File
@@ -101,6 +101,10 @@ pub enum Act {
to: u8, to: u8,
amount: u32, amount: u32,
}, },
// spend a talent point on a branch (talents.rs)
Talent {
branch: u8,
},
} }
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
@@ -148,7 +152,7 @@ impl Game {
fn mark_command(&mut self, act: Act, side: u8) { fn mark_command(&mut self, act: Act, side: u8) {
let ((x, y), kind, dur) = match act { let ((x, y), kind, dur) = match act {
Act::SendGold { .. } => return, // nothing to point at on the map Act::SendGold { .. } | Act::Talent { .. } => return, // nothing to point at on the map
Act::Dig(r) => (rect_center(r), MARK_DIG, 4.0), Act::Dig(r) => (rect_center(r), MARK_DIG, 4.0),
Act::Room { r, .. } => (rect_center(r), MARK_ROOM, 5.0), Act::Room { r, .. } => (rect_center(r), MARK_ROOM, 5.0),
Act::Sell(r) => (rect_center(r), MARK_SELL, 4.0), Act::Sell(r) => (rect_center(r), MARK_SELL, 4.0),
@@ -227,6 +231,7 @@ impl Game {
Act::CancelOrders => self.cancel_orders(), Act::CancelOrders => self.cancel_orders(),
Act::Unmark { x, y } => self.unmark(x, y), Act::Unmark { x, y } => self.unmark(x, y),
Act::SendGold { to, amount } => self.send_gold(to, amount), Act::SendGold { to, amount } => self.send_gold(to, amount),
Act::Talent { branch } => self.learn_talent(branch as usize),
} }
} }
} }
@@ -283,7 +288,7 @@ mod tests {
} }
#[test] #[test]
fn fixed_order_flags_do_not_need_the_debug_marker_setting() { fn command_markers_only_show_with_the_debug_setting() {
let mut g = game(13); let mut g = game(13);
g.debug_markers = false; g.debug_markers = false;
let (cx, cy) = g.map.core; let (cx, cy) = g.map.core;
@@ -291,10 +296,13 @@ mod tests {
g.queue(Act::Guard { x: cx, y: cy }); g.queue(Act::Guard { x: cx, y: cy });
g.queue(Act::Attack { ent: None, x: (cx + 20) as f32, y: (cy + 20) as f32 }); g.queue(Act::Attack { ent: None, x: (cx + 20) as f32, y: (cy + 20) as f32 });
g.queue(Act::Flag { x: cx, y: cy }); g.queue(Act::Flag { x: cx, y: cy });
assert!(!g.marks.iter().any(|m| m.kind == MARK_DIG), "dig stays hidden without debug markers"); assert!(g.marks.is_empty(), "the real shields, swords and flags show the orders");
assert!(g.marks.iter().any(|m| m.kind == MARK_GUARD && m.fixed), "guard is fixed"); // with the setting on they show, and removing a marker takes them away
assert!(g.marks.iter().any(|m| m.kind == MARK_ATTACK && m.fixed), "attack is fixed"); g.debug_markers = true;
assert!(g.marks.iter().any(|m| m.kind == MARK_FLAG && m.fixed), "gather flag is fixed"); g.queue(Act::Guard { x: cx + 2, y: cy + 2 });
assert!(g.marks.iter().any(|m| m.kind == MARK_GUARD));
g.unmark(cx as f32 + 2.5, cy as f32 + 2.5);
assert!(!g.marks.iter().any(|m| m.kind == MARK_GUARD), "gone with the marker");
} }
#[test] #[test]
+21 -1
View File
@@ -543,7 +543,13 @@ impl BlockEdit {
let r = lay.take(CW() * 10.0); let r = lay.take(CW() * 10.0);
im.text_field(gfx, fid(path, 6), r, name, 16); im.text_field(gfx, fid(path, 6), r, name, 16);
} }
Block::Wave { count } | Block::Gold { amount: count } | Block::Mana { amount: count } => self.value(im, gfx, count, Ty::Num, path, 1, lay, ctx), Block::Wave { count } | Block::Gold { amount: count } | Block::Mana { amount: count } | Block::TalentXp { amount: count } => self.value(im, gfx, count, Ty::Num, path, 1, lay, ctx),
Block::Weather { kind, secs } => {
weather_dd(im, gfx, kind, fid(path, 2), lay);
lay.word(gfx, &tr("blk.w_for"));
self.value(im, gfx, secs, Ty::Num, path, 1, lay, ctx);
lay.word(gfx, &tr("blk.w_seconds"));
}
Block::SetVar { name, value } | Block::AddVar { name, value } => { Block::SetVar { name, value } | Block::AddVar { name, value } => {
let r = lay.take(CW() * 12.0); let r = lay.take(CW() * 12.0);
im.text_field(gfx, fid(path, 0), r, name, 24); im.text_field(gfx, fid(path, 0), r, name, 24);
@@ -713,10 +719,24 @@ impl BlockEdit {
lay.word(gfx, &tr("blk.v.eventkind")); lay.word(gfx, &tr("blk.v.eventkind"));
self.kind_dd(im, gfx, kind, false, path, sub(0), lay); self.kind_dd(im, gfx, kind, false, path, sub(0), lay);
} }
Val::WeatherIs { kind } => {
lay.word(gfx, &tr("blk.v.weatheris"));
weather_dd(im, gfx, kind, fid(path, sub(0)), lay);
}
} }
} }
} }
// a dropdown with the kinds of weather
fn weather_dd(im: &mut Im, gfx: &Gfx, kind: &mut String, id: u64, lay: &mut Lay) {
let opts: Vec<String> = WEATHER.iter().map(|w| tr(&format!("weather.{}", w))).collect();
let cur = tr(&format!("weather.{}", kind));
let r = lay.take(CW() * 12.0);
if let Some(c) = im.dropdown(gfx, id, r, &cur, &opts) {
*kind = WEATHER[c].to_string();
}
}
// where the next field of a block goes: left to right, into a new row at the block's edge // where the next field of a block goes: left to right, into a new row at the block's edge
struct Lay { struct Lay {
x: f32, x: f32,
+34
View File
@@ -143,6 +143,40 @@ pub fn coop_campaign() -> CampaignDoc {
], ],
}); });
} }
// the rogues of the Order in the later missions: thieves from mission 7, an inquisitor
// from mission 15; the last mission (above ground) brews a storm
let lv = n((1 + i / 3) as f64);
if i >= 6 {
sc.triggers.push(TriggerDoc {
name: "thieves".into(),
kind: TriggerKind::Timer { secs: 420.0, every: false },
once: true,
enabled: true,
script: vec![Block::Message { text: tr("coop.raid") }, Block::Spawn { side: 1, kind: "Thief".into(), count: n(2.0), level: lv.clone(), place: Place::Zone(ZONES[i % 3].0.into()), name: String::new() }],
});
}
if i >= 14 {
sc.triggers.push(TriggerDoc {
name: "inquisitor".into(),
kind: TriggerKind::Timer { secs: 600.0, every: false },
once: true,
enabled: true,
script: vec![
Block::Message { text: tr("coop.raid") },
Block::Spawn { side: 1, kind: "Inquisitor".into(), count: n(1.0), level: lv.clone(), place: Place::Zone(ZONES[(i + 1) % 3].0.into()), name: String::new() },
Block::Spawn { side: 1, kind: "Monk".into(), count: n(2.0), level: lv.clone(), place: Place::Zone(ZONES[(i + 1) % 3].0.into()), name: String::new() },
],
});
}
if i + 1 == specs.len() {
sc.triggers.push(TriggerDoc {
name: "storm".into(),
kind: TriggerKind::Timer { secs: 480.0, every: false },
once: true,
enabled: true,
script: vec![Block::Weather { kind: "storm".into(), secs: n(120.0) }],
});
}
if let Some((kind, level, name, zone)) = sp.boss { if let Some((kind, level, name, zone)) = sp.boss {
sc.start.push(Block::Spawn { side: 1, kind: kind.into(), count: n(1.0), level: n(level as f64), place: Place::Zone(ZONES[zone].0.into()), name: name.into() }); sc.start.push(Block::Spawn { side: 1, kind: kind.into(), count: n(1.0), level: n(level as f64), place: Place::Zone(ZONES[zone].0.into()), name: name.into() });
// the boss falls: the mission is won once the goals (if any) are done too // the boss falls: the mission is won once the goals (if any) are done too
+1 -1
View File
@@ -137,7 +137,7 @@ impl Game {
} }
} }
// what the creatures and towers see along open ground (walls stop the view) // what the creatures and towers see along open ground (walls stop the view)
let mut eyes: Vec<(usize, i32)> = self.ents.iter().filter(|e| e.alive && !e.held && !e.hero && (e.side == self.me_side || self.allied(self.me_side, e.side)) && !e.chained).map(|e| (e.tile(), SIGHT)).collect(); let mut eyes: Vec<(usize, i32)> = self.ents.iter().filter(|e| e.alive && !e.held && !e.hero && (e.side == self.me_side || self.allied(self.me_side, e.side)) && !e.chained).map(|e| (e.tile(), self.sight_at(e.tile(), SIGHT))).collect();
let solo = self.masters.is_empty(); let solo = self.masters.is_empty();
eyes.extend(self.towers.iter().filter(|t| !t.hero && (solo || allies.contains(&self.map.t[t.tile].owner))).map(|t| (t.tile, t.range() as i32 + 1))); eyes.extend(self.towers.iter().filter(|t| !t.hero && (solo || allies.contains(&self.map.t[t.tile].owner))).map(|t| (t.tile, t.range() as i32 + 1)));
let mut stamp = vec![0u32; n]; let mut stamp = vec![0u32; n];
+115 -21
View File
@@ -62,17 +62,26 @@ pub enum Sfx {
Voice4, Voice4,
Voice5, Voice5,
Voice6, Voice6,
// weather, rogues and groves
Thunder,
Steal,
Snare,
Sermon,
Rain,
Wind,
Spores,
} }
pub const VOICE_SYLLABLES: [Sfx; 6] = [Sfx::Voice1, Sfx::Voice2, Sfx::Voice3, Sfx::Voice4, Sfx::Voice5, Sfx::Voice6]; pub const VOICE_SYLLABLES: [Sfx; 6] = [Sfx::Voice1, Sfx::Voice2, Sfx::Voice3, Sfx::Voice4, Sfx::Voice5, Sfx::Voice6];
pub const SFX_ALL: [Sfx; 42] = [ pub const SFX_ALL: [Sfx; 49] = [
Sfx::Dig, Sfx::GoldDig, Sfx::DigDone, Sfx::Claim, Sfx::Deposit, Sfx::Build, Sfx::Sell, Sfx::MeleeHit, Sfx::Dig, Sfx::GoldDig, Sfx::DigDone, Sfx::Claim, Sfx::Deposit, Sfx::Build, Sfx::Sell, Sfx::MeleeHit,
Sfx::Shoot, Sfx::Death, Sfx::HeroDeath, Sfx::Lightning, Sfx::Heal, Sfx::Summon, Sfx::PortalArrive, Sfx::Shoot, Sfx::Death, Sfx::HeroDeath, Sfx::Lightning, Sfx::Heal, Sfx::Summon, Sfx::PortalArrive,
Sfx::WaveAlarm, Sfx::WaveStart, Sfx::CoreHit, Sfx::GateHit, Sfx::GateDefenders, Sfx::LevelUp, Sfx::WaveAlarm, Sfx::WaveStart, Sfx::CoreHit, Sfx::GateHit, Sfx::GateDefenders, Sfx::LevelUp,
Sfx::Pickup, Sfx::Drop, Sfx::Payday, Sfx::FlagSet, Sfx::Victory, Sfx::Defeat, Sfx::Click, Sfx::Error, Sfx::Pickup, Sfx::Drop, Sfx::Payday, Sfx::FlagSet, Sfx::Victory, Sfx::Defeat, Sfx::Click, Sfx::Error,
Sfx::Reinforce, Sfx::Cannon, Sfx::Magic, Sfx::Capture, Sfx::Convert, Sfx::Slap, Sfx::Punch, Sfx::Reinforce, Sfx::Cannon, Sfx::Magic, Sfx::Capture, Sfx::Convert, Sfx::Slap, Sfx::Punch,
Sfx::Voice1, Sfx::Voice2, Sfx::Voice3, Sfx::Voice4, Sfx::Voice5, Sfx::Voice6, Sfx::Voice1, Sfx::Voice2, Sfx::Voice3, Sfx::Voice4, Sfx::Voice5, Sfx::Voice6,
Sfx::Thunder, Sfx::Steal, Sfx::Snare, Sfx::Sermon, Sfx::Rain, Sfx::Wind, Sfx::Spores,
]; ];
impl Sfx { impl Sfx {
@@ -152,6 +161,8 @@ pub struct St {
pub const RUNE_TOOL: usize = 20; // index of the rune forge tower in ui::TOOLS pub const RUNE_TOOL: usize = 20; // index of the rune forge tower in ui::TOOLS
pub const SUMMON_TOOL: usize = 11; // index of summoning in ui::TOOLS pub const SUMMON_TOOL: usize = 11; // index of summoning in ui::TOOLS
pub const LIGHTNING_TOOL: usize = 9; // lightning in ui::TOOLS
pub const HEAL_TOOL: usize = 10; // healing in ui::TOOLS
pub fn st(k: EK) -> St { pub fn st(k: EK) -> St {
let s = |name, hp, atk, range, speed, interval, wage, bounty| St { name, hp, atk, range, speed, interval, wage, bounty }; let s = |name, hp, atk, range, speed, interval, wage, bounty| St { name, hp, atk, range, speed, interval, wage, bounty };
@@ -292,6 +303,12 @@ pub struct Ent {
#[serde(default)] #[serde(default)]
pub plague: f32, // spell: seconds of plague left (poison that spreads) pub plague: f32, // spell: seconds of plague left (poison that spreads)
#[serde(default)] #[serde(default)]
pub name: u32, // seed of the creature's name (names.rs), 0: none (heroes, siege engines)
#[serde(default)]
pub kills: u32, // heroes this creature has slain (names.rs: the hall of the fallen)
#[serde(default)]
pub bane: u8, // what hurt it last: sprite index + 1 of a unit kind, or names::BANE_*
#[serde(default)]
pub ab_cd: f32, // cooldown of the learned ability pub ab_cd: f32, // cooldown of the learned ability
#[serde(default)] #[serde(default)]
pub mood_t: f32, // seconds until the next mood check (likes and hatreds) pub mood_t: f32, // seconds until the next mood check (likes and hatreds)
@@ -381,6 +398,9 @@ impl Ent {
shield: 0.0, shield: 0.0,
invis: 0.0, invis: 0.0,
plague: 0.0, plague: 0.0,
name: 0,
kills: 0,
bane: 0,
ab_cd: 0.0, ab_cd: 0.0,
mood_t: 0.0, mood_t: 0.0,
guard: false, guard: false,
@@ -523,6 +543,8 @@ pub struct MasterState {
pub rune_next: u32, pub rune_next: u32,
pub portal_timer: f32, pub portal_timer: f32,
pub punch_ready: f32, pub punch_ready: f32,
#[serde(default)]
pub talents: crate::talents::Talents,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@@ -634,12 +656,20 @@ pub struct Game {
#[serde(default)] #[serde(default)]
pub bridgework: crate::bridges::BridgeWork, // burning and worn bridges pub bridgework: crate::bridges::BridgeWork, // burning and worn bridges
#[serde(default)] #[serde(default)]
pub weather: crate::weather::Weather, // above ground (weather.rs)
#[serde(default)]
pub memorials: Vec<crate::names::Memorial>, // stones for fallen creatures (names.rs)
#[serde(default)]
pub hall: Vec<crate::names::Fallen>, // the local master's fallen of this game (names.rs)
#[serde(default)]
pub trading: crate::traders::Trading, // the visiting trader (traders.rs) pub trading: crate::traders::Trading, // the visiting trader (traders.rs)
#[serde(default)] #[serde(default)]
pub happenings: Option<crate::events::Events>, // random events (free games and versus), see events.rs pub happenings: Option<crate::events::Events>, // random events (free games and versus), see events.rs
#[serde(default)] #[serde(default)]
pub snares: Vec<usize>, // tiles with a ranger's snare (rogues.rs) pub snares: Vec<usize>, // tiles with a ranger's snare (rogues.rs)
#[serde(default)] #[serde(default)]
pub talents: crate::talents::Talents, // of the master being processed (talents.rs)
#[serde(default)]
pub guard_marks: Vec<usize>, // guard markers (shields) of the master being processed (orders.rs) pub guard_marks: Vec<usize>, // guard markers (shields) of the master being processed (orders.rs)
#[serde(default)] #[serde(default)]
pub attack_marks: Vec<usize>, // attack markers (swords) pub attack_marks: Vec<usize>, // attack markers (swords)
@@ -987,9 +1017,13 @@ impl Game {
vrng: crate::particles::vrng(), vrng: crate::particles::vrng(),
dwarf_caves: Vec::new(), dwarf_caves: Vec::new(),
bridgework: Default::default(), bridgework: Default::default(),
weather: Default::default(),
memorials: Vec::new(),
hall: Vec::new(),
trading: Default::default(), trading: Default::default(),
happenings: None, happenings: None,
snares: Vec::new(), snares: Vec::new(),
talents: Default::default(),
guard_marks: Vec::new(), guard_marks: Vec::new(),
attack_marks: Vec::new(), attack_marks: Vec::new(),
marks_t: 0.0, marks_t: 0.0,
@@ -1218,7 +1252,7 @@ impl Game {
// what the next payday costs: wages of all creatures plus the training bill // what the next payday costs: wages of all creatures plus the training bill
pub fn next_payday_cost(&self) -> f32 { pub fn next_payday_cost(&self) -> f32 {
let wages: f32 = self.ents.iter().filter(|e| e.alive && e.creature()).map(|e| st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)).sum(); let wages: f32 = self.ents.iter().filter(|e| e.alive && e.creature()).map(|e| st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)).sum::<f32>() * self.talents.wages();
wages + self.training_bill wages + self.training_bill
} }
@@ -1344,6 +1378,9 @@ impl Game {
e.disp_x = x; e.disp_x = x;
e.disp_y = y; e.disp_y = y;
e.level = level; e.level = level;
if !hero && !matches!(kind, EK::Ram | EK::Catapult | EK::Trader) {
e.name = crate::names::seed_for(id);
}
e.max_hp = s.hp * (1.0 + 0.15 * (level - 1) as f32); e.max_hp = s.hp * (1.0 + 0.15 * (level - 1) as f32);
e.hp = e.max_hp; e.hp = e.max_hp;
e.alive = true; e.alive = true;
@@ -1514,6 +1551,7 @@ impl Game {
rune_next: self.rune_next, rune_next: self.rune_next,
portal_timer: self.portal_timer, portal_timer: self.portal_timer,
punch_ready: self.punch_ready, punch_ready: self.punch_ready,
talents: self.talents.clone(),
} }
} }
@@ -1549,6 +1587,7 @@ impl Game {
self.rune_next = m.rune_next; self.rune_next = m.rune_next;
self.portal_timer = m.portal_timer; self.portal_timer = m.portal_timer;
self.punch_ready = m.punch_ready; self.punch_ready = m.punch_ready;
self.talents = m.talents;
} }
// make `side` the master being processed (no-op for sides that are no masters) // make `side` the master being processed (no-op for sides that are no masters)
@@ -1734,7 +1773,7 @@ impl Game {
// a master's household: mana, core, portals, payday // a master's household: mana, core, portals, payday
fn economy(&mut self, dt: f32) { fn economy(&mut self, dt: f32) {
self.recount(); self.recount();
self.mana = (self.mana + (1.0 + SPRING_MANA * self.springs as f32) * dt).min(self.mana_max()); self.mana = (self.mana + (1.0 + SPRING_MANA * self.springs as f32) * self.talents.mana_rate() * dt).min(self.mana_max());
self.core_hp = (self.core_hp + 0.5 * dt).min(CORE_HP); self.core_hp = (self.core_hp + 0.5 * dt).min(CORE_HP);
let owned = self.portals_owned(); let owned = self.portals_owned();
@@ -1767,6 +1806,7 @@ impl Game {
if (self.time / 2.0).floor() != ((self.time - dt) / 2.0).floor() { if (self.time / 2.0).floor() != ((self.time - dt) / 2.0).floor() {
self.reinforce_corners(); self.reinforce_corners();
self.tidy_torch_stands(); self.tidy_torch_stands();
self.tidy_memorials();
} }
self.update_forge(dt); self.update_forge(dt);
self.update_research(); self.update_research();
@@ -1798,6 +1838,7 @@ impl Game {
self.update_rivals(dt); self.update_rivals(dt);
} }
self.update_dwarves(dt); self.update_dwarves(dt);
self.update_weather(dt);
self.update_bridges(dt); self.update_bridges(dt);
self.update_wood(dt); self.update_wood(dt);
self.update_events(dt); self.update_events(dt);
@@ -2081,13 +2122,14 @@ impl Game {
self.training_bill = 0.0; self.training_bill = 0.0;
let mut n = 0; let mut n = 0;
let mut total = bill; let mut total = bill;
let merchant = self.talents.wages();
for i in 0..self.ents.len() { for i in 0..self.ents.len() {
if !self.ents[i].alive || !self.ents[i].creature() { if !self.ents[i].alive || !self.ents[i].creature() {
continue; continue;
} }
let r = self.rng.f(); let r = self.rng.f();
let e = &mut self.ents[i]; let e = &mut self.ents[i];
let w = st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32); let w = st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32) * merchant;
e.pay_due += w; e.pay_due += w;
e.pay_t = r * 55.0; e.pay_t = r * 55.0;
n += 1; n += 1;
@@ -2470,7 +2512,7 @@ impl Game {
self.sfx(Sfx::LevelUp, e.x, e.y); self.sfx(Sfx::LevelUp, e.x, e.y);
} }
if !e.hero && e.level >= 3 { if !e.hero && e.level >= 3 {
self.msg("msg.level_up", vec![format!("@{}", s.name), e.level.to_string()]); self.msg("msg.level_up", vec![crate::names::arg(e), e.level.to_string()]);
} }
} }
if e.chained { if e.chained {
@@ -2760,8 +2802,15 @@ impl Game {
true true
} }
#[cfg(test)]
pub(crate) fn hit_for_test(&mut self, e: &mut Ent, j: usize) {
self.hit(e, j);
}
fn hit(&mut self, e: &mut Ent, j: usize) { fn hit(&mut self, e: &mut Ent, j: usize) {
let dmg = e.atk() * (0.85 + self.rng.f() * 0.3); // the Warlord talent of the attacker's master
let boost = if !e.hero && Owner::MASTER_SIDES.contains(&e.side) { self.talents_of(e.side).damage() } else { 1.0 };
let dmg = e.atk() * (0.85 + self.rng.f() * 0.3) * boost;
let (tx, ty) = (self.ents[j].x, self.ents[j].y); let (tx, ty) = (self.ents[j].x, self.ents[j].y);
let s = st(e.kind); let s = st(e.kind);
// the higher the level, the bigger the attack looks (up to almost twice at Lv 10) // the higher the level, the bigger the attack looks (up to almost twice at Lv 10)
@@ -2870,9 +2919,17 @@ impl Game {
} }
let my_id = e.id; let my_id = e.id;
e.invis = 0.0; // attacking breaks invisibility e.invis = 0.0; // attacking breaks invisibility
let armour = {
let t = &self.ents[j];
if !t.hero && Owner::MASTER_SIDES.contains(&t.side) { self.talents_of(t.side).armour() } else { 1.0 }
};
let t = &mut self.ents[j]; let t = &mut self.ents[j];
t.hp -= if t.shield > 0.0 { dmg * 0.5 } else { dmg }; t.hp -= if t.shield > 0.0 { dmg * 0.5 } else { dmg } * armour;
t.flash = 0.15; t.flash = 0.15;
t.bane = crate::gfx::sprite_index(e.kind) as u8 + 1;
if t.hp <= 0.0 && t.hero && t.captive == 0 {
e.kills += 1;
}
if t.creature() && !matches!(t.job, Job::Attack(_)) { if t.creature() && !matches!(t.job, Job::Attack(_)) {
t.job = Job::Attack(my_id); t.job = Job::Attack(my_id);
t.path.clear(); t.path.clear();
@@ -2930,6 +2987,9 @@ impl Game {
} }
} }
self.release_all(id); self.release_all(id);
// a named creature of some standing gets a memorial stone
let dead = self.ents[j].clone();
let remembered = self.raise_memorial(&dead);
self.fx(3, x, y, 0.0, 0.0, 0.7, 0xff505070); self.fx(3, x, y, 0.0, 0.0, 0.7, 0xff505070);
if kind != EK::Worker && kind != EK::Revenant { if kind != EK::Worker && kind != EK::Revenant {
self.bury(x, y); self.bury(x, y);
@@ -2946,6 +3006,7 @@ impl Game {
} }
if hero { if hero {
let b = s.bounty as f32 + if kind == EK::Thief { self.thief_loot(id) } else { 0.0 }; let b = s.bounty as f32 + if kind == EK::Thief { self.thief_loot(id) } else { 0.0 };
self.add_master_xp(s.bounty as f32 * crate::talents::HERO_XP);
let got = self.add_gold(b); let got = self.add_gold(b);
self.fx(5, x, y, 0.0, 0.0, 1.0, 0xff30d0ff); self.fx(5, x, y, 0.0, 0.0, 1.0, 0xff30d0ff);
self.sfx(Sfx::HeroDeath, x, y); self.sfx(Sfx::HeroDeath, x, y);
@@ -2961,7 +3022,9 @@ impl Game {
self.msg("msg.rival_unit_slain", vec![format!("@{}", s.name), (got as i32).to_string()]); self.msg("msg.rival_unit_slain", vec![format!("@{}", s.name), (got as i32).to_string()]);
} else { } else {
self.sfx(Sfx::Death, x, y); self.sfx(Sfx::Death, x, y);
self.msg("msg.creature_died", vec![format!("@{}", s.name)]); if !remembered {
self.msg("msg.creature_died", vec![crate::names::arg(&dead)]);
}
} }
} }
@@ -2982,6 +3045,13 @@ impl Game {
} }
self.release_all(id); self.release_all(id);
self.hand.retain(|&h| h != id); self.hand.retain(|&h| h != id);
// a named creature lost in lava or water is remembered too
if !hero {
let mut dead = self.ents[j].clone();
let lava = self.map.get(x as i32, y as i32).map_or(false, |t| t.river() == Some(Kind::Lava));
dead.bane = if lava { crate::names::BANE_LAVA } else { crate::names::BANE_WATER };
self.raise_memorial(&dead);
}
self.fx(3, x, y, 0.0, 0.0, 1.0, 0xff2060ff); self.fx(3, x, y, 0.0, 0.0, 1.0, 0xff2060ff);
self.fx(2, x, y, 0.0, 0.0, 0.5, 0xff30a0ff); self.fx(2, x, y, 0.0, 0.0, 0.5, 0xff30a0ff);
self.sfx(Sfx::Death, x, y); self.sfx(Sfx::Death, x, y);
@@ -3495,7 +3565,7 @@ impl Game {
e.face = sgn(tx as f32 + 0.5 - e.x); e.face = sgn(tx as f32 + 0.5 - e.x);
e.working = true; e.working = true;
let skilled = if crate::personality::knows(e, "dig") { 1.25 } else { 1.0 }; let skilled = if crate::personality::knows(e, "dig") { 1.25 } else { 1.0 };
e.timer += dt * self.help_speed(t) * skilled; e.timer += dt * self.help_speed(t) * skilled * self.talents.dig_speed();
if e.timer >= 0.45 { if e.timer >= 0.45 {
e.timer = 0.0; e.timer = 0.0;
let fxx = (e.x + tx as f32 + 0.5) * 0.5; let fxx = (e.x + tx as f32 + 0.5) * 0.5;
@@ -3655,7 +3725,7 @@ impl Game {
} }
Job::Deliver => { Job::Deliver => {
if self.is_deposit(cur) { if self.is_deposit(cur) {
let amount = e.carry as f32; let amount = e.carry as f32 * self.talents.treasure();
let got = self.add_res_at(amount, e.carry_res, Some(cur)); let got = self.add_res_at(amount, e.carry_res, Some(cur));
if got < amount { if got < amount {
self.hint(3, "hint.treasury_full"); self.hint(3, "hint.treasury_full");
@@ -4282,7 +4352,9 @@ impl Game {
} }
pub fn add_mark(&mut self, x: f32, y: f32, kind: u8, side: u8, duration: f32, fixed: bool) { pub fn add_mark(&mut self, x: f32, y: f32, kind: u8, side: u8, duration: f32, fixed: bool) {
if (!fixed && !self.debug_markers) || duration <= 0.0 || !x.is_finite() || !y.is_finite() { // the shields, swords and the flag show themselves; these markers are for debugging only
let _ = fixed;
if !self.debug_markers || duration <= 0.0 || !x.is_finite() || !y.is_finite() {
return; return;
} }
let until = self.sim_steps + (duration / STEP).ceil() as u64; let until = self.sim_steps + (duration / STEP).ceil() as u64;
@@ -4831,6 +4903,13 @@ impl Game {
if (e.x - wx as f32 - 0.5).abs() < 2.5 && (e.y - wy as f32 - 0.5).abs() < 2.5 { if (e.x - wx as f32 - 0.5).abs() < 2.5 && (e.y - wy as f32 - 0.5).abs() < 2.5 {
e.waypoint = None; // broke into the realm: now for the core e.waypoint = None; // broke into the realm: now for the core
e.path.clear(); e.path.clear();
} else if let Some(&next) = e.path.first() {
// the realm grew into the way (imps claim fast): round it again
let realm = self.map.t[idx(wx, wy)].owner;
if self.map.t[next].owner == realm && next != idx(wx, wy) && self.map.t[e.tile()].owner != realm {
e.path.clear();
e.repath = 0.0;
}
} }
} }
if e.path.is_empty() && e.repath <= 0.0 { if e.path.is_empty() && e.repath <= 0.0 {
@@ -4838,7 +4917,8 @@ impl Game {
let path = match e.waypoint { let path = match e.waypoint {
Some((wx, wy)) => { Some((wx, wy)) => {
let goal = idx(wx, wy); let goal = idx(wx, wy);
let p = self.map.search(cur, self.map.hero_cost(), |i| i == goal, true); // round the realm from outside: in through the marked entry, not before
let p = self.entry_path(cur, goal).or_else(|| self.map.search(cur, self.map.hero_cost(), |i| i == goal, true));
if p.is_none() { if p.is_none() {
e.waypoint = None; e.waypoint = None;
} }
@@ -4903,14 +4983,28 @@ impl Game {
scored.push(((x, y), score)); scored.push(((x, y), score));
} }
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let first = scored[0].0; // only places the heroes can reach from the gate without crossing the realm first
let gate = idx(gx, gy);
let mut tried = 0;
let mut ok = |g: &Game, p: (i32, i32)| {
tried += 1;
tried > 12 || g.entry_path(gate, idx(p.0, p.1)).is_some()
};
let Some(first) = scored.iter().map(|s| s.0).find(|&p| ok(self, p)) else { return };
self.planned_entries.push(first); self.planned_entries.push(first);
// second entry point at least 10 tiles away (used by waves of 6+ heroes) // second entry point at least 10 tiles away (used by waves of 6+ heroes)
if let Some(&(second, _)) = scored.iter().find(|((x, y), _)| (x - first.0).pow(2) + (y - first.1).pow(2) >= 100) { if let Some(second) = scored.iter().map(|s| s.0).filter(|&(x, y)| (x - first.0).pow(2) + (y - first.1).pow(2) >= 100).find(|&p| ok(self, p)) {
self.planned_entries.push(second); self.planned_entries.push(second);
} }
} }
// a hero's way to an entry tile that stays off the realm (the master's ground) until the entry
pub(crate) fn entry_path(&self, from: usize, goal: usize) -> Option<Vec<usize>> {
let owner = self.map.t[goal].owner;
let cost = self.map.hero_cost();
self.map.search(from, |i| if i != goal && i != from && self.map.t[i].owner == owner { None } else { cost(i) }, |i| i == goal, true)
}
// compass direction of a tile as seen from the core (language key) // compass direction of a tile as seen from the core (language key)
pub fn direction_key(&self, (x, y): (i32, i32)) -> &'static str { pub fn direction_key(&self, (x, y): (i32, i32)) -> &'static str {
let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1); let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1);
@@ -4939,7 +5033,7 @@ impl Game {
pub fn build_rect(&mut self, room: usize, x0: i32, y0: i32, x1: i32, y1: i32) { pub fn build_rect(&mut self, room: usize, x0: i32, y0: i32, x1: i32, y1: i32) {
let kind = ROOM_KINDS[room]; let kind = ROOM_KINDS[room];
let cost = ROOM_COST[room] as f32; let cost = (ROOM_COST[room] as f32 * self.talents.room_cost()).round();
let mut built = 0; let mut built = 0;
let mut broke = false; let mut broke = false;
for y in y0.min(y1)..=y0.max(y1) { for y in y0.min(y1)..=y0.max(y1) {
@@ -5693,13 +5787,13 @@ impl Game {
} }
pub fn lightning(&mut self, wx: f32, wy: f32) { pub fn lightning(&mut self, wx: f32, wy: f32) {
const COST: f32 = 50.0; let cost = 50.0 * self.talents.spell_cost();
if self.mana < COST { if self.mana < cost {
self.msg("msg.no_mana_bolt", vec![]); self.msg("msg.no_mana_bolt", vec![]);
self.sfx_ui(Sfx::Error); self.sfx_ui(Sfx::Error);
return; return;
} }
self.mana -= COST; self.mana -= cost;
self.count(crate::stats::SPELLS, 1.0); self.count(crate::stats::SPELLS, 1.0);
self.sfx(Sfx::Lightning, wx, wy); self.sfx(Sfx::Lightning, wx, wy);
self.fx(1, wx, wy, 0.0, 0.0, 0.5, 0xffffeeaa); self.fx(1, wx, wy, 0.0, 0.0, 0.5, 0xffffeeaa);
@@ -5720,13 +5814,13 @@ impl Game {
} }
pub fn heal(&mut self, wx: f32, wy: f32) { pub fn heal(&mut self, wx: f32, wy: f32) {
const COST: f32 = 60.0; let cost = 60.0 * self.talents.spell_cost();
if self.mana < COST { if self.mana < cost {
self.msg("msg.no_mana_heal", vec![]); self.msg("msg.no_mana_heal", vec![]);
self.sfx_ui(Sfx::Error); self.sfx_ui(Sfx::Error);
return; return;
} }
self.mana -= COST; self.mana -= cost;
self.count(crate::stats::SPELLS, 1.0); self.count(crate::stats::SPELLS, 1.0);
self.sfx(Sfx::Heal, wx, wy); self.sfx(Sfx::Heal, wx, wy);
let mut fxs = Vec::new(); let mut fxs = Vec::new();
+7 -1
View File
@@ -97,7 +97,13 @@ pub fn tr(key: &str) -> String {
pub fn trf(key: &str, args: &[String]) -> String { pub fn trf(key: &str, args: &[String]) -> String {
let mut s = tr(key); let mut s = tr(key);
for (n, a) in args.iter().enumerate() { for (n, a) in args.iter().enumerate() {
let v = if let Some(k) = a.strip_prefix('@') { tr(k) } else { a.clone() }; let v = if let Some(k) = a.strip_prefix('@') {
tr(k)
} else if let Some(n) = a.strip_prefix('%') {
crate::names::from_arg(n) // a creature's name (names.rs)
} else {
a.clone()
};
s = s.replace(&format!("{{{}}}", n), &v); s = s.replace(&format!("{{{}}}", n), &v);
} }
s s
+43 -8
View File
@@ -1,4 +1,6 @@
// DungeonCraft – dungeon building & defence in the browser (Rust + macroquad, WASM). // DungeonCraft – dungeon building & defence in the browser (Rust + macroquad, WASM).
// Windows release builds are a window program: no console opens beside the game
#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")]
mod achievements; mod achievements;
mod ai; mod ai;
@@ -30,6 +32,10 @@ mod props;
mod research; mod research;
mod rival; mod rival;
mod rogues; mod rogues;
mod balance;
mod talents;
mod weather;
mod names;
mod render3d; mod render3d;
mod rooms; mod rooms;
mod save; mod save;
@@ -138,7 +144,16 @@ fn world_action(app: &mut App, ui: &mut Ui, r3d: &R3d, inp: &mut Input, mx: f32,
(Tool::Dig | Tool::Room(_) | Tool::Sell | Tool::Bridge | Tool::Steel | Tool::Drawbridge | Tool::Wall, Some(t)) => inp.ldown = Some(t), (Tool::Dig | Tool::Room(_) | Tool::Sell | Tool::Bridge | Tool::Steel | Tool::Drawbridge | Tool::Wall, Some(t)) => inp.ldown = Some(t),
(Tool::Hand, _) => { (Tool::Hand, _) => {
let j = r3d.pick_ent(&app.game, &app.cam, mx, my, false); let j = r3d.pick_ent(&app.game, &app.cam, mx, my, false);
let Some(j) = j else { return }; let Some(j) = j else {
// a memorial stone: the hall of the fallen opens
let (gx, gy) = (wx.floor() as i32, wy.floor() as i32);
if map::inb(gx, gy) && app.game.memorial_at(map::idx(gx, gy)).is_some() {
ui.show_overview = true;
ui.ov_fallen = true;
ui.ov_scroll = 0;
}
return;
};
let (id, hero, dwarf) = (app.game.ents[j].id, app.game.ents[j].hero, app.game.ents[j].dwarf()); let (id, hero, dwarf) = (app.game.ents[j].id, app.game.ents[j].hero, app.game.ents[j].dwarf());
if dwarf { if dwarf {
return; return;
@@ -916,8 +931,12 @@ async fn main() {
let mut voice = voice::Voice::new(); let mut voice = voice::Voice::new();
let mut was_briefing = false; let mut was_briefing = false;
let mut cache_t = 0.0f32; let mut cache_t = 0.0f32;
#[cfg(not(target_arch = "wasm32"))]
audio::native::start();
loop { loop {
#[cfg(not(target_arch = "wasm32"))]
audio::native::pump();
let raw_dt = get_frame_time(); let raw_dt = get_frame_time();
let dt = raw_dt.min(0.1); let dt = raw_dt.min(0.1);
let now = get_time(); let now = get_time();
@@ -969,9 +988,19 @@ async fn main() {
// pixel look: the interface on a virtual screen whose pixels are whole parts of the world's // pixel look: the interface on a virtual screen whose pixels are whole parts of the world's
let rows = if [270, 360, 480].contains(&app.settings.pixel_rows) { app.settings.pixel_rows as u32 } else { 360 }; let rows = if [270, 360, 480].contains(&app.settings.pixel_rows) { app.settings.pixel_rows as u32 } else { 360 };
// the interface needs room: the smallest whole multiple of the world's rows with at least // the interface needs room: the smallest whole multiple of the world's rows with at least
// 900 rows on a desktop (360 -> 1080) and 540 on a touch screen (360 -> 720) // 900 rows (360 -> 1080), on a desktop and on a touch screen alike (a phone has about
let need: u32 = if touch_mode { 540 } else { 900 }; // 1080 device rows in landscape; a smaller screen draws in device pixels)
ui::set_virtual_rows(rows * need.div_ceil(rows)); let need: u32 = 900;
// measured on the shorter side: upright (portrait) the width gets those pixels and the
// height grows with the screen, so the world and the menus keep their size. The setting
// "interface size" picks the interface pixels per world pixel: large 2, medium 3, small 4
let short = match app.settings.ui_factor {
k @ 2..=4 => rows * k as u32,
_ => rows * need.div_ceil(rows),
};
let (w, h) = (screen_width().max(1.0), screen_height().max(1.0));
let vrows = if h > w { (short as f32 * h / w).round() as u32 } else { short };
ui::set_virtual_rows(vrows);
if touch_mode { if touch_mode {
// the pointer is the finger, or the position of this frame's gesture // the pointer is the finger, or the position of this frame's gesture
let gpos = gestures.iter().rev().find_map(|g| match *g { let gpos = gestures.iter().rev().find_map(|g| match *g {
@@ -1402,7 +1431,7 @@ async fn main() {
if !inp.rdrag && !over_ui && app.game.state == 0 { if !inp.rdrag && !over_ui && app.game.state == 0 {
if inp.ldown.is_some() { if inp.ldown.is_some() {
inp.ldown = None; inp.ldown = None;
} else if app.game.hand.is_empty() && matches!(ui.tool, Tool::Order(_)) { } else if app.game.hand.is_empty() && matches!(ui.tool, Tool::Order(_) | Tool::Flag) {
app.game.queue(Act::Unmark { x: wx, y: wy }); app.game.queue(Act::Unmark { x: wx, y: wy });
} else { } else {
app.game.queue(Act::Drop { x: wx, y: wy }); app.game.queue(Act::Drop { x: wx, y: wy });
@@ -1474,7 +1503,7 @@ async fn main() {
MenuAction::Save | MenuAction::FileSave => { MenuAction::Save | MenuAction::FileSave => {
let name = save_file_name(); let name = save_file_name();
platform::download(&name, app.autosave_json().as_bytes()); platform::download(&name, app.autosave_json().as_bytes());
app.set_status(i18n::trf("save.saved", &[name]), now); app.set_status(i18n::trf("save.saved", &[platform::saved_as(&name)]), now);
} }
MenuAction::CloudSave(id, name) => { MenuAction::CloudSave(id, name) => {
if cloud.save(id.as_deref(), &name, &app.autosave_json()) { if cloud.save(id.as_deref(), &name, &app.autosave_json()) {
@@ -1521,7 +1550,7 @@ async fn main() {
MenuAction::CampaignSave => { MenuAction::CampaignSave => {
let name = menu.campaign_doc.file_name(); let name = menu.campaign_doc.file_name();
platform::download(&name, menu.campaign_doc.save().as_bytes()); platform::download(&name, menu.campaign_doc.save().as_bytes());
app.set_status(i18n::trf("campaign.saved", &[name]), now); app.set_status(i18n::trf("campaign.saved", &[platform::saved_as(&name)]), now);
} }
MenuAction::CampaignEditName => { MenuAction::CampaignEditName => {
if let Some(value) = prompt_line(&i18n::tr("campaign.prompt_name"), &menu.campaign_doc.name, 60) { if let Some(value) = prompt_line(&i18n::tr("campaign.prompt_name"), &menu.campaign_doc.name, 60) {
@@ -1821,6 +1850,7 @@ app.pending_briefing = briefing_of(&campaign, i);
} }
UiAction::OpenMenu => menu.page = Some(Page::Pause), UiAction::OpenMenu => menu.page = Some(Page::Pause),
UiAction::SendGold(to, amount) => app.game.queue(Act::SendGold { to, amount }), UiAction::SendGold(to, amount) => app.game.queue(Act::SendGold { to, amount }),
UiAction::LearnTalent(branch) => app.game.queue(Act::Talent { branch }),
UiAction::Locked => { UiAction::Locked => {
let locked = ui.last_locked.unwrap_or(usize::MAX); let locked = ui.last_locked.unwrap_or(usize::MAX);
app.game.msg(if app.game.can_research(locked) { "msg.tool_research" } else { "msg.tool_locked" }, vec![]); app.game.msg(if app.game.can_research(locked) { "msg.tool_research" } else { "msg.tool_locked" }, vec![]);
@@ -1978,6 +2008,7 @@ app.pending_briefing = briefing_of(&campaign, i);
for ev in app.game.events.drain(..) { for ev in app.game.events.drain(..) {
sounds.play(ev, vec2(app.cam.rx, app.cam.rz), right, hear, now); sounds.play(ev, vec2(app.cam.rx, app.cam.rz), right, hear, now);
} }
sounds.ambience(&app.game, (app.cam.rx, app.cam.rz), now);
// a won mission goes into the campaign progress (with the carry-over); a co-op mission of an // a won mission goes into the campaign progress (with the carry-over); a co-op mission of an
// editor campaign keeps no progress (its index is not a built-in mission) // editor campaign keeps no progress (its index is not a built-in mission)
if app.coop_editor { if app.coop_editor {
@@ -2324,7 +2355,11 @@ app.pending_briefing = briefing_of(&ed.doc, i);
ui.show_help = false; ui.show_help = false;
r3d.invalidate(); r3d.invalidate();
} }
editor::EdAction::SaveFile => platform::download(&ed.doc.file_name(), ed.doc.save().as_bytes()), editor::EdAction::SaveFile => {
let name = ed.doc.file_name();
platform::download(&name, ed.doc.save().as_bytes());
ed.set_status(i18n::trf("campaign.saved", &[platform::saved_as(&name)]));
}
editor::EdAction::OpenFile => platform::open_file(), editor::EdAction::OpenFile => platform::open_file(),
editor::EdAction::ServerSave => { editor::EdAction::ServerSave => {
if !cloud.available() { if !cloud.available() {
+15 -8
View File
@@ -141,13 +141,12 @@ fn item_value(it: Item, s: &Settings) -> String {
_ => "val.medium", _ => "val.medium",
}), }),
Item::Lights => s.max_lights.to_string(), Item::Lights => s.max_lights.to_string(),
Item::UiScale => { Item::UiScale => tr(match s.ui_factor {
if s.ui_factor == 0 { 2 => "val.large",
trf("val.auto", &[crate::ui::F().to_string()]) 3 => "val.medium",
} else { 4 => "val.small",
format!("{}x", s.ui_factor) _ => "val.auto_plain",
} }),
}
Item::DebugFps => onoff(s.debug_fps), Item::DebugFps => onoff(s.debug_fps),
Item::DebugFrameTime => onoff(s.debug_frame_time), Item::DebugFrameTime => onoff(s.debug_frame_time),
Item::DebugPing => onoff(s.debug_ping), Item::DebugPing => onoff(s.debug_ping),
@@ -202,7 +201,7 @@ fn item_change(it: Item, s: &mut Settings, d: i32) {
Item::Outlines => s.outlines = !s.outlines, Item::Outlines => s.outlines = !s.outlines,
Item::WallDetail => s.wall_detail = cycle(&[1u8, 2, 3], s.wall_detail, d), Item::WallDetail => s.wall_detail = cycle(&[1u8, 2, 3], s.wall_detail, d),
Item::Lights => s.max_lights = cycle(&[8u8, 16, 24], s.max_lights, d), Item::Lights => s.max_lights = cycle(&[8u8, 16, 24], s.max_lights, d),
Item::UiScale => s.ui_factor = cycle(&[0u8, 1, 2, 3, 4], s.ui_factor, d), Item::UiScale => s.ui_factor = cycle(&[0u8, 2, 3, 4], if [0, 2, 3, 4].contains(&s.ui_factor) { s.ui_factor } else { 0 }, d),
Item::RenderScale => s.pixel_rows = cycle(&[270u16, 360, 480], if [270, 360, 480].contains(&s.pixel_rows) { s.pixel_rows } else { 360 }, d), Item::RenderScale => s.pixel_rows = cycle(&[270u16, 360, 480], if [270, 360, 480].contains(&s.pixel_rows) { s.pixel_rows } else { 360 }, d),
Item::DebugFps => s.debug_fps = !s.debug_fps, Item::DebugFps => s.debug_fps = !s.debug_fps,
Item::DebugFrameTime => s.debug_frame_time = !s.debug_frame_time, Item::DebugFrameTime => s.debug_frame_time = !s.debug_frame_time,
@@ -304,6 +303,7 @@ enum Btn {
MatchChat, MatchChat,
Bestiary, Bestiary,
Achievements, Achievements,
AchHall, // achievements page: switch to the hall of the fallen and back
Tutorial, Tutorial,
BestTab(usize), BestTab(usize),
BestSel(i32), BestSel(i32),
@@ -455,6 +455,7 @@ pub struct Menu {
scroll: usize, scroll: usize,
rows_visible: usize, rows_visible: usize,
pub profile: crate::achievements::Profile, // shown on the achievements page pub profile: crate::achievements::Profile, // shown on the achievements page
ach_hall: bool, // the achievements page shows the hall of the fallen
buttons: Vec<(Rect, Btn)>, buttons: Vec<(Rect, Btn)>,
lang_open: Option<Rect>, // the language dropdown is open below/above this field lang_open: Option<Rect>, // the language dropdown is open below/above this field
lang_scroll: usize, // first language shown in the open dropdown lang_scroll: usize, // first language shown in the open dropdown
@@ -498,6 +499,7 @@ impl Menu {
focus: 0, focus: 0,
error: None, error: None,
match_ui: MatchUi::default(), match_ui: MatchUi::default(),
ach_hall: false,
coop_mission: crate::campaign::coop_mission_indexes().first().copied().unwrap_or(0), coop_mission: crate::campaign::coop_mission_indexes().first().copied().unwrap_or(0),
match_size: 128, match_size: 128,
coop_editor: false, coop_editor: false,
@@ -940,6 +942,11 @@ impl Menu {
} }
MenuAction::Consumed MenuAction::Consumed
} }
Some(Btn::AchHall) => {
self.ach_hall = !self.ach_hall;
self.scroll = 0;
MenuAction::Consumed
}
Some(Btn::Achievements) => { Some(Btn::Achievements) => {
self.page = Some(Page::Achievements { from_title }); self.page = Some(Page::Achievements { from_title });
self.scroll = 0; self.scroll = 0;
+69 -2
View File
@@ -15,7 +15,13 @@ impl Menu {
let rows = 40.min((sh / CH()) as i32 - 2); let rows = 40.min((sh / CH()) as i32 - 2);
let x = grid_center(sw, cols as f32 * CW()).max(0.0); let x = grid_center(sw, cols as f32 * CW()).max(0.0);
let y = grid_center_y(sh, rows as f32 * CH()).max(0.0); let y = grid_center_y(sh, rows as f32 * CH()).max(0.0);
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.achievements"), FRAME_HI); let title = if self.ach_hall { tr("hall.title") } else { tr("menu.achievements") };
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &title, FRAME_HI);
if self.ach_hall {
self.draw_hall_page(gfx, x, y, cols, rows);
self.ach_buttons(gfx, x, y, rows);
return;
}
let list = all(); let list = all();
let done = list.iter().filter(|a| self.profile.unlocked.contains_key(&a.0)).count(); let done = list.iter().filter(|a| self.profile.unlocked.contains_key(&a.0)).count();
let head = trf("ach.count", &[done.to_string(), list.len().to_string(), crate::achievements::fmt_count(self.profile.total("minutes"))]); let head = trf("ach.count", &[done.to_string(), list.len().to_string(), crate::achievements::fmt_count(self.profile.total("minutes"))]);
@@ -57,14 +63,75 @@ impl Menu {
} }
} }
// scrolling and back // scrolling and back
let _ = ix;
self.ach_buttons(gfx, x, y, rows);
}
// scrolling, the switch between achievements and the hall, back
fn ach_buttons(&mut self, gfx: &Gfx, x: f32, y: f32, rows: i32) {
let ix = x + CW() * 3.0;
let by = y + (rows - 3) as f32 * CH(); let by = y + (rows - 3) as f32 * CH();
let (mx, my) = mouse(); let (mx, my) = mouse();
let bw = 14.0 * CW(); let bw = 14.0 * CW();
let items = [(Btn::Scroll(-1), "▲".to_string()), (Btn::Scroll(1), "▼".to_string()), (Btn::Back, tr("menu.back"))]; let switch = if self.ach_hall { tr("menu.achievements") } else { tr("hall.title") };
let items = [(Btn::Scroll(-1), "▲".to_string()), (Btn::Scroll(1), "▼".to_string()), (Btn::AchHall, switch), (Btn::Back, tr("menu.back"))];
let bw = bw.min((sw() - ix - CW() * 4.0) / 4.0 - CW());
for (k, (b, label)) in items.iter().enumerate() { for (k, (b, label)) in items.iter().enumerate() {
let r = Rect::new(ix + k as f32 * (bw + CW()), by, bw, CH() * 2.0); let r = Rect::new(ix + k as f32 * (bw + CW()), by, bw, CH() * 2.0);
button(gfx, r, label, r.contains(vec2(mx, my)), FRAME); button(gfx, r, label, r.contains(vec2(mx, my)), FRAME);
self.buttons.push((r, *b)); self.buttons.push((r, *b));
} }
} }
// the hall of the fallen over all games: newest first, two lines each
fn draw_hall_page(&mut self, gfx: &Gfx, x: f32, y: f32, cols: i32, rows: i32) {
let hall = &self.profile.hall;
gfx.text(&trf("hall.count", &[hall.len().to_string()]), x + CW() * 3.0, y + CH() * 1.6, S(), GOLD);
let top = y + CH() * 3.5;
let bottom = y + (rows - 4) as f32 * CH();
let row_h = CH() * 2.4;
self.rows_visible = (((bottom - top) / row_h).floor() as usize).max(1);
self.scroll = self.scroll.min(hall.len().saturating_sub(self.rows_visible));
let ix = x + CW() * 3.0;
let maxc = (cols - 6) as usize;
if hall.is_empty() {
gfx.text(&tr("hall.empty"), ix, top, S(), DIM);
}
for (k, f) in hall.iter().rev().enumerate().skip(self.scroll).take(self.rows_visible) {
let ry = top + (k - self.scroll) as f32 * row_h;
let name = format!("{} ({})", crate::names::full_name(f.kind, f.name, f.level), trf("info.level", &[tr(crate::game::st(f.kind).name), f.level.to_string()]));
gfx.text(&name.chars().take(maxc).collect::<String>(), ix, ry, S(), FRAME_HI);
let kills = if f.kills > 0 { format!(" · {}", trf("hall.kills", &[f.kills.to_string()])) } else { String::new() };
let line = format!("{}{} · {}", crate::names::cause(f.bane), kills, date(f.when));
gfx.text(&line.chars().take(maxc * 2).collect::<String>(), ix + CW() * 2.0, ry + CH(), SMALL(), DIM);
}
}
}
// a unix time as a date (year-month-day)
fn date(unix: u64) -> String {
if unix == 0 {
return String::new();
}
// days since 1970-01-01 to a civil date (Howard Hinnant's algorithm)
let z = (unix / 86400) as i64 + 719468;
let era = z.div_euclid(146097);
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = yoe + era * 400 + if m <= 2 { 1 } else { 0 };
format!("{:04}-{:02}-{:02}", y, m, d)
}
#[cfg(test)]
mod tests {
#[test]
fn dates_are_civil() {
assert_eq!(super::date(0), "");
assert_eq!(super::date(1_798_675_200), "2026-12-31");
assert_eq!(super::date(1_709_164_800), "2024-02-29");
}
} }
+40
View File
@@ -109,6 +109,8 @@ enum Act {
Terrain(String, String), Terrain(String, String),
Kill(String, i32), Kill(String, i32),
Trigger(String, bool), Trigger(String, bool),
Weather(u8, f64),
TalentXp(f64),
} }
// the triggering unit (for "where the unit is" and "the unit is a ...") // the triggering unit (for "where the unit is" and "the unit is a ...")
@@ -539,6 +541,12 @@ impl Game {
Val::Or { a, b } => (self.cond(s, a, ev) || self.cond(s, b, ev)) as i32 as f64, Val::Or { a, b } => (self.cond(s, a, ev) || self.cond(s, b, ev)) as i32 as f64,
Val::Not { a } => (!self.cond(s, a, ev)) as i32 as f64, Val::Not { a } => (!self.cond(s, a, ev)) as i32 as f64,
Val::EventKind { kind } => (ev.kind.is_some() && ev.kind == kind_by_name(kind)) as i32 as f64, Val::EventKind { kind } => (ev.kind.is_some() && ev.kind == kind_by_name(kind)) as i32 as f64,
Val::WeatherIs { kind } => {
// the weather felt now (a change counts once it has come in)
let w = &self.weather;
let now = if w.level > 0.5 { w.shown } else { crate::weather::FAIR };
(crate::blocks::WEATHER.iter().position(|k| k == kind) == Some(now as usize)) as i32 as f64
}
}; };
if r.is_finite() { if r.is_finite() {
r r
@@ -568,6 +576,8 @@ impl Game {
Block::Wave { count } => Act::Wave(self.eval(s, count, ev).round() as i64), Block::Wave { count } => Act::Wave(self.eval(s, count, ev).round() as i64),
Block::Gold { amount } => Act::Gold(self.eval(s, amount, ev)), Block::Gold { amount } => Act::Gold(self.eval(s, amount, ev)),
Block::Mana { amount } => Act::Mana(self.eval(s, amount, ev)), Block::Mana { amount } => Act::Mana(self.eval(s, amount, ev)),
Block::Weather { kind, secs } => Act::Weather(crate::blocks::WEATHER.iter().position(|k| k == kind).unwrap_or(0) as u8, self.eval(s, secs, ev)),
Block::TalentXp { amount } => Act::TalentXp(self.eval(s, amount, ev)),
Block::SetVar { name, value } => { Block::SetVar { name, value } => {
let v = self.eval(s, value, ev); let v = self.eval(s, value, ev);
s.vars.insert(name.clone(), v); s.vars.insert(name.clone(), v);
@@ -824,6 +834,13 @@ impl Game {
} }
} }
Act::Mana(n) => self.mana = (self.mana + n as f32).clamp(0.0, self.mana_max()), Act::Mana(n) => self.mana = (self.mana + n as f32).clamp(0.0, self.mana_max()),
// the weather comes (softly) and stays the given seconds (0: until a block changes it)
Act::Weather(k, secs) => {
self.weather.kind = k;
self.weather.left = if secs > 0.0 { secs as f32 } else { 1.0e9 };
self.weather.strike_t = self.weather.strike_t.min(5.0);
}
Act::TalentXp(n) => self.add_master_xp(n.max(0.0) as f32),
Act::Win => { Act::Win => {
self.state = 1; self.state = 1;
self.script_msg("msg.mission_won", vec![]); self.script_msg("msg.mission_won", vec![]);
@@ -854,6 +871,7 @@ impl Game {
"water" => Kind::Water, "water" => Kind::Water,
"lava" => Kind::Lava, "lava" => Kind::Lava,
"gold" => Kind::Gold, "gold" => Kind::Gold,
"grove" => Kind::Grove,
_ => Kind::Floor, _ => Kind::Floor,
}; };
for i in zone_tiles(&z) { for i in zone_tiles(&z) {
@@ -960,6 +978,28 @@ mod tests {
assert!(g.script.as_ref().unwrap().error.is_none(), "{:?}", g.script.as_ref().unwrap().error); assert!(g.script.as_ref().unwrap().error.is_none(), "{:?}", g.script.as_ref().unwrap().error);
} }
#[test]
fn weather_and_talent_blocks_work() {
let mut g = Game::new(3, 64, 64);
g.add_surface(3, 0.5, 2);
let scripts = MissionScripts {
start: vec![
Block::Weather { kind: "storm".into(), secs: Val::num(0.0) },
Block::TalentXp { amount: Val::num(400.0) },
Block::Wait { secs: Val::num(25.0) },
Block::If { cond: Val::WeatherIs { kind: "storm".into() }, then: vec![Block::SetVar { name: "storm".into(), value: Val::num(1.0) }], els: vec![] },
],
..Default::default()
};
g.script = Some(ScriptState::new(scripts, vec![], 7));
run(&mut g, 30.0);
assert_eq!(g.weather.kind, crate::weather::STORM);
assert!(g.weather.left > 1.0e8, "it stays");
assert!(g.talents.xp >= 400.0 && g.talents.free() >= 1);
assert_eq!(g.script.as_ref().unwrap().vars.get("storm"), Some(&1.0), "the condition sees the storm");
assert!(crate::blocks::check(&g.script.as_ref().unwrap().scripts.start, &crate::blocks::Names { zones: &[], triggers: &[], vars: &[] }).iter().all(|p| p.key != "blk.err_weather"));
}
#[test] #[test]
fn a_zone_trigger_spawns_and_the_victory_script_wins() { fn a_zone_trigger_spawns_and_the_victory_script_wins() {
let mut g = Game::new(3, 64, 64); let mut g = Game::new(3, 64, 64);
+289
View File
@@ -0,0 +1,289 @@
// Named creatures: every creature of a master carries a name seed from its birth. The name
// comes from the list of its kind in the language files (so it follows a language switch);
// from level 5 on it earns a humorous epithet, from level 8 a grand one. A creature of level
// 3 or more that falls leaves a memorial stone with its name on its master's ground.
use crate::game::*;
use crate::i18n::{tr, trf};
use crate::map::*;
use serde::{Deserialize, Serialize};
// the fallen are remembered in the hall from HALL_LEVEL on; only the greatest (MEMORIAL_LEVEL)
// get a stone on the map, at most MAX_MEMORIALS of them (the oldest go)
pub const HALL_LEVEL: u32 = 3;
pub const MEMORIAL_LEVEL: u32 = 10;
pub const MAX_MEMORIALS: usize = 30;
// what killed a creature besides another unit (Ent::bane)
pub const BANE_SNARE: u8 = 250;
pub const BANE_LIGHTNING: u8 = 251;
pub const BANE_LAVA: u8 = 252;
pub const BANE_WATER: u8 = 253;
// an entry of the hall of the fallen (in the game and in the player's profile)
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Fallen {
pub kind: EK,
pub name: u32,
pub level: u32,
pub kills: u32,
pub bane: u8,
pub time: f32, // game seconds when it fell
#[serde(default)]
pub when: u64, // unix time (set when it goes into the profile)
}
// how it fell, in words
pub fn cause(bane: u8) -> String {
match bane {
BANE_SNARE => tr("cause.snare"),
BANE_LIGHTNING => tr("cause.lightning"),
BANE_LAVA => tr("cause.lava"),
BANE_WATER => tr("cause.water"),
0 => tr("cause.fell"),
k => match crate::gfx::SPRITE_KINDS.get(k as usize - 1) {
Some(&kind) => trf("cause.slain", &[tr(st(kind).name)]),
None => tr("cause.fell"),
},
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Memorial {
pub tile: usize,
pub kind: EK,
pub name: u32,
pub level: u32,
}
// the name list of a kind of creature
pub fn group(k: EK) -> &'static str {
match k {
EK::Worker => "imp",
EK::Kobold => "kobold",
EK::Spider => "spider",
EK::Mage => "mage",
EK::Ogre => "ogre",
EK::Jailer => "jailer",
EK::Priestess => "priestess",
EK::Chainbreaker => "chainbreaker",
EK::Bat => "bat",
EK::Gargoyle => "gargoyle",
EK::Owl => "owl",
EK::Wyrm => "wyrm",
EK::Cogling => "cogling",
EK::Revenant => "revenant",
EK::CaveBear | EK::Beetle => "beast",
_ => "human",
}
}
// the seed of a new creature's name (from its id: the same on every computer of a match)
pub fn seed_for(id: u32) -> u32 {
(id.wrapping_mul(2654435761) ^ 0x5bd1_e995) | 1
}
fn pick(key: &str, seed: u32) -> Option<String> {
let list = tr(key);
if list == key {
return None; // no list in this language
}
let items: Vec<&str> = list.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect();
(!items.is_empty()).then(|| items[seed as usize % items.len()].to_string())
}
// the name with its epithet (if the level has earned one); "" if the creature has no name
pub fn full_name(kind: EK, seed: u32, level: u32) -> String {
if seed == 0 {
return String::new();
}
let Some(base) = pick(&format!("names.{}", group(kind)), seed) else { return String::new() };
let list = if level >= 8 { "epithets.high" } else { "epithets.mid" };
match pick(list, seed >> 7).filter(|_| level >= 5) {
Some(ep) => trf("name.epithet", &[base, ep]),
None => base,
}
}
// the name of a unit, or its kind's name if it has none
pub fn display_name(e: &Ent) -> String {
let n = full_name(e.kind, e.name, e.level);
if n.is_empty() { tr(st(e.kind).name) } else { n }
}
// a message argument that is turned into the name when the message is shown (i18n::trf)
pub fn arg(e: &Ent) -> String {
if e.name == 0 {
return format!("@{}", st(e.kind).name);
}
format!("%{}.{}.{}", crate::gfx::sprite_index(e.kind), e.name, e.level)
}
// the counterpart of `arg`
pub fn from_arg(a: &str) -> String {
let p: Vec<u32> = a.split('.').filter_map(|s| s.parse().ok()).collect();
match p.as_slice() {
[k, seed, level] => {
let kind = crate::gfx::SPRITE_KINDS.get(*k as usize).copied().unwrap_or(EK::Kobold);
let n = full_name(kind, *seed, *level);
if n.is_empty() { tr(st(kind).name) } else { n }
}
_ => a.to_string(),
}
}
impl Game {
// a named creature of some standing fell: a stone with its name on its master's ground, as
// near to where it fell as there is plain floor
pub(crate) fn raise_memorial(&mut self, e: &Ent) -> bool {
if e.hero || e.name == 0 || e.level < HALL_LEVEL || !Owner::MASTER_SIDES.contains(&e.side) {
return false;
}
// the hall of the fallen keeps the local master's (also without room for a stone)
if e.side == self.local_side {
self.hall.push(Fallen { kind: e.kind, name: e.name, level: e.level, kills: e.kills, bane: e.bane, time: self.time, when: 0 });
}
// a stone only for the greatest
if e.level < MEMORIAL_LEVEL {
return false; // the usual message tells of it
}
let owner = Owner::of_side(e.side);
let (x, y) = (e.x as i32, e.y as i32);
let taken: Vec<usize> = self.memorials.iter().map(|m| m.tile).collect();
let mut best: Option<(usize, i32)> = None;
for dy in -6..=6 {
for dx in -6..=6 {
let (tx, ty) = (x + dx, y + dy);
if !inb(tx, ty) {
continue;
}
let i = idx(tx, ty);
let t = &self.map.t[i];
if t.kind == Kind::Floor && t.owner == owner && !taken.contains(&i) {
let d = dx * dx + dy * dy;
if best.map_or(true, |b| d < b.1) {
best = Some((i, d));
}
}
}
}
let Some((tile, _)) = best else { return false };
if self.memorials.len() >= MAX_MEMORIALS {
self.memorials.remove(0);
}
self.memorials.push(Memorial { tile, kind: e.kind, name: e.name, level: e.level });
if e.side == self.local_side {
self.tell_local("msg.creature_fallen", vec![arg(e)], None);
}
true
}
// stones on ground that was lost or built over are taken away
pub(crate) fn tidy_memorials(&mut self) {
let map = &self.map;
self.memorials.retain(|m| map.t[m.tile].kind == Kind::Floor && Owner::MASTER_SIDES.contains(&map.t[m.tile].owner.side()));
}
pub fn memorial_at(&self, i: usize) -> Option<&Memorial> {
self.memorials.iter().find(|m| m.tile == i)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creatures_have_names_and_earn_epithets() {
let mut g = Game::new(9, 64, 64);
let (cx, cy) = g.map.core;
let id = g.spawn(EK::Ogre, false, cx as f32 + 0.5, cy as f32 + 4.5, 1);
let e = g.ents.iter().find(|e| e.id == id).unwrap().clone();
assert!(e.name != 0);
let n1 = full_name(e.kind, e.name, 1);
assert!(!n1.is_empty() && !n1.contains("names."), "{}", n1);
let n5 = full_name(e.kind, e.name, 5);
let n9 = full_name(e.kind, e.name, 9);
assert!(n5.starts_with(&n1) || n5.ends_with(&n1), "{} / {}", n1, n5);
assert!(n5.len() > n1.len() && n9 != n5, "{} {} {}", n1, n5, n9);
assert_eq!(from_arg(arg(&e).trim_start_matches('%')), display_name(&e));
// heroes have none
let h = g.spawn(EK::Knight, true, cx as f32 + 0.5, cy as f32 + 5.5, 1);
assert_eq!(g.ents.iter().find(|e| e.id == h).unwrap().name, 0);
}
#[test]
fn the_hall_remembers_kills_and_how_they_fell() {
let mut g = Game::new(9, 64, 64);
let (cx, cy) = g.map.core;
for y in cy + 4..cy + 8 {
for x in cx..cx + 6 {
let t = &mut g.map.t[idx(x, y)];
t.kind = Kind::Floor;
t.owner = Owner::Player;
}
}
let mut t = crate::achievements::Tracker::new(Default::default());
let ctx = crate::achievements::Context { coop: false, editor_test: false };
g.time = 1.0;
t.update(&g, &ctx, 6.0, 1_800_000_000);
let ogre = g.spawn(EK::Ogre, false, cx as f32 + 1.5, cy as f32 + 5.5, 5);
let knight = g.spawn(EK::Knight, true, cx as f32 + 2.5, cy as f32 + 5.5, 1);
// the ogre slays the knight
let (oj, kj) = (g.ents.iter().position(|e| e.id == ogre).unwrap(), g.ents.iter().position(|e| e.id == knight).unwrap());
g.ents[kj].hp = 1.0;
let mut o = g.ents[oj].clone();
g.hit_for_test(&mut o, kj);
assert_eq!(o.kills, 1, "a hero slain");
g.ents[oj] = o;
// a second knight brings the ogre down
let k2 = g.spawn(EK::Knight, true, cx as f32 + 2.5, cy as f32 + 6.5, 1);
let k2j = g.ents.iter().position(|e| e.id == k2).unwrap();
let oj = g.ents.iter().position(|e| e.id == ogre).unwrap();
g.ents[oj].hp = 1.0;
let mut k = g.ents[k2j].clone();
g.hit_for_test(&mut k, oj);
assert_eq!(g.hall.len(), 1);
let f = &g.hall[0];
assert_eq!((f.kind, f.kills), (EK::Ogre, 1));
assert_eq!(f.bane as u32, crate::gfx::sprite_index(EK::Knight) + 1);
assert!(cause(f.bane).contains(&crate::i18n::tr("unit.knight")));
// into the profile, once
g.time = 100.0;
t.update(&g, &ctx, 6.0, 1_800_000_100);
t.update(&g, &ctx, 6.0, 1_800_000_200);
assert_eq!(t.profile.hall.len(), 1);
assert_eq!(t.profile.hall[0].when, 1_800_000_100);
}
#[test]
fn the_fallen_get_a_memorial_stone() {
let mut g = Game::new(9, 64, 64);
let (cx, cy) = g.map.core;
for y in cy + 4..cy + 8 {
for x in cx..cx + 4 {
let t = &mut g.map.t[idx(x, y)];
t.kind = Kind::Floor;
t.owner = Owner::Player;
}
}
let id = g.spawn(EK::Ogre, false, cx as f32 + 1.5, cy as f32 + 5.5, 10);
let j = g.ents.iter().position(|e| e.id == id).unwrap();
g.kill(j);
assert_eq!(g.memorials.len(), 1);
let m = g.memorials[0].clone();
assert_eq!((m.kind, m.level), (EK::Ogre, 10));
// level 4: in the hall, but no stone on the map
let o4 = g.spawn(EK::Ogre, false, cx as f32 + 3.5, cy as f32 + 5.5, 4);
let j = g.ents.iter().position(|e| e.id == o4).unwrap();
g.kill(j);
assert_eq!(g.memorials.len(), 1);
assert_eq!(g.hall.len(), 2);
assert!(g.memorial_at(m.tile).is_some());
// a level 1 kobold is not remembered
let k = g.spawn(EK::Kobold, false, cx as f32 + 2.5, cy as f32 + 5.5, 1);
let j = g.ents.iter().position(|e| e.id == k).unwrap();
g.kill(j);
assert_eq!(g.memorials.len(), 1);
}
}
+1 -1
View File
@@ -365,7 +365,7 @@ impl Game {
// the seat keeps its master (dungeon, gold, creatures): the brain (ai.rs) plays it // the seat keeps its master (dungeon, gold, creatures): the brain (ai.rs) plays it
let core = self.core_of_side(side); let core = self.core_of_side(side);
let difficulty = self.ai_difficulty; let difficulty = self.ai_difficulty;
self.rivals.push(crate::rival::Rival { side, core, hp: crate::rival::RIVAL_CORE_HP, alive: true, radius: 6, grow_t: 0.0, spawn_t: 0.0, attack_t: 0.0, difficulty, power: 0.0, gift_t: crate::rival::PARTNER_GIFT_EVERY, think_t: 1.0, attacking: false, army0: 0.0 }); self.rivals.push(crate::rival::Rival { side, core, hp: crate::rival::RIVAL_CORE_HP, alive: true, radius: 6, grow_t: 0.0, spawn_t: 0.0, attack_t: 0.0, difficulty, power: 0.0, gift_t: crate::rival::PARTNER_GIFT_EVERY, think_t: 1.0, attacking: false, army0: 0.0, focus: None, defending: false });
} }
} }
+35 -1
View File
@@ -86,7 +86,9 @@ impl Game {
let (mx, my) = xy(m); let (mx, my) = xy(m);
(mx - x).abs() <= 1 && (my - y).abs() <= 1 (mx - x).abs() <= 1 && (my - y).abs() <= 1
}) { }) {
return; // there is one already // a second click (tap) on a marker takes it away again
self.unmark(x as f32 + 0.5, y as f32 + 0.5);
return;
} }
if marks.len() >= MAX_MARKS { if marks.len() >= MAX_MARKS {
self.hint(31, "hint.marks_full"); self.hint(31, "hint.marks_full");
@@ -107,6 +109,8 @@ impl Game {
// a right click with an order tool: the marker there goes (its creatures are free again); // a right click with an order tool: the marker there goes (its creatures are free again);
// without a marker there the guards stand down // without a marker there the guards stand down
pub fn unmark(&mut self, wx: f32, wy: f32) { pub fn unmark(&mut self, wx: f32, wy: f32) {
// the command markers there go with it
self.marks.retain(|m| (m.x - wx).powi(2) + (m.y - wy).powi(2) >= 2.0 * 2.0);
let near = |m: &usize| { let near = |m: &usize| {
let (mx, my) = xy(*m); let (mx, my) = xy(*m);
(mx as f32 + 0.5 - wx).powi(2) + (my as f32 + 0.5 - wy).powi(2) < 1.6 * 1.6 (mx as f32 + 0.5 - wx).powi(2) + (my as f32 + 0.5 - wy).powi(2) < 1.6 * 1.6
@@ -128,6 +132,11 @@ impl Game {
} }
} }
} }
} else if let Some(f) = self.flag.filter(|f| near(f)) {
// the rally flag goes the same way
let (fx, fy) = xy(f);
self.set_flag(fx, fy);
return;
} else { } else {
self.cancel_orders(); self.cancel_orders();
return; return;
@@ -329,6 +338,31 @@ mod tests {
assert_eq!(g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count(), 0); assert_eq!(g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count(), 0);
} }
#[test]
fn a_second_tap_removes_a_marker_and_right_click_takes_the_flag() {
let mut g = Game::new(14, 64, 64);
let (cx, cy) = g.map.core;
for y in cy - 6..=cy + 8 {
for x in cx - 6..=cx + 10 {
let t = &mut g.map.t[idx(x, y)];
if t.kind != Kind::Core {
t.kind = Kind::Floor;
t.owner = Owner::Player;
}
}
}
g.place_mark(cx + 5, cy + 5, false);
g.place_mark(cx + 7, cy + 2, true);
assert_eq!((g.guard_marks.len(), g.attack_marks.len()), (1, 1));
g.place_mark(cx + 5, cy + 5, false);
g.place_mark(cx + 7, cy + 2, true);
assert_eq!((g.guard_marks.len(), g.attack_marks.len()), (0, 0), "tapped again: gone");
g.set_flag(cx + 4, cy - 3);
assert!(g.flag.is_some());
g.unmark(cx as f32 + 4.5, cy as f32 - 2.5);
assert!(g.flag.is_none(), "the flag goes too");
}
#[test] #[test]
fn shields_are_held_and_swords_share_the_creatures() { fn shields_are_held_and_swords_share_the_creatures() {
let mut g = Game::new(14, 64, 64); let mut g = Game::new(14, 64, 64);
+128 -14
View File
@@ -1,7 +1,7 @@
// Browser integration via our own miniquad JS plugin (www/dc_plugin.js): // Browser integration via our own miniquad JS plugin (www/dc_plugin.js):
// persistent key/value storage (localStorage), a cache that is written to storage when the // persistent key/value storage (localStorage), a cache that is written to storage when the
// page is hidden or closed, file download, file upload and HTTP requests. // page is hidden or closed, file download, file upload and HTTP requests.
// Native builds use simple fallbacks. // Native builds keep the storage as files in the data directory (see imp below).
// Result of an HTTP request: status 0 means the service could not be reached // Result of an HTTP request: status 0 means the service could not be reached
pub struct HttpResult { pub struct HttpResult {
@@ -113,6 +113,11 @@ mod imp {
unsafe { dc_cache_set(key.as_ptr(), key.len() as u32, val.as_ptr(), val.len() as u32) } unsafe { dc_cache_set(key.as_ptr(), key.len() as u32, val.as_ptr(), val.len() as u32) }
} }
// where download() put a file, for the status line: the browser's download folder
pub fn saved_as(name: &str) -> String {
name.to_string()
}
pub fn download(name: &str, data: &[u8]) { pub fn download(name: &str, data: &[u8]) {
unsafe { dc_download(name.as_ptr(), name.len() as u32, data.as_ptr(), data.len() as u32) } unsafe { dc_download(name.as_ptr(), name.len() as u32, data.as_ptr(), data.len() as u32) }
} }
@@ -188,14 +193,84 @@ mod imp {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
mod imp { mod imp {
use std::cell::RefCell; // Desktop builds: the storage keys are files in the data directory, savegames and
// campaigns are written to its "saves" folder, the system file dialog opens files.
use std::collections::HashMap; use std::collections::HashMap;
thread_local! { use std::path::PathBuf;
static STORE: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new()); use std::sync::Mutex;
static STORE: Mutex<Option<HashMap<String, String>>> = Mutex::new(None);
static PICKED: Mutex<Option<Vec<u8>>> = Mutex::new(None);
static PICKING: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
// DUNGEONCRAFT_DATA, else the usual per-user application data folder
pub fn data_dir() -> PathBuf {
let env = |k: &str| std::env::var_os(k).filter(|v| !v.is_empty()).map(PathBuf::from);
if let Some(d) = env("DUNGEONCRAFT_DATA") {
return d;
}
if cfg!(target_os = "windows") {
if let Some(d) = env("APPDATA") {
return d.join("DungeonCraft");
}
} else if cfg!(target_os = "macos") {
if let Some(h) = env("HOME") {
return h.join("Library/Application Support/DungeonCraft");
}
} else if let Some(d) = env("XDG_DATA_HOME").or_else(|| env("HOME").map(|h| h.join(".local/share"))) {
return d.join("dungeoncraft");
}
PathBuf::from("dungeoncraft-data")
} }
pub fn saves_dir() -> PathBuf {
data_dir().join("saves")
}
// a storage key as a file name: only harmless characters
pub(super) fn key_file(key: &str) -> String {
let name: String = key.chars().map(|c| if c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_') { c } else { '_' }).collect();
if name.is_empty() || name.starts_with('.') {
format!("_{}", name)
} else {
name
}
}
// writes through a temporary file, so a crash never leaves half a file
fn write_file(path: &std::path::Path, data: &[u8]) -> std::io::Result<()> {
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir)?;
}
let tmp = path.with_extension("tmp");
std::fs::write(&tmp, data)?;
std::fs::rename(&tmp, path)
}
pub fn storage_get(key: &str) -> Option<String> { pub fn storage_get(key: &str) -> Option<String> {
STORE.with(|s| s.borrow().get(key).cloned()) let mut store = STORE.lock().unwrap_or_else(|e| e.into_inner());
let store = store.get_or_insert_with(HashMap::new);
if let Some(v) = store.get(key) {
return Some(v.clone());
}
let v = std::fs::read_to_string(data_dir().join("store").join(key_file(key))).ok()?;
store.insert(key.to_string(), v.clone());
Some(v)
} }
pub fn storage_set(key: &str, val: &str) {
STORE.lock().unwrap_or_else(|e| e.into_inner()).get_or_insert_with(HashMap::new).insert(key.to_string(), val.to_string());
let path = data_dir().join("store").join(key_file(key));
if let Err(e) = write_file(&path, val.as_bytes()) {
eprintln!("DungeonCraft: cannot write {}: {}", path.display(), e);
}
}
// there is no "page left" moment on the desktop: write at once
pub fn cache_on_leave(key: &str, val: &str) {
storage_set(key, val);
}
pub fn prompt(_title: &str, _default: &str) -> Option<String> { pub fn prompt(_title: &str, _default: &str) -> Option<String> {
None None
} }
@@ -208,19 +283,47 @@ mod imp {
pub fn fetch_download_state() -> i32 { pub fn fetch_download_state() -> i32 {
0 0
} }
pub fn storage_set(key: &str, val: &str) {
STORE.with(|s| s.borrow_mut().insert(key.to_string(), val.to_string())); // a "download" lands in the saves folder
}
pub fn cache_on_leave(key: &str, val: &str) {
storage_set(key, val);
}
pub fn download(name: &str, data: &[u8]) { pub fn download(name: &str, data: &[u8]) {
let _ = std::fs::write(name, data); let path = saves_dir().join(key_file(name));
if let Err(e) = write_file(&path, data) {
eprintln!("DungeonCraft: cannot write {}: {}", path.display(), e);
}
} }
pub fn open_file() {}
// where download() put a file, for the status line
pub fn saved_as(name: &str) -> String {
saves_dir().join(key_file(name)).display().to_string()
}
fn pick() {
let dir = saves_dir();
let _ = std::fs::create_dir_all(&dir);
let file = rfd::FileDialog::new().set_directory(&dir).pick_file();
if let Some(bytes) = file.and_then(|f| std::fs::read(f).ok()) {
*PICKED.lock().unwrap_or_else(|e| e.into_inner()) = Some(bytes);
}
PICKING.store(false, std::sync::atomic::Ordering::SeqCst);
}
// opens the system file dialog (one at a time); poll with take_file()
pub fn open_file() {
if PICKING.swap(true, std::sync::atomic::Ordering::SeqCst) {
return;
}
// macOS dialogs must run on the main thread; elsewhere the game keeps running
if cfg!(target_os = "macos") {
pick();
} else if std::thread::Builder::new().name("file dialog".into()).spawn(pick).is_err() {
PICKING.store(false, std::sync::atomic::Ordering::SeqCst);
}
}
pub fn take_file() -> Option<Vec<u8>> { pub fn take_file() -> Option<Vec<u8>> {
None PICKED.lock().unwrap_or_else(|e| e.into_inner()).take()
} }
// no savegame service in native builds: every request fails like an unreachable server // no savegame service in native builds: every request fails like an unreachable server
pub fn http(_method: &str, _url: &str, _body: &[u8]) -> u32 { pub fn http(_method: &str, _url: &str, _body: &[u8]) -> u32 {
0 0
@@ -270,3 +373,14 @@ pub use imp::*;
pub extern "C" fn dungeoncraft_crate_version() -> u32 { pub extern "C" fn dungeoncraft_crate_version() -> u32 {
1 1
} }
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
#[test]
fn storage_keys_become_harmless_file_names() {
assert_eq!(super::imp::key_file("dc.settings"), "dc.settings");
assert_eq!(super::imp::key_file("../etc/passwd"), "_.._etc_passwd");
assert_eq!(super::imp::key_file("a b/c"), "a_b_c");
assert_eq!(super::imp::key_file(""), "_");
}
}
+117 -8
View File
@@ -85,12 +85,18 @@ impl Cam {
vec3(self.rx, 0.0, self.rz) vec3(self.rx, 0.0, self.rz)
} }
// visible world height in tiles: the zoom counts on the shorter side (upright screens: across)
pub fn view_h(&self) -> f32 {
let (w, h) = (screen_width().max(1.0), screen_height().max(1.0));
if h > w { self.zoom * h / w } else { self.zoom }
}
pub fn camera(&self) -> Camera3D { pub fn camera(&self) -> Camera3D {
Camera3D { Camera3D {
position: self.target() + self.eye_dir() * 60.0, position: self.target() + self.eye_dir() * 60.0,
target: self.target(), target: self.target(),
up: Vec3::Y, up: Vec3::Y,
fovy: self.zoom, fovy: self.view_h(),
projection: Projection::Orthographics, projection: Projection::Orthographics,
z_near: 1.0, z_near: 1.0,
z_far: 200.0, z_far: 200.0,
@@ -114,7 +120,7 @@ impl Cam {
pub fn ray(&self, sx: f32, sy: f32) -> (Vec3, Vec3) { pub fn ray(&self, sx: f32, sy: f32) -> (Vec3, Vec3) {
let (f, r, u) = self.basis(); let (f, r, u) = self.basis();
let half_h = self.zoom * 0.5; let half_h = self.view_h() * 0.5;
let half_w = half_h * screen_width() / screen_height(); let half_w = half_h * screen_width() / screen_height();
let nx = sx / screen_width() * 2.0 - 1.0; let nx = sx / screen_width() * 2.0 - 1.0;
let ny = 1.0 - sy / screen_height() * 2.0; let ny = 1.0 - sy / screen_height() * 2.0;
@@ -506,6 +512,11 @@ fn room_level_at(map: &Map, t: &Tile) -> u8 {
} }
// Ambient light field: own territory is lit, foreign areas are dim // Ambient light field: own territory is lit, foreign areas are dim
thread_local! {
// daylight above ground (bad weather dims it), in tenths
static SKY_LIGHT: std::cell::Cell<u32> = const { std::cell::Cell::new(16) };
}
fn ambient_field(map: &Map) -> Vec<f32> { fn ambient_field(map: &Map) -> Vec<f32> {
let mut f = vec![0.0f32; n()]; let mut f = vec![0.0f32; n()];
for (i, t) in map.t.iter().enumerate() { for (i, t) in map.t.iter().enumerate() {
@@ -516,7 +527,7 @@ fn ambient_field(map: &Map) -> Vec<f32> {
}; };
// daylight above ground (cliffs and trees too) // daylight above ground (cliffs and trees too)
if map.sky(i) { if map.sky(i) {
f[i] = 1.6; f[i] = SKY_LIGHT.with(|s| s.get()) as f32 / 10.0;
} }
// rooms of a higher level shine brighter // rooms of a higher level shine brighter
let lv = room_level_at(map, t); let lv = room_level_at(map, t);
@@ -1273,6 +1284,14 @@ impl R3d {
fn update_chunks(&mut self, g: &Game, gfx: &Gfx, m: &Mat4) { fn update_chunks(&mut self, g: &Game, gfx: &Gfx, m: &Mat4) {
self.ensure_chunks(); self.ensure_chunks();
let side = g.me.side().min(7); let side = g.me.side().min(7);
// the weather dims the daylight: the chunks above ground are lit again in steps
let sky = ((1.6 * (1.0 - g.weather.gloom())) * 10.0).round() as u32;
if SKY_LIGHT.with(|s| s.get()) != sky {
SKY_LIGHT.with(|s| s.set(sky));
for ch in self.chunks.iter_mut() {
ch.sig = 0;
}
}
if view_side() != side { if view_side() != side {
// another master looks: the marks change colour // another master looks: the marks change colour
VIEW_SIDE.with(|v| v.set(side)); VIEW_SIDE.with(|v| v.set(side));
@@ -1468,7 +1487,7 @@ impl R3d {
// Entity under the mouse (screen-space test against the projected sprite) // Entity under the mouse (screen-space test against the projected sprite)
pub fn pick_ent(&self, g: &Game, cam: &Cam, sx: f32, sy: f32, player_only: bool) -> Option<usize> { pub fn pick_ent(&self, g: &Game, cam: &Cam, sx: f32, sy: f32, player_only: bool) -> Option<usize> {
let m = cam.matrix(); let m = cam.matrix();
let px_per_tile = screen_height() / cam.zoom; let px_per_tile = screen_height() / cam.view_h();
let mut best = None; let mut best = None;
let mut bd = (px_per_tile * 0.4).max(10.0); let mut bd = (px_per_tile * 0.4).max(10.0);
for (j, e) in g.ents.iter().enumerate() { for (j, e) in g.ents.iter().enumerate() {
@@ -1494,7 +1513,9 @@ impl R3d {
// on the virtual screen (ui.rs) the world takes exactly k x k interface pixels per pixel // on the virtual screen (ui.rs) the world takes exactly k x k interface pixels per pixel
let vrows = crate::ui::virtual_rows(); let vrows = crate::ui::virtual_rows();
let pix = if vrows > 0 && self.pixel_rows > 0 { let pix = if vrows > 0 && self.pixel_rows > 0 {
let k = (vrows / self.pixel_rows).max(1); // the pixel rows count on the shorter side (upright screens: across)
let short = vrows.min(crate::ui::sw().round() as u32);
let k = (short / self.pixel_rows).max(1);
let th = vrows / k; let th = vrows / k;
Some(((crate::ui::sw() / k as f32).ceil() as u32, th, k as f32)) Some(((crate::ui::sw() / k as f32).ceil() as u32, th, k as f32))
} else { } else {
@@ -1601,6 +1622,7 @@ impl R3d {
} }
draw_traps_glow(g, t); draw_traps_glow(g, t);
draw_parts_glow(g); draw_parts_glow(g);
draw_weather(g, cam, t);
gl_use_default_material(); gl_use_default_material();
crate::ui::begin_ui(); crate::ui::begin_ui();
if let (Some((_, _, k)), Some((rt, tw, th))) = (pix, self.target.as_ref()) { if let (Some((_, _, k)), Some((rt, tw, th))) = (pix, self.target.as_ref()) {
@@ -1621,7 +1643,7 @@ impl R3d {
let (ix, iz) = (x as i32, z as i32); let (ix, iz) = (x as i32, z as i32);
let own = g.map.get(ix, iz).map_or(false, |t| matches!(t.owner, Owner::Player | Owner::Rival1 | Owner::Rival2 | Owner::Rival3)); let own = g.map.get(ix, iz).map_or(false, |t| matches!(t.owner, Owner::Player | Owner::Rival1 | Owner::Rival2 | Owner::Rival3));
if inb(ix, iz) && g.map.sky(idx(ix, iz)) { if inb(ix, iz) && g.map.sky(idx(ix, iz)) {
0.95 0.95 * (1.0 - 0.6 * g.weather.gloom())
} else if own { } else if own {
0.75 0.75
} else { } else {
@@ -2372,6 +2394,21 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
// soul core: an obsidian throne whose four curved spikes hold the crystal heart in chains // soul core: an obsidian throne whose four curved spikes hold the crystal heart in chains
let (cx, cy) = g.map.core; let (cx, cy) = g.map.core;
draw_throne(vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5), t); draw_throne(vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5), t);
// memorial stones of fallen creatures: a headstone on a little plinth, a rune that glows
for m in &g.memorials {
if !g.map.shown(m.tile) {
continue;
}
let (x, y) = xy(m.tile);
let h = (m.name % 7) as f32 * 0.02;
let c = vec3(x as f32 + 0.5 + ((m.name >> 3) % 5) as f32 * 0.04 - 0.08, 0.0, y as f32 + 0.5);
let stone = Color::from_rgba(118, 114, 124, 255);
draw_cube(c + vec3(0.0, 0.04, 0.0), vec3(0.42, 0.08, 0.26), None, Color::from_rgba(70, 66, 74, 255));
draw_cube(c + vec3(0.0, 0.3 + h * 0.5, 0.0), vec3(0.3, 0.46 + h, 0.1), None, stone);
draw_cube(c + vec3(0.0, 0.56 + h, 0.0), vec3(0.2, 0.06, 0.1), None, stone);
let glow = if m.level >= 8 { Color::from_rgba(255, 210, 90, 255) } else { Color::from_rgba(150, 120, 255, 255) };
draw_cube(c + vec3(0.0, 0.34 + h * 0.5, 0.052), vec3(0.06, 0.14, 0.01), None, glow);
}
// a ranger's snares: iron jaws on the floor, open and waiting // a ranger's snares: iron jaws on the floor, open and waiting
for &i in &g.snares { for &i in &g.snares {
if !g.map.shown(i) { if !g.map.shown(i) {
@@ -2481,6 +2518,69 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
let _ = t; let _ = t;
} }
// rain streaks and fog veils over the land above ground near the camera; a storm's lightning
// lights the sky up for a moment
fn draw_weather(g: &Game, cam: &Cam, t: f32) {
let w = &g.weather;
if w.level <= 0.01 || g.map.sky.is_empty() {
return;
}
let (cx, cz) = (cam.rx as i32, cam.rz as i32);
let reach = (cam.view_h() * 1.2) as i32 + 4;
let sky = |x: i32, z: i32| inb(x, z) && g.map.sky(idx(x, z));
// fog: veils at two heights, one plane per run of open sky tiles in a row
let fog = w.fog();
if fog > 0.01 {
for (h, a) in [(0.7f32, 0.22f32), (1.6, 0.16)] {
let col = Color::new(0.75, 0.78, 0.8, a * fog);
for z in cz - reach..=cz + reach {
let mut x = cx - reach;
while x <= cx + reach {
if !sky(x, z) {
x += 1;
continue;
}
let x0 = x;
while x <= cx + reach && sky(x, z) {
x += 1;
}
let len = (x - x0) as f32;
let drift = (t * 0.2 + z as f32 * 0.7).sin() * 0.15;
draw_plane(vec3(x0 as f32 + len * 0.5 + drift, h, z as f32 + 0.5), vec2(len * 0.5, 0.5), None, col);
}
}
}
}
// rain: slanted streaks falling over the open land
let rain = w.rain();
if rain > 0.01 {
let n = (1100.0 * rain) as u32;
let col = Color::new(0.75, 0.82, 0.95, 0.8);
let span = (2 * reach + 1) as u32;
for k in 0..n {
let h = k.wrapping_mul(2654435761) ^ 0x9e37_79b9;
let x = cx - reach + (h % span) as i32;
let z = cz - reach + ((h >> 11) % span) as i32;
if !sky(x, z) {
continue;
}
let fx = x as f32 + ((h >> 3) % 100) as f32 / 100.0;
let fz = z as f32 + ((h >> 17) % 100) as f32 / 100.0;
let fall = (t * 1.6 + (h % 1000) as f32 / 1000.0).fract();
let y = 3.0 * (1.0 - fall);
line3(vec3(fx, y + 0.5, fz - 0.08), vec3(fx + 0.05, y, fz + 0.08), 0.035, col);
}
}
// the flash of a lightning bolt
let since = g.time - w.flash;
if w.flash > 0.0 && (0.0..0.25).contains(&since) {
let a = 0.35 * (1.0 - since / 0.25);
for z in (cz - reach..=cz + reach).step_by(4) {
draw_plane(vec3(cx as f32, 0.05, z as f32 + 2.0), vec2(reach as f32, 2.0), None, Color::new(0.9, 0.92, 1.0, a));
}
}
}
// growth stage of a giant mushroom: 0 a sprout .. 4 fully grown (felling only then) // growth stage of a giant mushroom: 0 a sprout .. 4 fully grown (felling only then)
fn mushroom_stage(t: &Tile) -> u32 { fn mushroom_stage(t: &Tile) -> u32 {
if t.grown() { if t.grown() {
@@ -2808,7 +2908,7 @@ fn draw_marks(g: &Game, t: f32) {
return; return;
} }
for m in &g.marks { for m in &g.marks {
if !m.fixed && !g.debug_markers { if !g.debug_markers {
continue; continue;
} }
let left = m.until.saturating_sub(g.sim_steps) as f32 * STEP; let left = m.until.saturating_sub(g.sim_steps) as f32 * STEP;
@@ -3277,12 +3377,21 @@ fn draw_entry_warnings(g: &Game, t: f32, n: usize) {
let line = [255, 90, 60, (120.0 + 120.0 * p) as u8]; let line = [255, 90, 60, (120.0 + 120.0 * p) as u8];
let mut mb = MB::new(); let mut mb = MB::new();
for &(ex, ey) in &g.planned_entries { for &(ex, ey) in &g.planned_entries {
let realm = g.map.get(ex, ey).map_or(Owner::Player, |t| t.owner);
// the entry tile itself: a pulsing red plate (also where an open corridor leads in)
let c = vec3(ex as f32 + 0.5, 0.03, ey as f32 + 0.5);
mb.quad(
[c + vec3(-0.5, 0.0, -0.5), c + vec3(0.5, 0.0, -0.5), c + vec3(0.5, 0.0, 0.5), c + vec3(-0.5, 0.0, 0.5)],
[Vec2::ZERO; 4],
[col(vec3(1.0, 0.16, 0.12) * (0.6 + 0.6 * p), 0.45 + 0.35 * p); 4],
[Vec3::Y; 4],
);
for dy in -2..=2 { for dy in -2..=2 {
for dx in -2..=2 { for dx in -2..=2 {
let (x, y) = (ex + dx, ey + dy); let (x, y) = (ex + dx, ey + dy);
let Some(tile) = g.map.get(x, y) else { continue }; let Some(tile) = g.map.get(x, y) else { continue };
// walls of the realm's border near the entry point (not rock) // walls of the realm's border near the entry point (not rock)
let border = DIR4.iter().any(|&(ax, ay)| g.map.get(x + ax, y + ay).map_or(false, |o| o.owner == Owner::Player && !o.solid())); let border = DIR4.iter().any(|&(ax, ay)| g.map.get(x + ax, y + ay).map_or(false, |o| o.owner == realm && !o.solid()));
if tile.diggable() && border && dx.abs() + dy.abs() <= 3 && mb.v.len() < 30000 { if tile.diggable() && border && dx.abs() + dy.abs() <= 3 && mb.v.len() < 30000 {
block_shell(&mut mb, &g.map, x, y, n, fill, line, 1.0, 0.04); block_shell(&mut mb, &g.map, x, y, n, fill, line, 1.0, 0.04);
} }
+5 -1
View File
@@ -33,6 +33,10 @@ pub struct Rival {
pub attacking: bool, // the attack flag is set on an enemy heart pub attacking: bool, // the attack flag is set on an enemy heart
#[serde(default)] #[serde(default)]
pub army0: f32, // army strength when the attack began pub army0: f32, // army strength when the attack began
#[serde(default)]
pub focus: Option<u8>, // balance runs: this master learns only one talent branch
#[serde(default)]
pub defending: bool, // the rally flag is on intruders in the dungeon (ai.rs)
} }
impl Rival { impl Rival {
@@ -98,7 +102,7 @@ impl Game {
let core = (cx - 1, cy - 1); let core = (cx - 1, cy - 1);
self.carve_master_area(side, core); self.carve_master_area(side, core);
self.add_master_portal(side, core); self.add_master_portal(side, core);
self.rivals.push(Rival { side, core, hp: RIVAL_CORE_HP, alive: true, radius: 5, grow_t: 20.0, spawn_t: 5.0, attack_t: 0.0, difficulty, power: 0.0, gift_t: PARTNER_GIFT_EVERY, think_t: 2.0, attacking: false, army0: 0.0 }); self.rivals.push(Rival { side, core, hp: RIVAL_CORE_HP, alive: true, radius: 5, grow_t: 20.0, spawn_t: 5.0, attack_t: 0.0, difficulty, power: 0.0, gift_t: PARTNER_GIFT_EVERY, think_t: 2.0, attacking: false, army0: 0.0, focus: None, defending: false });
self.adopt_master(side, core, difficulty, true); self.adopt_master(side, core, difficulty, true);
} }
+4 -2
View File
@@ -94,6 +94,7 @@ impl Game {
} }
e.carry += got; e.carry += got;
self.fx(5, e.x, e.y, 0.0, 0.0, 0.5, 0xff30d0ff); self.fx(5, e.x, e.y, 0.0, 0.0, 0.5, 0xff30d0ff);
self.sfx(Sfx::Steal, e.x, e.y);
} }
return true; return true;
} }
@@ -192,10 +193,11 @@ impl Game {
let e = &mut self.ents[j]; let e = &mut self.ents[j];
e.stun = e.stun.max(SNARE_STUN); e.stun = e.stun.max(SNARE_STUN);
e.hp -= SNARE_DAMAGE; e.hp -= SNARE_DAMAGE;
e.bane = crate::names::BANE_SNARE;
e.flash = 0.2; e.flash = 0.2;
let (x, y, side, kind, dead) = (e.x, e.y, e.side, e.kind, e.hp <= 0.0); let (x, y, side, kind, dead) = (e.x, e.y, e.side, e.kind, e.hp <= 0.0);
self.fx(2, x, y, 0.0, 0.0, 0.4, 0xff4040ff); self.fx(2, x, y, 0.0, 0.0, 0.4, 0xff4040ff);
self.sfx(Sfx::MeleeHit, x, y); self.sfx(Sfx::Snare, x, y);
if side == self.local_side { if side == self.local_side {
self.tell_local("msg.snare_caught", vec![format!("@{}", st(kind).name)], None); self.tell_local("msg.snare_caught", vec![format!("@{}", st(kind).name)], None);
} }
@@ -244,7 +246,7 @@ impl Game {
self.shot(e.x, e.y, 0.6, x, y, 0.45, 0.35, 0xff40d0ff); self.shot(e.x, e.y, 0.6, x, y, 0.45, 0.35, 0xff40d0ff);
self.style_last_fx(crate::particles::ST_HOLY); self.style_last_fx(crate::particles::ST_HOLY);
self.fx(4, x, y, 0.0, 0.0, 0.9, 0xffffeecc); self.fx(4, x, y, 0.0, 0.0, 0.9, 0xffffeecc);
self.sfx(Sfx::Capture, x, y); self.sfx(Sfx::Sermon, x, y);
if side == self.local_side { if side == self.local_side {
self.tell_local("msg.converted", vec![format!("@{}", st(kind).name)], Some(Sfx::WaveAlarm)); self.tell_local("msg.converted", vec![format!("@{}", st(kind).name)], Some(Sfx::WaveAlarm));
} }
+37
View File
@@ -264,3 +264,40 @@ fn export_rogues_scene() {
g.snares.push(idx(cx + 7, cy + 3)); g.snares.push(idx(cx + 7, cy + 3));
save("rogues", &mut g, cx as f32 + 6.5, cy as f32 + 4.5, 8.0, 0.0, false); save("rogues", &mut g, cx as f32 + 6.5, cy as f32 + 4.5, 8.0, 0.0, false);
} }
// weather above ground: rain and fog over the land
#[test]
#[ignore]
fn export_weather_scenes() {
std::fs::create_dir_all("/tmp/scenes").unwrap();
for (name, kind) in [("weather-rain", crate::weather::RAIN), ("weather-fog", crate::weather::FOG), ("weather-storm", crate::weather::STORM)] {
let mut g = Game::new(7, 96, 96);
g.add_surface(7, 0.5, 3);
g.weather = crate::weather::Weather { kind, shown: kind, level: 1.0, left: 900.0, ..Default::default() };
// the camera on the land above ground, near the edge of the dungeon side
let i = (0..g.map.t.len()).filter(|&i| g.map.sky(i)).min_by_key(|&i| {
let (x, y) = xy(i);
(x - 52).abs() + (y - 48).abs()
}).unwrap();
let (x, y) = xy(i);
save(name, &mut g, x as f32 + 4.5, y as f32 + 0.5, 18.0, 0.0, false);
}
}
// named creatures and memorial stones of the fallen
#[test]
#[ignore]
fn export_names_scene() {
std::fs::create_dir_all("/tmp/scenes").unwrap();
let mut g = Game::new(11, 80, 64);
let (cx, cy) = g.map.core;
open(&mut g, cx - 9, cy - 8, cx + 11, cy + 9);
g.place_all_torches();
creatures(&mut g, &[(EK::Ogre, 9), (EK::Mage, 5), (EK::Kobold, 2)], cx as f32 + 6.5, cy as f32 + 5.0);
for (k, &(kind, lv)) in [(EK::Spider, 4), (EK::Ogre, 8), (EK::Mage, 6)].iter().enumerate() {
let id = g.spawn(kind, false, cx as f32 + 2.5 + k as f32 * 2.0, cy as f32 + 6.5, lv);
let j = g.ents.iter().position(|e| e.id == id).unwrap();
g.kill(j);
}
save("names", &mut g, cx as f32 + 5.0, cy as f32 + 5.5, 9.0, 0.0, false);
}
+1 -1
View File
@@ -148,7 +148,7 @@ pub struct Settings {
pub outlines: bool, pub outlines: bool,
pub wall_detail: u8, // 1 low, 2 medium, 3 high pub wall_detail: u8, // 1 low, 2 medium, 3 high
pub max_lights: u8, // 8, 16 or 24 pub max_lights: u8, // 8, 16 or 24
pub ui_factor: u8, // interface size: 0 = automatic, 1..4 = fixed integer factor pub ui_factor: u8, // interface size: 0 = automatic, 2 large, 3 medium, 4 small (interface pixels per world pixel)
pub render_scale: u8, // old pixel-look divisor (no longer used, kept for old settings files) pub render_scale: u8, // old pixel-look divisor (no longer used, kept for old settings files)
pub pixel_rows: u16, // pixel look: rows of the world on every screen (270, 360, 480) pub pixel_rows: u16, // pixel look: rows of the world on every screen (270, 360, 480)
pub touch_ui: u8, // touch interface: 0 = automatic (after the first touch), 1 = on, 2 = off pub touch_ui: u8, // touch interface: 0 = automatic (after the first touch), 1 = on, 2 = off
+43
View File
@@ -164,6 +164,31 @@ fn def(s: Sfx, r: f32) -> Vec<f32> {
Sfx::Voice4 => l(&[seg(Square(0.15), 900.0, 980.0, 0.05, 0.13, 10.0), vib(seg(Square(0.2), 700.0, 560.0, 0.06, 0.14, 8.0), 0.04)], 65), Sfx::Voice4 => l(&[seg(Square(0.15), 900.0, 980.0, 0.05, 0.13, 10.0), vib(seg(Square(0.2), 700.0, 560.0, 0.06, 0.14, 8.0), 0.04)], 65),
Sfx::Voice5 => mix(vec![l(&[vib(seg(Square(0.25), 470.0, 560.0, 0.1, 0.15, 5.0), 0.06)], 66), l(&[seg(Noise, 3000.0, 2000.0, 0.025, 0.06, 30.0)], 67)]), Sfx::Voice5 => mix(vec![l(&[vib(seg(Square(0.25), 470.0, 560.0, 0.1, 0.15, 5.0), 0.06)], 66), l(&[seg(Noise, 3000.0, 2000.0, 0.025, 0.06, 30.0)], 67)]),
Sfx::Voice6 => l(&[vib(seg(Square(0.2), 680.0, 820.0, 0.06, 0.15, 9.0), 0.05), seg(Square(0.2), 820.0, 700.0, 0.04, 0.12, 12.0)], 68), Sfx::Voice6 => l(&[vib(seg(Square(0.2), 680.0, 820.0, 0.06, 0.15, 9.0), 0.05), seg(Square(0.2), 820.0, 700.0, 0.04, 0.12, 12.0)], 68),
// a long rumble rolling away after the crack
Sfx::Thunder => mix(vec![
l(&[seg(Noise, 420.0, 70.0, 2.4, 0.55, 1.1)], 70),
l(&[vib(seg(Tri, 55.0, 32.0, 2.2, 0.5, 1.2), 0.08)], 71),
l(&[seg(Noise, 160.0, 160.0, 0.35, 0.0, 0.0), seg(Noise, 160.0, 90.0, 1.4, 0.3, 1.6)], 72),
]),
// the thief's fingers in the coins: a quick, soft jingle
Sfx::Steal => mix(vec![l(&arp(Square(0.25), &[2093.0, 1760.0, 2349.0], 0.04, 0.1), 73), l(&[seg(Noise, 7000.0, 7000.0, 0.03, 0.08, 60.0)], 74)]),
// iron jaws snap shut
Sfx::Snare => mix(vec![l(&[seg(Noise, 8000.0, 3000.0, 0.04, 0.5, 50.0)], 75), l(&[seg(Square(0.5), 1400.0, 300.0, 0.09, 0.22, 25.0)], 76)]),
// the inquisitor's sermon: a short, hollow chant
Sfx::Sermon => mix(vec![
l(&[vib(seg(Sine, 330.0, 330.0, 0.7, 0.28, 2.0), 0.02)], 77),
l(&[vib(seg(Sine, 392.0, 415.0, 0.7, 0.2, 2.0), 0.02)], 78),
l(&[vib(seg(Sine, 494.0, 494.0, 0.7, 0.16, 2.0), 0.03)], 79),
]),
// rain on the land: fades in and out, played over and over while it rains
Sfx::Rain => mix(vec![
l(&[seg(Noise, 7000.0, 7000.0, 0.3, 0.04, -6.0), seg(Noise, 7000.0, 7000.0, 0.9, 0.24, 0.0), seg(Noise, 7000.0, 7000.0, 0.3, 0.24, 6.0)], 80),
l(&[seg(Noise, 2500.0, 2500.0, 0.3, 0.02, -6.0), seg(Noise, 2500.0, 2500.0, 0.9, 0.12, 0.0), seg(Noise, 2500.0, 2500.0, 0.3, 0.12, 6.0)], 81),
]),
// wind over the land: a slow, howling sweep
Sfx::Wind => l(&[seg(Noise, 300.0, 700.0, 0.6, 0.03, -3.0), seg(Noise, 700.0, 500.0, 1.4, 0.18, 0.2), seg(Noise, 500.0, 250.0, 0.6, 0.15, 3.0)], 82),
// the spores of a grove: a faint chime
Sfx::Spores => l(&arp(Sine, &[2093.0, 2637.0, 3136.0, 2637.0], 0.09, 0.07), 83),
} }
} }
@@ -171,3 +196,21 @@ fn def(s: Sfx, r: f32) -> Vec<f32> {
pub fn generate_all(rate: f32) -> Vec<Vec<f32>> { pub fn generate_all(rate: f32) -> Vec<Vec<f32>> {
SFX_ALL.iter().map(|&s| def(s, rate)).collect() SFX_ALL.iter().map(|&s| def(s, rate)).collect()
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_effect_is_audible_and_does_not_clip() {
let all = generate_all(22050.0);
for (s, buf) in SFX_ALL.iter().zip(all.iter()) {
let peak = buf.iter().fold(0.0f32, |m, v| m.max(v.abs()));
assert!(!buf.is_empty() && peak > 0.02, "{:?} is silent", s);
assert!(peak < 1.5, "{:?} is too loud: {}", s, peak);
}
// the rain loops: it is long enough to overlap when played every 1.25 s
let rain = &all[Sfx::Rain.index()];
assert!(rain.len() as f32 / 22050.0 > 1.3);
}
}
+42 -1
View File
@@ -8,11 +8,52 @@ use macroquad::prelude::*;
pub struct SoundBank { pub struct SoundBank {
last: Vec<f64>, last: Vec<f64>,
next_rain: f64,
next_wind: f64,
next_spores: f64,
} }
impl SoundBank { impl SoundBank {
pub fn new() -> SoundBank { pub fn new() -> SoundBank {
SoundBank { last: vec![-1.0; SFX_ALL.len()] } SoundBank { last: vec![-1.0; SFX_ALL.len()], next_rain: 0.0, next_wind: 0.0, next_spores: 0.0 }
}
// background sounds where the camera looks: rain and wind over the land above ground (the
// samples fade in and out and overlap a little), the chime of a mushroom grove now and then
pub fn ambience(&mut self, g: &crate::game::Game, (cx, cz): (f32, f32), now: f64) {
use crate::map::{idx, inb, Kind};
let near = |r: i32, f: &dyn Fn(usize) -> bool| {
let (x, z) = (cx as i32, cz as i32);
let mut n = 0;
for dz in (-r..=r).step_by(2) {
for dx in (-r..=r).step_by(2) {
if inb(x + dx, z + dz) && f(idx(x + dx, z + dz)) {
n += 1;
}
}
}
n
};
if g.state != 0 {
return;
}
let outside = !g.map.sky.is_empty() && near(6, &|i| g.map.sky(i)) >= 6;
let w = &g.weather;
if outside && w.rain() > 0.05 && now >= self.next_rain {
self.next_rain = now + 1.25;
audio::play_sfx(Sfx::Rain.index(), 0.55 * w.rain(), 0.0);
}
let windy = if w.shown == crate::weather::STORM { w.level } else { w.fog() * 0.5 };
if outside && windy > 0.05 && now >= self.next_wind {
self.next_wind = now + 2.2;
audio::play_sfx(Sfx::Wind.index(), 0.45 * windy, (now.sin() * 0.4) as f32);
}
if now >= self.next_spores {
self.next_spores = now + 3.0 + (now * 7.3).sin().abs() * 3.0;
if near(5, &|i| matches!(g.map.t[i].kind, Kind::Grove)) >= 3 {
audio::play_sfx(Sfx::Spores.index(), 0.35, ((now * 1.7).sin() * 0.5) as f32);
}
}
} }
// minimum gap between two identical sounds (avoids a wall of noise) // minimum gap between two identical sounds (avoids a wall of noise)
+1 -1
View File
@@ -92,7 +92,7 @@ impl Game {
} }
pub fn cast(&mut self, spell: Spell, wx: f32, wy: f32) { pub fn cast(&mut self, spell: Spell, wx: f32, wy: f32) {
let cost = spell.cost(); let cost = spell.cost() * self.talents.spell_cost();
if self.mana < cost { if self.mana < cost {
self.msg("msg.no_mana_cost", vec![(cost as i32).to_string()]); self.msg("msg.no_mana_cost", vec![(cost as i32).to_string()]);
self.sfx_ui(Sfx::Error); self.sfx_ui(Sfx::Error);
+2
View File
@@ -48,6 +48,8 @@ impl GameStats {
impl Game { impl Game {
// counts for the master at this computer (another master's deeds count on his computer) // counts for the master at this computer (another master's deeds count on his computer)
pub(crate) fn count(&mut self, k: usize, n: f32) { pub(crate) fn count(&mut self, k: usize, n: f32) {
// every master's deeds bring him experience for his talents
self.add_master_xp(crate::talents::xp_for(k) * n);
if self.me_side != self.local_side || self.demo.is_some() { if self.me_side != self.local_side || self.demo.is_some() {
return; return;
} }
+228
View File
@@ -0,0 +1,228 @@
// Master talents: a master earns experience (slain heroes, dug tiles, rooms, walls, traps,
// towers, spells, gold) and with it talent points, spent on four branches of five levels each:
// Builder (imps dig faster, rooms cost less), Warlord (creatures hit harder and take less
// damage), Warlock (more mana, cheaper spells), Merchant (lower wages, more gold from treasure).
// Per game in free play and matches; in the campaign they go along from mission to mission.
use crate::game::*;
use serde::{Deserialize, Serialize};
pub const BRANCHES: usize = 4;
pub const BUILDER: usize = 0;
pub const WARLORD: usize = 1;
pub const WARLOCK: usize = 2;
pub const MERCHANT: usize = 3;
pub const MAX_LEVEL: u8 = 5;
pub const NAMES: [&str; BRANCHES] = ["talent.builder", "talent.warlord", "talent.warlock", "talent.merchant"];
// experience for the deeds of a master (stats counter -> experience per unit)
pub fn xp_for(counter: usize) -> f32 {
use crate::stats as k;
match counter {
k::DUG => 2.0,
k::ROOMS => 3.0,
k::WALLS => 2.0,
k::TRAPS => 8.0,
k::TOWERS => 25.0,
k::SPELLS => 5.0,
k::SIEGE => 20.0,
k::GOLD => 0.02,
_ => 0.0,
}
}
// experience for a slain hero: a share of his bounty
pub const HERO_XP: f32 = 0.5;
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Talents {
pub xp: f32,
pub lv: [u8; BRANCHES],
}
// experience the k-th talent point needs in all (the points get dearer)
pub fn point_xp(k: u32) -> f32 {
(0..k).map(|i| 300.0 + 150.0 * i as f32).sum()
}
impl Talents {
pub fn spent(&self) -> u32 {
self.lv.iter().map(|&l| l as u32).sum()
}
// points earned so far (at most enough for every level)
pub fn earned(&self) -> u32 {
let max = BRANCHES as u32 * MAX_LEVEL as u32;
(1..=max).take_while(|&k| self.xp >= point_xp(k)).count() as u32
}
pub fn free(&self) -> u32 {
self.earned().saturating_sub(self.spent())
}
// progress towards the next point: (experience into it, experience it needs)
pub fn progress(&self) -> Option<(f32, f32)> {
let k = self.earned();
if k >= BRANCHES as u32 * MAX_LEVEL as u32 {
return None;
}
let (a, b) = (point_xp(k), point_xp(k + 1));
Some((self.xp - a, b - a))
}
pub fn can_learn(&self, branch: usize) -> bool {
branch < BRANCHES && self.lv[branch] < MAX_LEVEL && self.free() > 0
}
fn level(&self, branch: usize) -> f32 {
self.lv.get(branch).copied().unwrap_or(0) as f32
}
// Builder: imps dig and claim faster, rooms cost less
pub fn dig_speed(&self) -> f32 {
1.0 + 0.15 * self.level(BUILDER)
}
pub fn room_cost(&self) -> f32 {
1.0 - 0.1 * self.level(BUILDER)
}
// Warlord: creatures hit harder and take less damage
pub fn damage(&self) -> f32 {
1.0 + 0.05 * self.level(WARLORD)
}
pub fn armour(&self) -> f32 {
1.0 / (1.0 + 0.05 * self.level(WARLORD))
}
// Warlock: mana flows faster, spells cost less
pub fn mana_rate(&self) -> f32 {
1.0 + 0.15 * self.level(WARLOCK)
}
pub fn spell_cost(&self) -> f32 {
1.0 - 0.06 * self.level(WARLOCK)
}
// Merchant: lower wages, more gold from delivered treasure
pub fn wages(&self) -> f32 {
1.0 - 0.06 * self.level(MERCHANT)
}
pub fn treasure(&self) -> f32 {
1.0 + 0.06 * self.level(MERCHANT)
}
}
impl Game {
// the talents of the master on `side`
pub fn talents_of(&self, side: u8) -> Talents {
if side == self.me_side {
self.talents.clone()
} else {
self.masters.iter().find(|m| m.side == side).map(|m| m.talents.clone()).unwrap_or_default()
}
}
// experience for the master being processed; a new talent point is announced
pub(crate) fn add_master_xp(&mut self, xp: f32) {
if xp <= 0.0 || self.demo.is_some() {
return;
}
let before = self.talents.earned();
self.talents.xp += xp;
if self.talents.earned() > before && self.me_side == self.local_side {
self.msg("msg.talent_point", vec![]);
self.sfx_ui(Sfx::LevelUp);
}
}
// the master spends a point on a branch (an order: the same on every computer of a match)
pub fn learn_talent(&mut self, branch: usize) {
if !self.talents.can_learn(branch) {
self.sfx_ui(Sfx::Error);
return;
}
self.talents.lv[branch] += 1;
let lv = self.talents.lv[branch];
self.msg("msg.talent_learned", vec![format!("@{}", NAMES[branch]), lv.to_string()]);
self.sfx_ui(Sfx::LevelUp);
}
// an AI master spends his points by himself: the branches in turn, the lowest first (a balance
// run may hold him to one branch)
pub(crate) fn ai_talents(&mut self, focus: Option<u8>) {
if let Some(b) = focus {
let b = b as usize;
while self.talents.free() > 0 && self.talents.lv[b] < MAX_LEVEL {
self.talents.lv[b] += 1;
}
return;
}
while self.talents.free() > 0 {
let order = [WARLORD, BUILDER, MERCHANT, WARLOCK];
let Some(&b) = order.iter().filter(|&&b| self.talents.lv[b] < MAX_LEVEL).min_by_key(|&&b| self.talents.lv[b]) else { break };
self.talents.lv[b] += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn points_get_dearer_and_levels_are_capped() {
let mut t = Talents::default();
assert_eq!(t.earned(), 0);
t.xp = point_xp(1);
assert_eq!((t.earned(), t.free()), (1, 1));
assert!(point_xp(3) - point_xp(2) > point_xp(2) - point_xp(1));
t.xp = 1e9;
assert_eq!(t.earned(), 20);
t.lv = [5, 5, 5, 5];
assert_eq!(t.free(), 0);
assert!(!t.can_learn(0) && t.progress().is_none());
}
#[test]
fn deeds_bring_points_and_points_bring_talents() {
let mut g = Game::new(9, 64, 64);
let (cx, cy) = g.map.core;
g.mark_rect(cx - 6, cy - 6, cx + 8, cy + 8, true);
for _ in 0..(20 * 60 * 6) {
g.update(0.05);
}
assert!(g.talents.xp > 100.0, "digging gives experience: {}", g.talents.xp);
g.talents.xp = point_xp(2);
let room0 = g.talents.room_cost();
g.learn_talent(BUILDER);
g.learn_talent(WARLORD);
g.learn_talent(WARLORD); // no third point
assert_eq!(g.talents.lv, [1, 1, 0, 0]);
assert!(g.talents.room_cost() < room0 && g.talents.damage() > 1.0);
}
#[test]
fn talents_make_rooms_cheaper_and_wages_lower() {
let mut g = Game::new(9, 64, 64);
let (cx, cy) = g.map.core;
for y in cy + 4..cy + 7 {
for x in cx..cx + 3 {
let t = &mut g.map.t[crate::map::idx(x, y)];
t.kind = crate::map::Kind::Floor;
t.owner = crate::map::Owner::Player;
}
}
g.gold = 10000.0;
let gold0 = g.gold;
g.build_rect(1, cx, cy + 4, cx + 2, cy + 6);
let plain = gold0 - g.gold;
g.sell_rect(cx, cy + 4, cx + 2, cy + 6);
g.talents.lv[BUILDER] = 5;
let gold1 = g.gold;
g.build_rect(1, cx, cy + 4, cx + 2, cy + 6);
let cheap = gold1 - g.gold;
assert!(cheap < plain * 0.8, "{} < {}", cheap, plain);
g.spawn(EK::Ogre, false, cx as f32 + 1.5, cy as f32 + 5.5, 3);
let wages = g.next_payday_cost();
g.talents.lv[MERCHANT] = 5;
assert!(g.next_payday_cost() < wages);
}
#[test]
fn ai_masters_spend_their_points() {
let mut g = Game::new(9, 64, 64);
g.talents.xp = point_xp(6);
g.ai_talents(None);
assert_eq!(g.talents.free(), 0);
assert!(g.talents.lv.iter().all(|&l| l >= 1), "{:?}", g.talents.lv);
}
}
+181 -5
View File
@@ -457,8 +457,11 @@ enum TBtn {
Tool(usize), Tool(usize),
Menu, Menu,
Overview, Overview,
Talents, // the master's talent window
Learn(u8), // spend a point on a branch
OvRow(u32), OvRow(u32),
OvScroll(i32), OvScroll(i32),
OvFallen, // switch the overview between the living and the fallen
GoldTo, // the next ally to receive gold GoldTo, // the next ally to receive gold
Gold(u32), // send that much gold to him Gold(u32), // send that much gold to him
} }
@@ -478,6 +481,7 @@ pub enum UiAction {
ChatSetTeam(bool), ChatSetTeam(bool),
ChatSend, ChatSend,
SendGold(u8, u32), // gold for an ally of a match (side, amount) SendGold(u8, u32), // gold for an ally of a match (side, amount)
LearnTalent(u8), // spend a talent point on a branch
Consumed, Consumed,
} }
@@ -485,9 +489,11 @@ pub struct Ui {
pub tool: Tool, pub tool: Tool,
pub show_help: bool, pub show_help: bool,
pub show_overview: bool, // list of all creatures pub show_overview: bool, // list of all creatures
pub show_talents: bool, // the master's talents
pub coop_end: Option<(bool, bool, bool)>, // end of an editor co-op mission: (host, a next mission, asked for it) pub coop_end: Option<(bool, bool, bool)>, // end of an editor co-op mission: (host, a next mission, asked for it)
gold_to: usize, // the ally chosen in the creature overview gold_to: usize, // the ally chosen in the creature overview
ov_scroll: usize, pub ov_scroll: usize,
pub ov_fallen: bool, // the overview shows the hall of the fallen
ov_rect: Option<Rect>, // where the creature overview is (mouse wheel, swipe) ov_rect: Option<Rect>, // where the creature overview is (mouse wheel, swipe)
pub show_langsel: bool, pub show_langsel: bool,
pub log: Vec<(Msg, f64)>, pub log: Vec<(Msg, f64)>,
@@ -547,6 +553,28 @@ pub fn wrap(s: &str, cols: usize) -> Vec<String> {
out out
} }
// what a talent branch gives at its level now, and at the next level
fn branch_text(t: &crate::talents::Talents, b: usize) -> String {
use crate::talents::*;
let desc = |lv: u8| {
let l = lv as i32;
let (a, c) = match b {
BUILDER => (15 * l, 10 * l),
WARLORD => (5 * l, 5 * l),
WARLOCK => (15 * l, 6 * l),
_ => (6 * l, 6 * l),
};
trf(&format!("{}.desc", NAMES[b]), &[a.to_string(), c.to_string()])
};
let lv = t.lv[b];
let now = if lv == 0 { tr("talent.none") } else { desc(lv) };
if lv < MAX_LEVEL {
format!("{} {}", now, trf("talent.then", &[desc(lv + 1)]))
} else {
now
}
}
#[cfg(test)] #[cfg(test)]
mod wrap_tests { mod wrap_tests {
#[test] #[test]
@@ -573,9 +601,11 @@ impl Ui {
tool: Tool::Dig, tool: Tool::Dig,
show_help: true, show_help: true,
show_overview: false, show_overview: false,
show_talents: false,
coop_end: None, coop_end: None,
gold_to: 0, gold_to: 0,
ov_scroll: 0, ov_scroll: 0,
ov_fallen: false,
ov_rect: None, ov_rect: None,
show_langsel: false, show_langsel: false,
log: Vec::new(), log: Vec::new(),
@@ -664,6 +694,19 @@ impl Ui {
TBtn::Overview => { TBtn::Overview => {
self.open_cat = None; self.open_cat = None;
self.show_overview = !self.show_overview; self.show_overview = !self.show_overview;
self.show_talents = false;
self.ov_scroll = 0;
UiAction::Consumed
}
TBtn::Talents => {
self.open_cat = None;
self.show_talents = !self.show_talents;
self.show_overview = false;
UiAction::Consumed
}
TBtn::Learn(b) => UiAction::LearnTalent(b),
TBtn::OvFallen => {
self.ov_fallen = !self.ov_fallen;
self.ov_scroll = 0; self.ov_scroll = 0;
UiAction::Consumed UiAction::Consumed
} }
@@ -774,6 +817,13 @@ impl Ui {
}; };
segs.insert(3, vec![txt(tr(key), col), Part::Gap, Part::Bar(rv.hp / crate::rival::RIVAL_CORE_HP, col)]); segs.insert(3, vec![txt(tr(key), col), Part::Gap, Part::Bar(rv.hp / crate::rival::RIVAL_CORE_HP, col)]);
} }
// the weather above ground (maps with land above ground only)
if g.has_sky() {
let w = &g.weather;
let k = if w.level > 0.05 { w.shown } else { crate::weather::FAIR };
let col = [GOLD, Color::new(0.55, 0.7, 1.0, 1.0), Color::new(0.75, 0.78, 0.8, 1.0), Color::new(0.75, 0.6, 1.0, 1.0)][k as usize];
segs.push(vec![txt(tr(crate::weather::NAMES[k as usize]), col)]);
}
if g.waves_on() { if g.waves_on() {
segs.push(vec![txt(trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col), Part::Gap, txt(mmss(g.wave_timer), TXT)]); segs.push(vec![txt(trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col), Part::Gap, txt(mmss(g.wave_timer), TXT)]);
} }
@@ -891,7 +941,7 @@ impl Ui {
let mut popup_top = tb_y; let mut popup_top = tb_y;
// categories + menu button; a category with several tools opens a row above // categories + menu button; a category with several tools opens a row above
// (the same layout for mouse and touch; on the desktop the keys still select tools directly) // (the same layout for mouse and touch; on the desktop the keys still select tools directly)
let n = CATS.len() + 2; let n = CATS.len() + 3;
let bcols = (((cols - 2) as usize / n) as f32).clamp(4.0, 8.0); let bcols = (((cols - 2) as usize / n) as f32).clamp(4.0, 8.0);
let bw = bcols * CW(); let bw = bcols * CW();
let x0 = grid_center(sw, bw * n as f32); let x0 = grid_center(sw, bw * n as f32);
@@ -932,10 +982,27 @@ impl Ui {
let ol: String = ol.chars().take(maxc).collect(); let ol: String = ol.chars().take(maxc).collect();
gfx.text(&ol, bx + (bw - gfx.measure(&ol, SMALL())) * 0.5, by + 2.0 * CH() + 4.0 * px, SMALL(), TXT); gfx.text(&ol, bx + (bw - gfx.measure(&ol, SMALL())) * 0.5, by + 2.0 * CH() + 4.0 * px, SMALL(), TXT);
self.touch_btns.push((r, TBtn::Overview)); self.touch_btns.push((r, TBtn::Overview));
// menu button // the master's talents: the frame pulses while points are free
let bx = x0 + (CATS.len() + 1) as f32 * bw; let bx = x0 + (CATS.len() + 1) as f32 * bw;
let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH()); let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH());
let hovered = !touch_ui() && r.contains(vec2(mx, my)); let hovered = !touch_ui() && r.contains(vec2(mx, my));
let free = g.talents.free();
let glow = free > 0 && ((now * 3.0) as i64) % 2 == 0;
draw_rectangle(r.x, r.y, r.w, r.h, if hovered || self.show_talents { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, if glow { GOLD } else if self.show_talents { FRAME_HI } else { Color::new(0.35, 0.28, 0.2, 1.0) });
let isz = 2.0 * CH() - 2.0 * px;
draw_texture_ex(&gfx.atlas, bx + (bw - isz) * 0.5, by + 2.0 * px, WHITE, DrawTextureParams { source: Some(Gfx::cell(I_RUNE)), dest_size: Some(vec2(isz, isz)), ..Default::default() });
if free > 0 {
gfx.text(&free.to_string(), r.x + 3.0 * px, r.y + 3.0 * px, SMALL(), if glow { WHITE } else { GOLD });
}
let tl = tr("cat.master");
let tl: String = tl.chars().take(maxc).collect();
gfx.text(&tl, bx + (bw - gfx.measure(&tl, SMALL())) * 0.5, by + 2.0 * CH() + 4.0 * px, SMALL(), TXT);
self.touch_btns.push((r, TBtn::Talents));
// menu button
let bx = x0 + (CATS.len() + 2) as f32 * bw;
let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH());
let hovered = !touch_ui() && r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) }); draw_rectangle(r.x, r.y, r.w, r.h, if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, Color::new(0.35, 0.28, 0.2, 1.0)); draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, Color::new(0.35, 0.28, 0.2, 1.0));
gfx.glyph('≡', bx + (bw - GW * TITLE()) * 0.5, by + 2.0 * px, TITLE(), FRAME_HI); gfx.glyph('≡', bx + (bw - GW * TITLE()) * 0.5, by + 2.0 * px, TITLE(), FRAME_HI);
@@ -1204,6 +1271,9 @@ impl Ui {
if self.show_overview { if self.show_overview {
self.draw_overview(g, gfx, tb_y); self.draw_overview(g, gfx, tb_y);
} }
if self.show_talents {
self.draw_talents(g, gfx, tb_y);
}
// ---------------- Tooltip // ---------------- Tooltip
if let Some(i) = hover_tool { if let Some(i) = hover_tool {
@@ -1232,6 +1302,85 @@ impl Ui {
} }
// all creatures, grouped by kind: level, mood, wage, job, health; a click jumps there // all creatures, grouped by kind: level, mood, wage, job, health; a click jumps there
// the master's talents: four branches of five levels, points from experience
fn draw_talents(&mut self, g: &Game, gfx: &Gfx, bottom: f32) {
use crate::talents::*;
let (mx, my) = mouse();
let t = &g.talents;
let cols = ((sw() / CW()) as i32 - 2).min(48);
let inner = (cols - 4) as usize;
// the lines of each branch: name and level, what it gives now (wrapped)
let rows_of = |b: usize| wrap(&branch_text(t, b), inner.saturating_sub(6)).len() as i32 + 2;
let rows = 5 + (0..BRANCHES).map(rows_of).sum::<i32>();
let x = grid_center(sw(), cols as f32 * CW()).max(0.0);
let y = (bottom - rows as f32 * CH() - CH()).max(CH() * 4.0);
gfx.window_titled(x, y, cols, rows, S(), false, FRAME_HI, BG, &tr("talent.title"), FRAME_HI);
self.rects.push(Rect::new(x, y, cols as f32 * CW(), rows as f32 * CH()));
let ix = x + CW() * 2.0;
let mut yy = y + CH();
// points and the way to the next one
gfx.text(&trf("talent.points", &[t.free().to_string()]), ix, yy, S(), if t.free() > 0 { GOLD } else { TXT });
yy += CH() * 1.2;
match t.progress() {
Some((have, need)) => {
ascii_bar(gfx, ix, yy, 16, (have / need).clamp(0.0, 1.0), MANA, SMALL());
gfx.text(&trf("talent.next", &[(have as i32).to_string(), (need as i32).to_string()]), ix + CW() * 17.0, yy, SMALL(), DIM);
}
None => gfx.text(&tr("talent.all"), ix, yy, SMALL(), DIM),
}
yy += CH() * 1.8;
for b in 0..BRANCHES {
let lv = t.lv[b];
let pips: String = (0..MAX_LEVEL).map(|k| if k < lv { '■' } else { '·' }).collect();
gfx.text(&format!("{} {}", tr(NAMES[b]), pips), ix, yy, S(), FRAME_HI);
// a button to learn the next level
if t.can_learn(b) {
let r = Rect::new(x + (cols - 7) as f32 * CW(), yy - CH() * 0.2, CW() * 4.0, CH() * 1.6);
let hov = r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hov { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.15, 0.1, 0.08, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, GOLD);
gfx.text("+", r.x + (r.w - gfx.measure("+", S())) * 0.5, r.y + (r.h - GH * S()) * 0.5, S(), GOLD);
self.touch_btns.push((r, TBtn::Learn(b as u8)));
}
yy += CH() * 1.2;
for l in wrap(&branch_text(t, b), inner.saturating_sub(6)) {
gfx.text(&l, ix + CW() * 2.0, yy, SMALL(), DIM);
yy += CH();
}
yy += CH() * 0.8;
}
}
// the hall of the fallen of this game: name, kind, level, heroes slain, how it fell
fn draw_hall(&mut self, g: &Game, gfx: &Gfx, x: f32, y: f32, cols: i32, rows: i32) {
gfx.text(&tr("hall.title"), x + CW() * 2.0, y + CH(), S(), GOLD);
let mut lines: Vec<(String, Color)> = Vec::new();
for f in g.hall.iter().rev() {
lines.push((format!("{:>2} {}", f.level, crate::names::full_name(f.kind, f.name, f.level)), FRAME_HI));
let kills = if f.kills > 0 { format!(" · {}", trf("hall.kills", &[f.kills.to_string()])) } else { String::new() };
lines.push((format!(" {} · {}{} · {}", tr(st(f.kind).name), crate::names::cause(f.bane), kills, mmss(f.time)), DIM));
}
if lines.is_empty() {
lines.push((tr("hall.empty"), DIM));
}
let first = y + CH() * 2.6;
let visible = (rows - 4).max(1) as usize;
self.ov_scroll = self.ov_scroll.min(lines.len().saturating_sub(visible));
let maxc = (cols - 4) as usize;
for (n, (text, col)) in lines.iter().enumerate().skip(self.ov_scroll).take(visible) {
let t: String = text.chars().take(maxc).collect();
gfx.text(&t, x + CW() * 2.0, first + (n - self.ov_scroll) as f32 * CH(), S(), *col);
}
if lines.len() > visible {
let up = Rect::new(x + (cols - 4) as f32 * CW(), y + CH() * 2.4, CW() * 2.0, CH() * 1.5);
let down = Rect::new(x + (cols - 4) as f32 * CW(), y + (rows - 2) as f32 * CH(), CW() * 2.0, CH() * 1.5);
gfx.text("\u{25b2}", up.x + CW() * 0.5, up.y, S(), FRAME_HI);
gfx.text("\u{25bc}", down.x + CW() * 0.5, down.y, S(), FRAME_HI);
self.touch_btns.push((up, TBtn::OvScroll(-2)));
self.touch_btns.push((down, TBtn::OvScroll(2)));
}
}
fn draw_overview(&mut self, g: &Game, gfx: &Gfx, bottom: f32) { fn draw_overview(&mut self, g: &Game, gfx: &Gfx, bottom: f32) {
let (sw, _) = (sw(), sh()); let (sw, _) = (sw(), sh());
let (mx, my) = mouse(); let (mx, my) = mouse();
@@ -1246,6 +1395,21 @@ impl Ui {
let mine: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && e.creature()).collect(); let mine: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && e.creature()).collect();
let wage: f32 = mine.iter().map(|e| st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)).sum(); let wage: f32 = mine.iter().map(|e| st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)).sum();
let mood = if mine.is_empty() { 0.0 } else { mine.iter().map(|e| e.happy.clamp(0.0, 1.0)).sum::<f32>() / mine.len() as f32 }; let mood = if mine.is_empty() { 0.0 } else { mine.iter().map(|e| e.happy.clamp(0.0, 1.0)).sum::<f32>() / mine.len() as f32 };
// the switch to the hall of the fallen (top right)
{
let label = if self.ov_fallen { tr("ov.living") } else { trf("ov.fallen", &[g.hall.len().to_string()]) };
let bw = gfx.measure(&label, SMALL()) + CW() * 2.0;
let r = Rect::new(x + (cols - 3) as f32 * CW() - bw, y + CH() * 0.8, bw, CH() * 1.4);
let hov = r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hov { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.15, 0.1, 0.08, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, FRAME_HI);
gfx.text(&label, r.x + CW(), r.y + (r.h - GH * SMALL()) * 0.5, SMALL(), FRAME_HI);
self.touch_btns.push((r, TBtn::OvFallen));
}
if self.ov_fallen {
self.draw_hall(g, gfx, x, y, cols, rows);
return;
}
let head = trf("ov.summary", &[mine.len().to_string(), (wage as i32).to_string(), ((mood * 100.0) as i32).to_string()]); let head = trf("ov.summary", &[mine.len().to_string(), (wage as i32).to_string(), ((mood * 100.0) as i32).to_string()]);
gfx.text(&head, x + CW() * 2.0, y + CH(), S(), GOLD); gfx.text(&head, x + CW() * 2.0, y + CH(), S(), GOLD);
// a match with allies: a row to send them gold (the name switches between the allies) // a match with allies: a row to send them gold (the name switches between the allies)
@@ -1291,8 +1455,9 @@ impl Ui {
for e in group { for e in group {
let w = (st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)) as i32; let w = (st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)) as i32;
let happy = (e.happy.clamp(0.0, 1.0) * 100.0) as i32; let happy = (e.happy.clamp(0.0, 1.0) * 100.0) as i32;
let job: String = tr(job_name(e)).chars().take(16).collect(); let job: String = tr(job_name(e)).chars().take(12).collect();
let line = format!(" {:>2} {:>3}% {:>4}g {:<16}", e.level, happy, w, job); let name: String = crate::names::display_name(e).chars().take(18).collect();
let line = format!(" {:>2} {:<18} {:>3}% {:>4}g {:<12}", e.level, name, happy, w, job);
let col = if e.happy > 0.6 { TXT } else if e.happy > 0.25 { GOLD } else { RED }; let col = if e.happy > 0.6 { TXT } else if e.happy > 0.25 { GOLD } else { RED };
lines.push((line, col, Some(e.id), e.hp / e.max_hp)); lines.push((line, col, Some(e.id), e.hp / e.max_hp));
} }
@@ -1667,6 +1832,10 @@ pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec
out.push((trf("trader.left", &[(left.ceil() as i32).to_string()]), DIM)); out.push((trf("trader.left", &[(left.ceil() as i32).to_string()]), DIM));
return out; return out;
} }
// a named creature: its name is the title
if e.name != 0 {
out.push((crate::names::display_name(e), GOLD));
}
out.push((trf("info.level", &[tr(s.name), e.level.to_string()]), if e.hero { BLUE } else { FRAME_HI })); out.push((trf("info.level", &[tr(s.name), e.level.to_string()]), if e.hero { BLUE } else { FRAME_HI }));
out.push((trf("info.rank", &[tr(&format!("rank.{}", e.level.clamp(1, 10)))]), GOLD)); out.push((trf("info.rank", &[tr(&format!("rank.{}", e.level.clamp(1, 10)))]), GOLD));
out.push((trf("info.hp", &[(e.hp.max(0.0) as i32).to_string(), (e.max_hp as i32).to_string()]), GREEN)); out.push((trf("info.hp", &[(e.hp.max(0.0) as i32).to_string(), (e.max_hp as i32).to_string()]), GREEN));
@@ -1729,6 +1898,13 @@ pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec
} }
let Some(t) = g.map.get(x, y) else { return out }; let Some(t) = g.map.get(x, y) else { return out };
let i = idx(x, y); let i = idx(x, y);
// a memorial stone: who lies here
if let Some(m) = g.memorial_at(i) {
let name = crate::names::full_name(m.kind, m.name, m.level);
out.push((tr("tile.memorial"), DIM));
out.push((trf("tile.memorial_info", &[name, tr(st(m.kind).name), m.level.to_string()]), TXT));
return out;
}
let owner = match t.owner { let owner = match t.owner {
Owner::Player => "owner.player", Owner::Player => "owner.player",
Owner::Hero => "owner.hero", Owner::Hero => "owner.hero",
+245
View File
@@ -0,0 +1,245 @@
// Weather above ground: mostly fair, now and then rain, fog or a storm, each for a few minutes
// with soft changes. Rain puts out burning bridges and keeps fire from catching; fog shortens the
// sight of everyone outside; a storm brings both rain and lightning that strikes units and trees.
// Only maps with land above ground have weather. It runs in the shared simulation (same on every
// computer of a match).
use crate::game::*;
use crate::map::*;
use serde::{Deserialize, Serialize};
pub const FAIR: u8 = 0;
pub const RAIN: u8 = 1;
pub const FOG: u8 = 2;
pub const STORM: u8 = 3;
pub const NAMES: [&str; 4] = ["weather.fair", "weather.rain", "weather.fog", "weather.storm"];
// seconds for a change to fade in or out
const FADE: f32 = 20.0;
// damage of a lightning strike
pub const BOLT_DAMAGE: f32 = 40.0;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Weather {
pub kind: u8, // the weather that is coming or here
pub shown: u8, // the weather that is felt now (changes when the old one has faded out)
pub level: f32, // how strong it is: 0 none .. 1 full
pub left: f32, // seconds until the next change
pub strike_t: f32, // seconds until the next lightning (storm)
pub flash: f32, // game time of the last lightning (the sky lights up)
}
// the weather that comes next: about 60% fair, 20% rain, 12% fog, 8% storm
fn pick(r: f32) -> u8 {
if r < 0.6 {
FAIR
} else if r < 0.8 {
RAIN
} else if r < 0.92 {
FOG
} else {
STORM
}
}
impl Weather {
// strength of an effect: rain falls in rain and storms, fog in fog (a little in storms)
pub fn rain(&self) -> f32 {
if matches!(self.shown, RAIN | STORM) { self.level } else { 0.0 }
}
pub fn fog(&self) -> f32 {
match self.shown {
FOG => self.level,
STORM => self.level * 0.4,
_ => 0.0,
}
}
// how much the daylight is dimmed (0 .. 1)
pub fn gloom(&self) -> f32 {
self.level
* match self.shown {
RAIN => 0.3,
FOG => 0.2,
STORM => 0.55,
_ => 0.0,
}
}
}
impl Game {
pub fn has_sky(&self) -> bool {
self.map.sky.iter().any(|&s| s)
}
// it rains on tile i right now (enough to put out fire)
pub fn rain_at(&self, i: usize) -> bool {
self.weather.rain() > 0.4 && self.map.sky(i)
}
// sight of a unit standing on tile i (fog outside shortens it)
pub fn sight_at(&self, i: usize, base: i32) -> i32 {
if !self.map.sky(i) {
return base;
}
((base as f32) * (1.0 - 0.5 * self.weather.fog())).round().max(2.0) as i32
}
pub(crate) fn update_weather(&mut self, dt: f32) {
if self.map.sky.is_empty() {
return;
}
let w = &mut self.weather;
w.left -= dt;
if w.left <= 0.0 {
// after bad weather it clears up more often
let r = self.rng.f();
let next = if w.kind != FAIR && r < 0.5 { FAIR } else { pick(self.rng.f()) };
let w = &mut self.weather;
w.kind = next;
w.left = if next == FAIR { 180.0 + self.rng.f() * 240.0 } else { 90.0 + self.rng.f() * 150.0 };
let w = &mut self.weather;
if w.kind != w.shown || w.level < 0.99 {
let key = ["msg.weather_clear", "msg.weather_rain", "msg.weather_fog", "msg.weather_storm"][next as usize];
if next != FAIR || w.shown != FAIR {
self.tell_local(key, vec![], None);
}
}
}
// soft changes: the old weather fades out before the new one fades in
let w = &mut self.weather;
if w.shown != w.kind {
w.level -= dt / FADE;
if w.level <= 0.0 {
w.level = 0.0;
w.shown = w.kind;
}
} else if w.kind != FAIR {
w.level = (w.level + dt / FADE).min(1.0);
} else {
w.level = (w.level - dt / FADE).max(0.0);
}
// rain puts out the burning bridges outside
if self.weather.rain() > 0.4 && !self.bridgework.burning.is_empty() {
let map = &self.map;
self.bridgework.burning.retain(|b| !map.sky(b.0));
}
// lightning in a storm
if self.weather.shown == STORM && self.weather.level > 0.6 {
self.weather.strike_t -= dt;
if self.weather.strike_t <= 0.0 {
self.weather.strike_t = 6.0 + self.rng.f() * 14.0;
self.lightning_strike();
}
}
}
// a bolt from the sky: a unit outside (now and then) or a tree, else bare land
pub(crate) fn lightning_strike(&mut self) {
let outside: Vec<usize> = (0..self.ents.len()).filter(|&j| {
let e = &self.ents[j];
e.alive && !e.held && e.captive == 0 && self.map.sky(e.tile())
}).collect();
let r = self.rng.f();
let (x, y) = if r < 0.35 && !outside.is_empty() {
let j = outside[(self.rng.next() as usize) % outside.len()];
let e = &mut self.ents[j];
e.hp -= BOLT_DAMAGE;
e.flash = 0.3;
e.bane = crate::names::BANE_LIGHTNING;
let (x, y, side, kind, hero, dead) = (e.x, e.y, e.side, e.kind, e.hero, e.hp <= 0.0);
if !hero && side == self.local_side {
self.tell_local("msg.lightning_unit", vec![format!("@{}", st(kind).name)], None);
}
if dead {
self.kill(j);
}
(x, y)
} else {
let trees: Vec<usize> = (0..self.map.t.len()).filter(|&i| self.map.t[i].kind == Kind::Tree && self.map.sky(i)).collect();
if let Some(&i) = trees.get((self.rng.next() as usize) % trees.len().max(1)) {
// the tree is split and burns down
let t = &mut self.map.t[i];
t.kind = Kind::Floor;
t.owner = Owner::None;
t.marked = false;
t.hp = 0.0;
let (tx, ty) = xy(i);
self.fx(3, tx as f32 + 0.5, ty as f32 + 0.5, 0.0, 0.0, 1.2, 0xff304050);
(tx as f32 + 0.5, ty as f32 + 0.5)
} else {
let sky: Vec<usize> = (0..self.map.t.len()).filter(|&i| self.map.sky(i)).collect();
let Some(&i) = sky.get((self.rng.next() as usize) % sky.len().max(1)) else { return };
let (tx, ty) = xy(i);
(tx as f32 + 0.5, ty as f32 + 0.5)
}
};
self.weather.flash = self.time;
self.fx(1, x, y, 0.0, 0.0, 0.5, 0xffffeeaa);
self.sfx(Sfx::Lightning, x, y);
// the thunder rolls over the whole land
if self.events.len() < 64 {
self.events.push(SfxEvent { sfx: Sfx::Thunder, pos: None });
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn surface_game() -> Game {
let mut g = Game::new(7, 96, 96);
g.add_surface(7, 0.5, 3);
assert!(g.has_sky());
g
}
#[test]
fn the_weather_changes_and_is_mostly_fair() {
let mut g = surface_game();
let mut seen = [0usize; 4];
for _ in 0..(4 * 60 * 60) {
g.update_weather(1.0);
seen[g.weather.shown as usize] += 1;
}
assert!(seen[FAIR as usize] > seen.iter().sum::<usize>() / 3, "{:?}", seen);
assert!(seen.iter().filter(|&&n| n > 0).count() >= 3, "several kinds of weather: {:?}", seen);
// no weather underground only
let mut u = Game::new(7, 96, 96);
u.weather.left = 0.0;
u.update_weather(1.0);
assert_eq!(u.weather.level, 0.0);
}
#[test]
fn rain_puts_out_fires_and_fog_shortens_sight() {
let mut g = surface_game();
let i = (0..g.map.t.len()).find(|&i| g.map.sky(i)).unwrap();
g.bridgework.burning.push((i, 0.0));
g.weather = Weather { kind: RAIN, shown: RAIN, level: 1.0, left: 100.0, ..Default::default() };
g.update_weather(0.1);
assert!(g.bridgework.burning.is_empty(), "rain puts the fire out");
assert!(g.rain_at(i));
g.weather = Weather { kind: FOG, shown: FOG, level: 1.0, left: 100.0, ..Default::default() };
assert!(g.sight_at(i, 6) < 6);
let under = (0..g.map.t.len()).find(|&i| !g.map.sky(i)).unwrap();
assert_eq!(g.sight_at(under, 6), 6, "underground nothing changes");
}
#[test]
fn storms_strike_with_lightning() {
let mut g = surface_game();
let trees0 = g.map.t.iter().filter(|t| t.kind == Kind::Tree).count();
let i = (0..g.map.t.len()).find(|&i| g.map.sky(i) && g.map.t[i].walkable()).unwrap();
let (x, y) = xy(i);
let id = g.spawn(EK::Knight, true, x as f32 + 0.5, y as f32 + 0.5, 1);
g.weather = Weather { kind: STORM, shown: STORM, level: 1.0, left: 1000.0, ..Default::default() };
for _ in 0..30 {
g.lightning_strike();
}
let trees = g.map.t.iter().filter(|t| t.kind == Kind::Tree).count();
let hurt = g.ents.iter().find(|e| e.id == id).map_or(true, |e| e.hp < e.max_hp);
assert!(trees < trees0 || hurt, "lightning hit something");
assert!(g.weather.flash > 0.0 || g.time == 0.0);
}
}
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
"""Build the desktop versions of DungeonCraft into dist/.
python3 tools/build_desktop.py # everything this machine can build
python3 tools/build_desktop.py linux windows # only these
- linux: dist/DungeonCraft-<version>-x86_64.AppImage
built with cargo-zigbuild against glibc 2.28, so it runs on distributions from about 2019 on;
needs on the build machine: zig, cargo-zigbuild, the ALSA headers (alsa-lib / libasound2-dev);
appimagetool is downloaded once into ~/.cache/dungeoncraft/
- windows: dist/DungeonCraft-<version>-windows-x64.exe (one file, Windows 10 and newer)
cross-compiled with cargo-zigbuild for x86_64-pc-windows-gnu
- macos: dist/DungeonCraft-<version>-macos.zip (DungeonCraft.app, Apple silicon + Intel)
only on a Mac (Apple's SDK is needed); the app is signed ad hoc, not notarised
Everything the game needs (graphics, music, languages) is inside the program; nothing else is packed.
The tools: `cargo install --locked cargo-zigbuild`, zig from the package manager or `mise install zig@0.15`
(found on PATH, in $CARGO_ZIGBUILD_ZIG_PATH or in mise's install folder), and the Rust targets
(`rustup target add ...`, the script tells which).
"""
import os
import pathlib
import platform
import shutil
import stat
import subprocess
import sys
import urllib.request
ROOT = pathlib.Path(__file__).resolve().parent.parent
DIST = ROOT / "dist"
CACHE = pathlib.Path(os.environ.get("XDG_CACHE_HOME") or pathlib.Path.home() / ".cache") / "dungeoncraft"
ICON = ROOT / "www" / "icons" / "icon-512.png"
GLIBC = "2.28"
APPIMAGETOOL_URL = "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
DESKTOP_ENTRY = """[Desktop Entry]
Type=Application
Name=DungeonCraft
Comment=Dig. Build. Defend. Conquer.
Exec=dungeoncraft
Icon=dungeoncraft
Categories=Game;StrategyGame;
Terminal=false
"""
INFO_PLIST = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>DungeonCraft</string>
<key>CFBundleDisplayName</key><string>DungeonCraft</string>
<key>CFBundleIdentifier</key><string>org.familie-zink.dungeoncraft</string>
<key>CFBundleExecutable</key><string>DungeonCraft</string>
<key>CFBundleIconFile</key><string>DungeonCraft</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key><string>{short}</string>
<key>CFBundleVersion</key><string>{version}</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>LSApplicationCategoryType</key><string>public.app-category.strategy-games</string>
<key>NSHighResolutionCapable</key><true/>
</dict>
</plist>
"""
class BuildError(Exception):
pass
def log(msg):
print(f"==> {msg}", flush=True)
def run(cmd, env=None, cwd=ROOT):
print(" $", " ".join(str(c) for c in cmd), flush=True)
r = subprocess.run([str(c) for c in cmd], cwd=cwd, env=env)
if r.returncode != 0:
raise BuildError(f"{pathlib.Path(str(cmd[0])).name} failed (exit {r.returncode})")
def version():
return (ROOT / "VERSION").read_text(encoding="utf-8").strip()
def cargo():
c = shutil.which("cargo") or str(pathlib.Path.home() / ".cargo" / "bin" / "cargo")
if not pathlib.Path(c).exists():
raise BuildError("cargo not found: install Rust with rustup")
return c
def tool_env():
env = dict(os.environ)
env["PATH"] = str(pathlib.Path(cargo()).parent) + os.pathsep + env.get("PATH", "")
return env
def need_target(triple):
rustup = shutil.which("rustup", path=tool_env()["PATH"])
if not rustup:
return
have = subprocess.run([rustup, "target", "list", "--installed"], capture_output=True, text=True).stdout.split()
if triple not in have:
raise BuildError(f"Rust target missing: rustup target add {triple}")
def zig_env():
env = tool_env()
if not shutil.which("cargo-zigbuild", path=env["PATH"]):
raise BuildError("cargo-zigbuild missing: cargo install --locked cargo-zigbuild")
installs = pathlib.Path.home() / ".local" / "share" / "mise" / "installs" / "zig"
from_mise = sorted(installs.glob("*/zig"), key=lambda p: [int(x) if x.isdigit() else 0 for x in p.parent.name.split(".")])
candidates = [env.get("CARGO_ZIGBUILD_ZIG_PATH"), shutil.which("zig", path=env["PATH"])] + [str(p) for p in reversed(from_mise)]
def works(z): # a mise shim without an active version is on PATH but does not run
try:
return subprocess.run([z, "version"], capture_output=True, timeout=30).returncode == 0
except OSError:
return False
zig = next((z for z in candidates if z and works(z)), None)
if not zig:
raise BuildError("zig not found: install it (package manager, or `mise install zig@0.15`)")
env["CARGO_ZIGBUILD_ZIG_PATH"] = zig
return env
# ------------------------------------------------------------------ Linux: AppImage
def appimagetool():
tool = CACHE / "appimagetool-x86_64.AppImage"
if not tool.exists():
log(f"downloading appimagetool to {tool}")
CACHE.mkdir(parents=True, exist_ok=True)
tmp = tool.with_suffix(".part")
with urllib.request.urlopen(APPIMAGETOOL_URL, timeout=120) as r, open(tmp, "wb") as f:
shutil.copyfileobj(r, f)
tmp.rename(tool)
tool.chmod(tool.stat().st_mode | stat.S_IXUSR)
return tool
def build_linux(ver):
if platform.system() != "Linux" or platform.machine() not in ("x86_64", "AMD64"):
raise BuildError("the AppImage is built on x86_64 Linux")
need_target("x86_64-unknown-linux-gnu")
env = zig_env()
# libasound is only linked by name: its own glibc references are the user's system's business
env["RUSTFLAGS"] = (env.get("RUSTFLAGS", "") + " -C link-arg=-Wl,--allow-shlib-undefined").strip()
log(f"Linux build (glibc {GLIBC})")
run([cargo(), "zigbuild", "--release", "--target", f"x86_64-unknown-linux-gnu.{GLIBC}"], env=env)
binary = ROOT / "target" / "x86_64-unknown-linux-gnu" / "release" / "dungeoncraft"
appdir = ROOT / "target" / "AppDir"
shutil.rmtree(appdir, ignore_errors=True)
(appdir / "usr" / "bin").mkdir(parents=True)
shutil.copy2(binary, appdir / "usr" / "bin" / "dungeoncraft")
(appdir / "dungeoncraft.desktop").write_text(DESKTOP_ENTRY, encoding="utf-8")
shutil.copy2(ICON, appdir / "dungeoncraft.png")
os.symlink("dungeoncraft.png", appdir / ".DirIcon")
os.symlink("usr/bin/dungeoncraft", appdir / "AppRun")
out = DIST / f"DungeonCraft-{ver}-x86_64.AppImage"
out.unlink(missing_ok=True)
tenv = dict(os.environ, ARCH="x86_64", APPIMAGE_EXTRACT_AND_RUN="1") # no FUSE needed for the tool
log("packing the AppImage")
run([appimagetool(), "--no-appstream", appdir, out], env=tenv)
return out
# ------------------------------------------------------------------ Windows: one exe
def build_windows(ver):
need_target("x86_64-pc-windows-gnu")
env = zig_env()
log("Windows build")
run([cargo(), "zigbuild", "--release", "--target", "x86_64-pc-windows-gnu"], env=env)
out = DIST / f"DungeonCraft-{ver}-windows-x64.exe"
shutil.copy2(ROOT / "target" / "x86_64-pc-windows-gnu" / "release" / "dungeoncraft.exe", out)
return out
# ------------------------------------------------------------------ macOS: universal .app in a zip
def build_macos(ver):
if platform.system() != "Darwin":
raise BuildError("the macOS app can only be built on a Mac (Apple's SDK is needed)")
env = tool_env()
arches = ["aarch64-apple-darwin", "x86_64-apple-darwin"]
for t in arches:
need_target(t)
env["MACOSX_DEPLOYMENT_TARGET"] = "11.0"
for t in arches:
log(f"macOS build {t}")
run([cargo(), "build", "--release", "--target", t], env=env)
app = ROOT / "target" / "DungeonCraft.app"
shutil.rmtree(app, ignore_errors=True)
(app / "Contents" / "MacOS").mkdir(parents=True)
(app / "Contents" / "Resources").mkdir(parents=True)
run(["lipo", "-create", "-output", app / "Contents" / "MacOS" / "DungeonCraft"] + [ROOT / "target" / t / "release" / "dungeoncraft" for t in arches])
short = ver.lstrip("v").replace("-", ".")
(app / "Contents" / "Info.plist").write_text(INFO_PLIST.format(short=short, version=ver), encoding="utf-8")
iconset = ROOT / "target" / "DungeonCraft.iconset"
shutil.rmtree(iconset, ignore_errors=True)
iconset.mkdir()
for size in (16, 32, 128, 256, 512):
run(["sips", "-z", size, size, ICON, "--out", iconset / f"icon_{size}x{size}.png"])
if size <= 256:
run(["sips", "-z", size * 2, size * 2, ICON, "--out", iconset / f"icon_{size}x{size}@2x.png"])
run(["iconutil", "-c", "icns", iconset, "-o", app / "Contents" / "Resources" / "DungeonCraft.icns"])
run(["codesign", "--force", "--deep", "--sign", "-", app])
out = DIST / f"DungeonCraft-{ver}-macos.zip"
out.unlink(missing_ok=True)
run(["ditto", "-c", "-k", "--keepParent", app, out])
return out
BUILDERS = {"linux": build_linux, "windows": build_windows, "macos": build_macos}
def main(argv):
if any(a in ("-h", "--help") for a in argv):
print(__doc__)
return 0
wanted = argv or (["macos"] if platform.system() == "Darwin" else ["linux", "windows"])
unknown = [w for w in wanted if w not in BUILDERS]
if unknown:
print(f"unknown target(s): {', '.join(unknown)} (linux, windows, macos)", file=sys.stderr)
return 2
DIST.mkdir(exist_ok=True)
ver = version()
done, failed = [], []
for w in wanted:
try:
done.append(BUILDERS[w](ver))
except BuildError as e:
failed.append(f"{w}: {e}")
print(f"!! {w}: {e}", file=sys.stderr)
print()
for p in done:
print(f"built {p.relative_to(ROOT)} ({p.stat().st_size / 1e6:.1f} MB)")
for f in failed:
print(f"failed {f}")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))