feat: drive agent behavior with Rune scripts
This commit is contained in:
parent
5760d097f7
commit
dd02a16d36
|
|
@ -2,37 +2,604 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ascii"
|
name = "ascii"
|
||||||
version = "1.1.0"
|
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 = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chunked_transfer"
|
name = "chunked_transfer"
|
||||||
version = "1.5.0"
|
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 = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
|
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "codespan-reporting"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||||
|
dependencies = [
|
||||||
|
"termcolor",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "critical-section"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
||||||
|
|
||||||
|
[[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]]
|
||||||
|
name = "generator"
|
||||||
|
version = "0.8.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54ade96dc9003043bce7c035c85a9df5a858bfb2039c5a2e6fdf00f324f6c551"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"rustversion",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpdate"
|
name = "httpdate"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.190"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.34"
|
version = "0.4.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loom"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"generator",
|
||||||
|
"scoped-tls",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchers"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||||
|
dependencies = [
|
||||||
|
"regex-automata",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "musli"
|
||||||
|
version = "0.0.124"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b310b280353d9e1c92861820321f8742b02666acaf984a29cd8946965444384"
|
||||||
|
dependencies = [
|
||||||
|
"loom",
|
||||||
|
"musli-core",
|
||||||
|
"serde",
|
||||||
|
"simdutf8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "musli-core"
|
||||||
|
version = "0.0.124"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d00e227a374e92550ce2eb5002ae116e02a43926d7243c95997138406ae4e157"
|
||||||
|
dependencies = [
|
||||||
|
"musli-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "musli-macros"
|
||||||
|
version = "0.0.124"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7427c9aa85c882cd4dbe712d2fcdc511db05d595f7787e6747c90cd7d67efc4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.50.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project"
|
||||||
|
version = "1.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-internal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-internal"
|
||||||
|
version = "1.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rune"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a577a0dc4b3e4cfe89247325b14b5f30096ddc82fa51fffb1d823fbc85b410"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"codespan-reporting",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"musli",
|
||||||
|
"num",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project",
|
||||||
|
"rune-alloc",
|
||||||
|
"rune-core",
|
||||||
|
"rune-macros",
|
||||||
|
"rune-tracing",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"syntree",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rune-alloc"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "702abcb136e7e4ff61c29006f018ab535728388b950bfbd50a0a66d7a2db1ff6"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"pin-project",
|
||||||
|
"rune-alloc-macros",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rune-alloc-macros"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98fa2ddc4ebb390a257f05659081e5f4a1419ca65f0c7a3f4b02547860dd93f4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rune-core"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fab02420e2189ad2d8d9efceca5319782873eb547c61b68732746cc161547aa"
|
||||||
|
dependencies = [
|
||||||
|
"musli",
|
||||||
|
"rune-alloc",
|
||||||
|
"serde",
|
||||||
|
"twox-hash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rune-macros"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d314f34d8b30569189d90ac0f96eeedf629492c10d18146b40cfb76bd3fd2d52"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rune-core",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rune-tracing"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5b903935dfcb0ab9eec395f4c2e04b174448bb8d1fd35ef5181ad67140239a9"
|
||||||
|
dependencies = [
|
||||||
|
"rune-tracing-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rune-tracing-macros"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75193c702bddd8a5b74b1f9a5a992cef3c6b2df2909d6f0bb0f4b0f3ec8a2f66"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sim"
|
name = "sim"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"rune",
|
||||||
"tiny_http",
|
"tiny_http",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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 = "smallvec"
|
||||||
|
version = "1.16.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
|
||||||
|
|
||||||
|
[[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]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syntree"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00c99c9cda412afe293a6b962af651b4594161ba88c1affe7ef66459ea040a06"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny_http"
|
name = "tiny_http"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
|
|
@ -44,3 +611,135 @@ dependencies = [
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||||
|
dependencies = [
|
||||||
|
"matchers",
|
||||||
|
"nu-ansi-term",
|
||||||
|
"once_cell",
|
||||||
|
"regex-automata",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "twox-hash"
|
||||||
|
version = "2.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[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-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "879fb705ce98c32e41ebdb970fbe1204f8492423b314c6ab0354c3e7b5542866"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "708882a28301d604fa039cc7727607a98d04c4b86dc76ec9cc683f805d709759"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,5 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rune = "0.14.2"
|
||||||
tiny_http = "0.12"
|
tiny_http = "0.12"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Behaviour scripts. Each function receives what the entity senses and
|
||||||
|
// returns the name of an action; the Rust side carries it out.
|
||||||
|
|
||||||
|
// person: sleep | go_home | eat | haul | hunt | forage | chop | mine | wander
|
||||||
|
pub fn person(c) {
|
||||||
|
if c.night {
|
||||||
|
return if c.at_home { "sleep" } else { "go_home" };
|
||||||
|
}
|
||||||
|
if c.hunger > 50.0 && c.carried_food == 0 && c.stock_food > 0 {
|
||||||
|
return "eat";
|
||||||
|
}
|
||||||
|
if c.carry >= 6 {
|
||||||
|
return "haul";
|
||||||
|
}
|
||||||
|
if c.stock_food < 12 || c.hunger > 50.0 {
|
||||||
|
if c.rabbit_near && (!c.berry_near || c.roll < 0.3) {
|
||||||
|
return "hunt";
|
||||||
|
}
|
||||||
|
if c.berry_near {
|
||||||
|
return "forage";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.stock_wood <= c.stock_stone * 2 + 8 {
|
||||||
|
if c.tree_near { "chop" } else if c.stone_near { "mine" } else { "wander" }
|
||||||
|
} else {
|
||||||
|
if c.stone_near { "mine" } else if c.tree_near { "chop" } else { "wander" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rabbit: flee | wander | stay
|
||||||
|
pub fn rabbit(c) {
|
||||||
|
if c.wolf_near {
|
||||||
|
if c.roll < 0.8 { "flee" } else { "stay" }
|
||||||
|
} else if c.roll < 0.5 {
|
||||||
|
"wander"
|
||||||
|
} else {
|
||||||
|
"stay"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wolf: hunt | wander
|
||||||
|
pub fn wolf(c) {
|
||||||
|
if c.prey_near { "hunt" } else { "wander" }
|
||||||
|
}
|
||||||
145
src/index.html
145
src/index.html
|
|
@ -14,11 +14,31 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#side {
|
#side {
|
||||||
width: 280px;
|
width: 340px;
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#side input[type=number] {
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#script {
|
||||||
|
width: 100%;
|
||||||
|
height: 260px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #181818;
|
||||||
|
color: #ddd;
|
||||||
|
font: 12px monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scriptErr {
|
||||||
|
color: #f77;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
@ -40,8 +60,39 @@
|
||||||
<div id="side">
|
<div id="side">
|
||||||
<h3>World</h3>
|
<h3>World</h3>
|
||||||
<div id="hud"></div>
|
<div id="hud"></div>
|
||||||
|
<div>
|
||||||
|
<button id="pause">Pause</button>
|
||||||
|
Speed <select id="speed">
|
||||||
|
<option>1</option>
|
||||||
|
<option>2</option>
|
||||||
|
<option>4</option>
|
||||||
|
<option>8</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<h3>Tool</h3>
|
||||||
|
<div>
|
||||||
|
<select id="tool">
|
||||||
|
<option value="inspect">Inspect</option>
|
||||||
|
<option value="terrain">Paint terrain</option>
|
||||||
|
<option value="res">Place resource</option>
|
||||||
|
<option value="spawn">Spawn</option>
|
||||||
|
<option value="remove">Remove</option>
|
||||||
|
<option value="house">Add house</option>
|
||||||
|
<option value="energy">Set animal energy</option>
|
||||||
|
</select>
|
||||||
|
<select id="param"></select>
|
||||||
|
<input id="amount" type="number" value="5" min="1" max="100" title="amount / energy">
|
||||||
|
</div>
|
||||||
|
<div id="toolHelp" style="color:#888"></div>
|
||||||
<h3>Selected</h3>
|
<h3>Selected</h3>
|
||||||
<div id="hover">click a tile or drag to select a region</div>
|
<div id="hover">click a tile or drag to select a region</div>
|
||||||
|
<div id="editor"></div>
|
||||||
|
<h3>Stockpile</h3>
|
||||||
|
<div id="stockEdit"></div>
|
||||||
|
<h3>Behaviour script (Rune)</h3>
|
||||||
|
<textarea id="script" spellcheck="false"></textarea>
|
||||||
|
<div><button id="applyScript">Apply script</button></div>
|
||||||
|
<div id="scriptErr"></div>
|
||||||
<h3>Events</h3>
|
<h3>Events</h3>
|
||||||
<div id="log"></div>
|
<div id="log"></div>
|
||||||
<h3>Legend</h3>
|
<h3>Legend</h3>
|
||||||
|
|
@ -53,11 +104,12 @@
|
||||||
const COLORS = ['#10305e', '#2a62a8', '#e0d08a', '#6aaa4a', '#3f7d3a', '#8a8a6a', '#aaaaaa'];
|
const COLORS = ['#10305e', '#2a62a8', '#e0d08a', '#6aaa4a', '#3f7d3a', '#8a8a6a', '#aaaaaa'];
|
||||||
const cv = document.getElementById('c');
|
const cv = document.getElementById('c');
|
||||||
const ctx = cv.getContext('2d');
|
const ctx = cv.getContext('2d');
|
||||||
let terrain = null, off = null, state = null;
|
let terrain = null, off = null, state = null, terrainRev = -1;
|
||||||
|
|
||||||
async function loadTerrain() {
|
async function loadTerrain() {
|
||||||
const t = await (await fetch('/api/terrain')).json();
|
const t = await (await fetch('/api/terrain')).json();
|
||||||
terrain = t;
|
terrain = t;
|
||||||
|
terrainRev = state ? state.rev : -1;
|
||||||
cv.width = t.w * T; cv.height = t.h * T;
|
cv.width = t.w * T; cv.height = t.h * T;
|
||||||
off = document.createElement('canvas');
|
off = document.createElement('canvas');
|
||||||
off.width = cv.width; off.height = cv.height;
|
off.width = cv.width; off.height = cv.height;
|
||||||
|
|
@ -105,6 +157,8 @@
|
||||||
async function poll() {
|
async function poll() {
|
||||||
try {
|
try {
|
||||||
state = await (await fetch('/api/state')).json();
|
state = await (await fetch('/api/state')).json();
|
||||||
|
if (terrain && state.rev !== terrainRev) await loadTerrain();
|
||||||
|
syncControls();
|
||||||
const s = state.stock, h = Math.floor(state.hour), m = Math.floor((state.hour % 1) * 60);
|
const s = state.stock, h = Math.floor(state.hour), m = Math.floor((state.hour % 1) * 60);
|
||||||
document.getElementById('hud').innerHTML =
|
document.getElementById('hud').innerHTML =
|
||||||
`Day ${state.day}, ${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}<br>` +
|
`Day ${state.day}, ${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}<br>` +
|
||||||
|
|
@ -183,7 +237,92 @@
|
||||||
sel.x1 = t.x; sel.y1 = t.y;
|
sel.x1 = t.x; sel.y1 = t.y;
|
||||||
draw(); showSelection();
|
draw(); showSelection();
|
||||||
});
|
});
|
||||||
window.addEventListener('mouseup', () => { dragging = false; });
|
window.addEventListener('mouseup', () => {
|
||||||
|
if (!dragging) return;
|
||||||
|
dragging = false;
|
||||||
|
const cmd = toolCommand();
|
||||||
|
if (cmd) post('/api/edit', cmd);
|
||||||
|
buildEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
const $ = id => document.getElementById(id);
|
||||||
|
const post = (url, body) => fetch(url, { method: 'POST', body });
|
||||||
|
|
||||||
|
const TOOLS = {
|
||||||
|
inspect: { help: 'Click or drag to inspect and edit.', opts: null },
|
||||||
|
terrain: { help: 'Drag a region to paint it.', opts: TERRAIN.map((n, i) => [i, n]) },
|
||||||
|
res: { help: 'Drag a region; amount sets units per tile.', opts: [[1, 'Tree'], [2, 'Berry bush'], [3, 'Stone'], [0, 'Clear']] },
|
||||||
|
spawn: { help: 'Drag a region; one per walkable tile (max 25).', opts: [['person', 'Person'], ['rabbit', 'Rabbit'], ['wolf', 'Wolf']] },
|
||||||
|
remove: { help: 'Drag a region to remove things in it.', opts: [['all', 'Everything'], ['people', 'People'], ['rabbits', 'Rabbits'], ['wolves', 'Wolves'], ['houses', 'Houses'], ['res', 'Resources']] },
|
||||||
|
house: { help: 'Drag a region to add houses.', opts: null },
|
||||||
|
energy: { help: 'Drag a region; amount sets animal energy (1-100).', opts: null },
|
||||||
|
};
|
||||||
|
|
||||||
|
function toolCommand() {
|
||||||
|
const tool = $('tool').value;
|
||||||
|
if (tool === 'inspect' || !sel) return null;
|
||||||
|
const [x0, y0, x1, y1] = selRect();
|
||||||
|
const r = `${x0} ${y0} ${x1} ${y1}`, p = $('param').value, a = parseInt($('amount').value) || 1;
|
||||||
|
switch (tool) {
|
||||||
|
case 'terrain': return `terrain ${r} ${p}`;
|
||||||
|
case 'res': return `res ${r} ${p} ${a}`;
|
||||||
|
case 'spawn': return `spawn ${p} ${r}`;
|
||||||
|
case 'remove': return `remove ${r} ${p}`;
|
||||||
|
case 'house': return `house ${r}`;
|
||||||
|
case 'energy': return `energy ${r} ${a}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('tool').addEventListener('change', () => {
|
||||||
|
const t = TOOLS[$('tool').value];
|
||||||
|
$('param').innerHTML = (t.opts || []).map(([v, n]) => `<option value="${v}">${n}</option>`).join('');
|
||||||
|
$('param').style.display = t.opts ? '' : 'none';
|
||||||
|
$('toolHelp').textContent = t.help;
|
||||||
|
});
|
||||||
|
$('tool').dispatchEvent(new Event('change'));
|
||||||
|
|
||||||
|
$('pause').addEventListener('click', () => post('/api/edit', `pause ${state && state.paused ? 0 : 1}`));
|
||||||
|
$('speed').addEventListener('change', () => post('/api/edit', `speed ${$('speed').value}`));
|
||||||
|
|
||||||
|
let stockBuilt = false;
|
||||||
|
function syncControls() {
|
||||||
|
$('pause').textContent = state.paused ? 'Resume' : 'Pause';
|
||||||
|
if (document.activeElement !== $('speed')) $('speed').value = state.speed;
|
||||||
|
if (!stockBuilt) {
|
||||||
|
stockBuilt = true;
|
||||||
|
$('stockEdit').innerHTML = ['food', 'wood', 'stone', 'tools'].map(k =>
|
||||||
|
`${k} <input type="number" id="stock_${k}" min="0" value="${state.stock[k]}"> `).join('') +
|
||||||
|
'<button id="stockSet">Set</button>';
|
||||||
|
$('stockSet').addEventListener('click', () => {
|
||||||
|
for (const k of ['food', 'wood', 'stone', 'tools'])
|
||||||
|
post('/api/edit', `stock ${k} ${parseInt($('stock_' + k).value) || 0}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEditor() {
|
||||||
|
const el = $('editor');
|
||||||
|
el.innerHTML = '';
|
||||||
|
if (!sel || !state) return;
|
||||||
|
const [x0, y0, x1, y1] = selRect();
|
||||||
|
const people = state.people.filter(p => p[1] >= x0 && p[1] <= x1 && p[2] >= y0 && p[2] <= y1).slice(0, 5);
|
||||||
|
for (const p of people) {
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.innerHTML = `Edit #${p[0]}: ` + [['hunger', p[4]], ['food', p[5]], ['wood', p[6]], ['stone', p[7]]].map(([k, v]) =>
|
||||||
|
`${k} <input type="number" min="0" max="99" data-k="${k}" value="${v}">`).join(' ') + ' <button>Set</button>';
|
||||||
|
row.querySelector('button').addEventListener('click', () => {
|
||||||
|
for (const i of row.querySelectorAll('input'))
|
||||||
|
post('/api/edit', `person ${p[0]} ${i.dataset.k} ${parseInt(i.value) || 0}`);
|
||||||
|
});
|
||||||
|
el.appendChild(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('/api/script').then(r => r.text()).then(t => { $('script').value = t; });
|
||||||
|
$('applyScript').addEventListener('click', async () => {
|
||||||
|
const r = await post('/api/script', $('script').value);
|
||||||
|
$('scriptErr').textContent = r.ok ? 'Script applied.' : await r.text();
|
||||||
|
});
|
||||||
|
|
||||||
loadTerrain().then(poll);
|
loadTerrain().then(poll);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
100
src/main.rs
100
src/main.rs
|
|
@ -1,26 +1,63 @@
|
||||||
mod world;
|
mod world;
|
||||||
|
|
||||||
|
use std::io::Read;
|
||||||
|
use std::sync::mpsc::{self, Sender};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tiny_http::{Header, Response, Server};
|
use tiny_http::{Header, Method, Response, Server};
|
||||||
use world::World;
|
use world::{DEFAULT_SCRIPT, World};
|
||||||
|
|
||||||
const INDEX: &str = include_str!("index.html");
|
const INDEX: &str = include_str!("index.html");
|
||||||
|
|
||||||
|
enum Msg {
|
||||||
|
Edit(String),
|
||||||
|
Script(String, Sender<Result<(), String>>),
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let seed = std::env::args()
|
let seed = std::env::args()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.and_then(|s| s.parse().ok())
|
.and_then(|s| s.parse().ok())
|
||||||
.unwrap_or(42u64);
|
.unwrap_or(42u64);
|
||||||
let addr = std::env::var("SIM_ADDR").unwrap_or_else(|_| "127.0.0.1:7878".into());
|
let addr = std::env::var("SIM_ADDR").unwrap_or_else(|_| "127.0.0.1:7878".into());
|
||||||
let world = Arc::new(Mutex::new(World::new(seed)));
|
// The Rune VM is not Send, so the world lives on the sim thread and publishes JSON snapshots.
|
||||||
|
let terrain = Arc::new(Mutex::new(String::new()));
|
||||||
|
let state = Arc::new(Mutex::new(String::new()));
|
||||||
|
let script = Arc::new(Mutex::new(DEFAULT_SCRIPT.to_string()));
|
||||||
|
let (tx, rx) = mpsc::channel::<Msg>();
|
||||||
{
|
{
|
||||||
let world = Arc::clone(&world);
|
let (terrain, state, script) = (
|
||||||
|
Arc::clone(&terrain),
|
||||||
|
Arc::clone(&state),
|
||||||
|
Arc::clone(&script),
|
||||||
|
);
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
|
let mut world = World::new(seed);
|
||||||
|
let mut rev = u32::MAX;
|
||||||
loop {
|
loop {
|
||||||
world.lock().unwrap().step();
|
while let Ok(msg) = rx.try_recv() {
|
||||||
|
match msg {
|
||||||
|
Msg::Edit(cmd) => world.apply(&cmd),
|
||||||
|
Msg::Script(src, reply) => {
|
||||||
|
let r = world.set_script(&src);
|
||||||
|
if r.is_ok() {
|
||||||
|
*script.lock().unwrap() = src;
|
||||||
|
}
|
||||||
|
let _ = reply.send(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !world.paused {
|
||||||
|
for _ in 0..world.speed {
|
||||||
|
world.step();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if world.terrain_rev != rev {
|
||||||
|
rev = world.terrain_rev;
|
||||||
|
*terrain.lock().unwrap() = world.terrain_json();
|
||||||
|
}
|
||||||
|
*state.lock().unwrap() = world.state_json();
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -28,20 +65,51 @@ fn main() {
|
||||||
|
|
||||||
let server = Server::http(&addr).expect("failed to bind");
|
let server = Server::http(&addr).expect("failed to bind");
|
||||||
println!("Simulation running at http://{addr}");
|
println!("Simulation running at http://{addr}");
|
||||||
for req in server.incoming_requests() {
|
for mut req in server.incoming_requests() {
|
||||||
let world = Arc::clone(&world);
|
let (terrain, state, script) = (
|
||||||
|
Arc::clone(&terrain),
|
||||||
|
Arc::clone(&state),
|
||||||
|
Arc::clone(&script),
|
||||||
|
);
|
||||||
|
let tx = tx.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let (body, ctype) = match req.url() {
|
let post = *req.method() == Method::Post;
|
||||||
"/" => (INDEX.to_string(), "text/html; charset=utf-8"),
|
let mut input = String::new();
|
||||||
"/api/terrain" => (world.lock().unwrap().terrain_json(), "application/json"),
|
if post {
|
||||||
"/api/state" => (world.lock().unwrap().state_json(), "application/json"),
|
let _ = req.as_reader().take(65536).read_to_string(&mut input);
|
||||||
_ => {
|
|
||||||
let _ = req.respond(Response::from_string("not found").with_status_code(404));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
let (status, body, ctype) = match (post, req.url()) {
|
||||||
|
(false, "/") => (200, INDEX.to_string(), "text/html; charset=utf-8"),
|
||||||
|
(false, "/api/terrain") => {
|
||||||
|
(200, terrain.lock().unwrap().clone(), "application/json")
|
||||||
|
}
|
||||||
|
(false, "/api/state") => (200, state.lock().unwrap().clone(), "application/json"),
|
||||||
|
(false, "/api/script") => (
|
||||||
|
200,
|
||||||
|
script.lock().unwrap().clone(),
|
||||||
|
"text/plain; charset=utf-8",
|
||||||
|
),
|
||||||
|
(true, "/api/edit") if input.len() <= 256 => {
|
||||||
|
let _ = tx.send(Msg::Edit(input));
|
||||||
|
(200, "ok".to_string(), "text/plain")
|
||||||
|
}
|
||||||
|
(true, "/api/script") => {
|
||||||
|
let (rtx, rrx) = mpsc::channel();
|
||||||
|
let _ = tx.send(Msg::Script(input, rtx));
|
||||||
|
match rrx.recv_timeout(Duration::from_secs(5)) {
|
||||||
|
Ok(Ok(())) => (200, "ok".to_string(), "text/plain"),
|
||||||
|
Ok(Err(e)) => (400, e, "text/plain; charset=utf-8"),
|
||||||
|
Err(_) => (503, "timed out".to_string(), "text/plain"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (404, "not found".to_string(), "text/plain"),
|
||||||
};
|
};
|
||||||
let header = Header::from_bytes("Content-Type", ctype).unwrap();
|
let header = Header::from_bytes("Content-Type", ctype).unwrap();
|
||||||
let _ = req.respond(Response::from_string(body).with_header(header));
|
let _ = req.respond(
|
||||||
|
Response::from_string(body)
|
||||||
|
.with_status_code(status)
|
||||||
|
.with_header(header),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
510
src/world.rs
510
src/world.rs
|
|
@ -1,4 +1,7 @@
|
||||||
|
use rune::termcolor::Buffer;
|
||||||
|
use rune::{Any, Context, Diagnostics, Module, Source, Sources, Vm};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub const W: usize = 96;
|
pub const W: usize = 96;
|
||||||
pub const H: usize = 64;
|
pub const H: usize = 64;
|
||||||
|
|
@ -73,6 +76,77 @@ fn fbm(x: f32, y: f32, seed: u64) -> f32 {
|
||||||
sum / norm
|
sum / norm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What an entity senses this tick; passed to the Rune behaviour scripts.
|
||||||
|
#[derive(Any, Default, Clone)]
|
||||||
|
struct Ctx {
|
||||||
|
#[rune(get)]
|
||||||
|
night: bool,
|
||||||
|
#[rune(get)]
|
||||||
|
at_home: bool,
|
||||||
|
#[rune(get)]
|
||||||
|
hunger: f64,
|
||||||
|
#[rune(get)]
|
||||||
|
carry: i64,
|
||||||
|
#[rune(get)]
|
||||||
|
carried_food: i64,
|
||||||
|
#[rune(get)]
|
||||||
|
stock_food: i64,
|
||||||
|
#[rune(get)]
|
||||||
|
stock_wood: i64,
|
||||||
|
#[rune(get)]
|
||||||
|
stock_stone: i64,
|
||||||
|
#[rune(get)]
|
||||||
|
berry_near: bool,
|
||||||
|
#[rune(get)]
|
||||||
|
tree_near: bool,
|
||||||
|
#[rune(get)]
|
||||||
|
stone_near: bool,
|
||||||
|
#[rune(get)]
|
||||||
|
rabbit_near: bool,
|
||||||
|
#[rune(get)]
|
||||||
|
wolf_near: bool,
|
||||||
|
#[rune(get)]
|
||||||
|
prey_near: bool,
|
||||||
|
#[rune(get)]
|
||||||
|
roll: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DEFAULT_SCRIPT: &str = include_str!("behavior.rn");
|
||||||
|
|
||||||
|
/// Compiles the script and smoke-tests every entry point so errors surface at load time.
|
||||||
|
fn load_script(src: &str) -> Result<Vm, String> {
|
||||||
|
let mut module = Module::new();
|
||||||
|
module.ty::<Ctx>().map_err(|e| e.to_string())?;
|
||||||
|
let mut context = Context::with_default_modules().map_err(|e| e.to_string())?;
|
||||||
|
context.install(module).map_err(|e| e.to_string())?;
|
||||||
|
let mut sources = Sources::new();
|
||||||
|
sources
|
||||||
|
.insert(Source::new("behavior", src).map_err(|e| e.to_string())?)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let mut diagnostics = Diagnostics::new();
|
||||||
|
let unit = rune::prepare(&mut sources)
|
||||||
|
.with_context(&context)
|
||||||
|
.with_diagnostics(&mut diagnostics)
|
||||||
|
.build();
|
||||||
|
let unit = match unit {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(_) => {
|
||||||
|
let mut buf = Buffer::no_color();
|
||||||
|
let _ = diagnostics.emit(&mut buf, &sources);
|
||||||
|
return Err(String::from_utf8_lossy(buf.as_slice()).into_owned());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let runtime = context.runtime().map_err(|e| e.to_string())?;
|
||||||
|
let mut vm = Vm::new(Arc::new(runtime), Arc::new(unit));
|
||||||
|
for f in ["person", "rabbit", "wolf"] {
|
||||||
|
let v = vm
|
||||||
|
.call([f], (Ctx::default(),))
|
||||||
|
.map_err(|e| format!("{f}(): {e}"))?;
|
||||||
|
rune::from_value::<String>(v).map_err(|_| format!("{f}() must return a string"))?;
|
||||||
|
}
|
||||||
|
Ok(vm)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Person {
|
pub struct Person {
|
||||||
id: u32,
|
id: u32,
|
||||||
|
|
@ -116,6 +190,10 @@ pub struct World {
|
||||||
tick: u64,
|
tick: u64,
|
||||||
next_id: u32,
|
next_id: u32,
|
||||||
log: Vec<String>,
|
log: Vec<String>,
|
||||||
|
vm: Vm,
|
||||||
|
pub paused: bool,
|
||||||
|
pub speed: u32,
|
||||||
|
pub terrain_rev: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cheb(a: (usize, usize), b: (usize, usize)) -> usize {
|
fn cheb(a: (usize, usize), b: (usize, usize)) -> usize {
|
||||||
|
|
@ -163,6 +241,10 @@ impl World {
|
||||||
tick: DAY / 4,
|
tick: DAY / 4,
|
||||||
next_id: 1,
|
next_id: 1,
|
||||||
log: vec![],
|
log: vec![],
|
||||||
|
vm: load_script(DEFAULT_SCRIPT).expect("behavior.rn failed to load"),
|
||||||
|
paused: false,
|
||||||
|
speed: 1,
|
||||||
|
terrain_rev: 0,
|
||||||
};
|
};
|
||||||
w.home = w
|
w.home = w
|
||||||
.find_land(GRASS)
|
.find_land(GRASS)
|
||||||
|
|
@ -393,11 +475,22 @@ impl World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decide(&mut self, func: &str, c: Ctx) -> String {
|
||||||
|
match self.vm.call([func], (c,)) {
|
||||||
|
Ok(v) => rune::from_value::<String>(v).unwrap_or_else(|_| "wander".into()),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("script error in {func}: {e}");
|
||||||
|
"wander".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn tick_person(&mut self, i: usize) {
|
fn tick_person(&mut self, i: usize) {
|
||||||
let mut p = self.people[i].clone();
|
let mut p = self.people[i].clone();
|
||||||
let night = self.daylight() < 0.2;
|
let night = self.daylight() < 0.2;
|
||||||
let pos = (p.x, p.y);
|
let pos = (p.x, p.y);
|
||||||
let at_home = cheb(pos, self.home) <= 1;
|
let home = self.home;
|
||||||
|
let at_home = cheb(pos, home) <= 1;
|
||||||
p.hunger += if night && at_home { 0.04 } else { 0.1 };
|
p.hunger += if night && at_home { 0.04 } else { 0.1 };
|
||||||
if p.hunger >= 100.0 {
|
if p.hunger >= 100.0 {
|
||||||
p.dead = true;
|
p.dead = true;
|
||||||
|
|
@ -416,6 +509,97 @@ impl World {
|
||||||
self.food -= 1;
|
self.food -= 1;
|
||||||
p.hunger = (p.hunger - 25.0).max(0.0);
|
p.hunger = (p.hunger - 25.0).max(0.0);
|
||||||
}
|
}
|
||||||
|
self.craft_and_build();
|
||||||
|
}
|
||||||
|
if p.hunger > 50.0 && p.food > 0 {
|
||||||
|
p.food -= 1;
|
||||||
|
p.hunger = (p.hunger - 25.0).max(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let berry = self.nearest_res(pos, BERRY, 25);
|
||||||
|
let tree = self.nearest_res(pos, TREE, 25);
|
||||||
|
let stone = self.nearest_res(pos, STONE, 25);
|
||||||
|
let rabbit = Self::nearest_animal(&self.rabbits, pos, 25);
|
||||||
|
let c = Ctx {
|
||||||
|
night,
|
||||||
|
at_home,
|
||||||
|
hunger: p.hunger as f64,
|
||||||
|
carry: p.carry() as i64,
|
||||||
|
carried_food: p.food as i64,
|
||||||
|
stock_food: self.food as i64,
|
||||||
|
stock_wood: self.wood as i64,
|
||||||
|
stock_stone: self.stone as i64,
|
||||||
|
berry_near: berry.is_some(),
|
||||||
|
tree_near: tree.is_some(),
|
||||||
|
stone_near: stone.is_some(),
|
||||||
|
rabbit_near: rabbit.is_some(),
|
||||||
|
roll: self.rng.f() as f64,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let choice = self.decide("person", c);
|
||||||
|
|
||||||
|
let yield_n: u16 = if self.tools >= self.people.len() as u32 {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
let gather = match choice.as_str() {
|
||||||
|
"forage" => Some((BERRY, berry, "foraging")),
|
||||||
|
"chop" => Some((TREE, tree, "chopping wood")),
|
||||||
|
"mine" => Some((STONE, stone, "mining stone")),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
match (choice.as_str(), gather, rabbit) {
|
||||||
|
("sleep", ..) => p.action = "sleeping",
|
||||||
|
("go_home" | "haul" | "eat", ..) => {
|
||||||
|
p.action = match choice.as_str() {
|
||||||
|
"go_home" => "going home",
|
||||||
|
"haul" => "hauling goods",
|
||||||
|
_ => "going to eat",
|
||||||
|
};
|
||||||
|
if !at_home {
|
||||||
|
(p.x, p.y) = self.move_toward(pos, home);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("hunt", _, Some(r)) => {
|
||||||
|
p.action = "hunting";
|
||||||
|
let t = (self.rabbits[r].x, self.rabbits[r].y);
|
||||||
|
if cheb(pos, t) <= 1 {
|
||||||
|
if self.rng.chance(0.5) {
|
||||||
|
self.rabbits[r].dead = true;
|
||||||
|
p.food += 3;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(p.x, p.y) = self.move_toward(pos, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, Some((kind, Some(t), label)), _) => {
|
||||||
|
p.action = label;
|
||||||
|
let idx = pos.1 * W + pos.0;
|
||||||
|
if pos == t && self.res[idx].0 == kind {
|
||||||
|
let take = yield_n.min(self.res[idx].1);
|
||||||
|
self.res[idx].1 -= take;
|
||||||
|
if self.res[idx].1 == 0 {
|
||||||
|
self.res[idx].0 = 0;
|
||||||
|
}
|
||||||
|
match kind {
|
||||||
|
BERRY => p.food += take as u32,
|
||||||
|
TREE => p.wood += take as u32,
|
||||||
|
_ => p.stone += take as u32,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(p.x, p.y) = self.move_toward(pos, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
p.action = "wandering";
|
||||||
|
(p.x, p.y) = self.wander(p.x, p.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.people[i] = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn craft_and_build(&mut self) {
|
||||||
if self.wood >= 3 && self.stone >= 1 && self.tools < self.people.len() as u32 {
|
if self.wood >= 3 && self.stone >= 1 && self.tools < self.people.len() as u32 {
|
||||||
self.wood -= 3;
|
self.wood -= 3;
|
||||||
self.stone -= 1;
|
self.stone -= 1;
|
||||||
|
|
@ -436,103 +620,6 @@ impl World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.hunger > 50.0 && p.food > 0 {
|
|
||||||
p.food -= 1;
|
|
||||||
p.hunger = (p.hunger - 25.0).max(0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let home = self.home;
|
|
||||||
let yield_n = if self.tools >= self.people.len() as u32 {
|
|
||||||
2
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
};
|
|
||||||
let mut target: Option<(usize, usize)> = None;
|
|
||||||
let mut gather: Option<u8> = None;
|
|
||||||
let mut hunt = false;
|
|
||||||
|
|
||||||
if night {
|
|
||||||
if at_home {
|
|
||||||
p.action = "sleeping";
|
|
||||||
} else {
|
|
||||||
p.action = "going home";
|
|
||||||
target = Some(home);
|
|
||||||
}
|
|
||||||
} else if p.hunger > 50.0 && p.food == 0 && self.food > 0 {
|
|
||||||
p.action = "going to eat";
|
|
||||||
target = Some(home);
|
|
||||||
} else if p.carry() >= 6 {
|
|
||||||
p.action = "hauling goods";
|
|
||||||
target = Some(home);
|
|
||||||
} else {
|
|
||||||
let order: [u8; 3] = if self.food < 12 || p.hunger > 50.0 {
|
|
||||||
[BERRY, TREE, STONE]
|
|
||||||
} else if self.wood <= self.stone * 2 + 8 {
|
|
||||||
[TREE, STONE, BERRY]
|
|
||||||
} else {
|
|
||||||
[STONE, TREE, BERRY]
|
|
||||||
};
|
|
||||||
for k in order {
|
|
||||||
if let Some(t) = self.nearest_res(pos, k, 25) {
|
|
||||||
target = Some(t);
|
|
||||||
gather = Some(k);
|
|
||||||
p.action = match k {
|
|
||||||
BERRY => "foraging",
|
|
||||||
TREE => "chopping wood",
|
|
||||||
_ => "mining stone",
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (self.food < 12 || p.hunger > 50.0)
|
|
||||||
&& (gather != Some(BERRY) || self.rng.chance(0.3))
|
|
||||||
{
|
|
||||||
if let Some(r) = Self::nearest_animal(&self.rabbits, pos, 25) {
|
|
||||||
target = Some((self.rabbits[r].x, self.rabbits[r].y));
|
|
||||||
hunt = true;
|
|
||||||
gather = None;
|
|
||||||
p.action = "hunting";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(t) = target {
|
|
||||||
if hunt {
|
|
||||||
if cheb(pos, t) <= 1 {
|
|
||||||
if let Some(r) = Self::nearest_animal(&self.rabbits, pos, 1) {
|
|
||||||
if self.rng.chance(0.5) {
|
|
||||||
self.rabbits[r].dead = true;
|
|
||||||
p.food += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(p.x, p.y) = self.move_toward(pos, t);
|
|
||||||
}
|
|
||||||
} else if let Some(k) = gather {
|
|
||||||
let idx = pos.1 * W + pos.0;
|
|
||||||
if pos == t && self.res[idx].0 == k {
|
|
||||||
let take = (yield_n as u16).min(self.res[idx].1);
|
|
||||||
self.res[idx].1 -= take;
|
|
||||||
if self.res[idx].1 == 0 {
|
|
||||||
self.res[idx].0 = 0;
|
|
||||||
}
|
|
||||||
match k {
|
|
||||||
BERRY => p.food += take as u32,
|
|
||||||
TREE => p.wood += take as u32,
|
|
||||||
_ => p.stone += take as u32,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(p.x, p.y) = self.move_toward(pos, t);
|
|
||||||
}
|
|
||||||
} else if !at_home || p.action == "going to eat" && cheb(pos, t) > 1 {
|
|
||||||
(p.x, p.y) = self.move_toward(pos, t);
|
|
||||||
}
|
|
||||||
} else if p.action != "sleeping" {
|
|
||||||
p.action = "wandering";
|
|
||||||
(p.x, p.y) = self.wander(p.x, p.y);
|
|
||||||
}
|
|
||||||
self.people[i] = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick_rabbit(&mut self, i: usize) {
|
fn tick_rabbit(&mut self, i: usize) {
|
||||||
let mut a = self.rabbits[i].clone();
|
let mut a = self.rabbits[i].clone();
|
||||||
|
|
@ -541,14 +628,20 @@ impl World {
|
||||||
a.energy = (a.energy + 0.2).min(100.0);
|
a.energy = (a.energy + 0.2).min(100.0);
|
||||||
}
|
}
|
||||||
let pos = (a.x, a.y);
|
let pos = (a.x, a.y);
|
||||||
if let Some(w) = Self::nearest_animal(&self.wolves, pos, 4) {
|
let wolf = Self::nearest_animal(&self.wolves, pos, 4);
|
||||||
if self.rng.chance(0.8) {
|
let c = Ctx {
|
||||||
|
wolf_near: wolf.is_some(),
|
||||||
|
roll: self.rng.f() as f64,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
match (self.decide("rabbit", c).as_str(), wolf) {
|
||||||
|
("flee", Some(w)) => {
|
||||||
let dx = (a.x as i32 - self.wolves[w].x as i32).signum();
|
let dx = (a.x as i32 - self.wolves[w].x as i32).signum();
|
||||||
let dy = (a.y as i32 - self.wolves[w].y as i32).signum();
|
let dy = (a.y as i32 - self.wolves[w].y as i32).signum();
|
||||||
(a.x, a.y) = self.move_dir(a.x, a.y, dx, dy);
|
(a.x, a.y) = self.move_dir(a.x, a.y, dx, dy);
|
||||||
}
|
}
|
||||||
} else if self.rng.chance(0.5) {
|
("wander", _) => (a.x, a.y) = self.wander(a.x, a.y),
|
||||||
(a.x, a.y) = self.wander(a.x, a.y);
|
_ => {}
|
||||||
}
|
}
|
||||||
if a.energy > 80.0 && self.rabbits.len() < 120 && self.rng.chance(0.01) {
|
if a.energy > 80.0 && self.rabbits.len() < 120 && self.rng.chance(0.01) {
|
||||||
a.energy -= 30.0;
|
a.energy -= 30.0;
|
||||||
|
|
@ -569,7 +662,13 @@ impl World {
|
||||||
let mut a = self.wolves[i].clone();
|
let mut a = self.wolves[i].clone();
|
||||||
a.energy -= 0.15;
|
a.energy -= 0.15;
|
||||||
let pos = (a.x, a.y);
|
let pos = (a.x, a.y);
|
||||||
if let Some(r) = Self::nearest_animal(&self.rabbits, pos, 10) {
|
let prey = Self::nearest_animal(&self.rabbits, pos, 10);
|
||||||
|
let c = Ctx {
|
||||||
|
prey_near: prey.is_some(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
match (self.decide("wolf", c).as_str(), prey) {
|
||||||
|
("hunt", Some(r)) => {
|
||||||
let t = (self.rabbits[r].x, self.rabbits[r].y);
|
let t = (self.rabbits[r].x, self.rabbits[r].y);
|
||||||
if cheb(pos, t) <= 1 {
|
if cheb(pos, t) <= 1 {
|
||||||
if self.rng.chance(0.4) {
|
if self.rng.chance(0.4) {
|
||||||
|
|
@ -579,8 +678,8 @@ impl World {
|
||||||
} else {
|
} else {
|
||||||
(a.x, a.y) = self.move_toward(pos, t);
|
(a.x, a.y) = self.move_toward(pos, t);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
(a.x, a.y) = self.wander(a.x, a.y);
|
_ => (a.x, a.y) = self.wander(a.x, a.y),
|
||||||
}
|
}
|
||||||
if a.energy > 90.0 && self.wolves.len() < 10 && self.rng.chance(0.02) {
|
if a.energy > 90.0 && self.wolves.len() < 10 && self.rng.chance(0.02) {
|
||||||
a.energy -= 40.0;
|
a.energy -= 40.0;
|
||||||
|
|
@ -597,6 +696,202 @@ impl World {
|
||||||
self.wolves[i] = a;
|
self.wolves[i] = a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_script(&mut self, src: &str) -> Result<(), String> {
|
||||||
|
self.vm = load_script(src)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rect(t: &[&str], i: usize) -> Option<(usize, usize, usize, usize)> {
|
||||||
|
let n: Vec<i64> = (i..i + 4).filter_map(|k| t.get(k)?.parse().ok()).collect();
|
||||||
|
if n.len() != 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let c = |v: i64, max: usize| v.clamp(0, max as i64 - 1) as usize;
|
||||||
|
let (x0, x1) = (c(n[0].min(n[2]), W), c(n[0].max(n[2]), W));
|
||||||
|
let (y0, y1) = (c(n[1].min(n[3]), H), c(n[1].max(n[3]), H));
|
||||||
|
Some((x0, y0, x1, y1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies one whitespace-separated edit command; malformed commands are ignored.
|
||||||
|
pub fn apply(&mut self, cmd: &str) {
|
||||||
|
let t: Vec<&str> = cmd.split_whitespace().collect();
|
||||||
|
let num = |i: usize| t.get(i).and_then(|s| s.parse::<i64>().ok());
|
||||||
|
match t.first().copied() {
|
||||||
|
Some("pause") => self.paused = num(1) != Some(0),
|
||||||
|
Some("speed") => {
|
||||||
|
if let Some(n) = num(1) {
|
||||||
|
self.speed = n.clamp(1, 8) as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("stock") => {
|
||||||
|
if let (Some(name), Some(v)) = (t.get(1), num(2)) {
|
||||||
|
let v = v.clamp(0, 9999) as u32;
|
||||||
|
match *name {
|
||||||
|
"food" => self.food = v,
|
||||||
|
"wood" => self.wood = v,
|
||||||
|
"stone" => self.stone = v,
|
||||||
|
"tools" => self.tools = v,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("person") => {
|
||||||
|
if let (Some(id), Some(field), Some(v)) = (num(1), t.get(2), num(3)) {
|
||||||
|
if let Some(p) = self.people.iter_mut().find(|p| p.id as i64 == id) {
|
||||||
|
let u = v.clamp(0, 99) as u32;
|
||||||
|
match *field {
|
||||||
|
"hunger" => p.hunger = v.clamp(0, 99) as f32,
|
||||||
|
"food" => p.food = u,
|
||||||
|
"wood" => p.wood = u,
|
||||||
|
"stone" => p.stone = u,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("energy") => {
|
||||||
|
if let (Some((x0, y0, x1, y1)), Some(v)) = (Self::rect(&t, 1), num(5)) {
|
||||||
|
let v = v.clamp(1, 100) as f32;
|
||||||
|
let inside = |a: &Animal| a.x >= x0 && a.x <= x1 && a.y >= y0 && a.y <= y1;
|
||||||
|
for a in self.rabbits.iter_mut().chain(self.wolves.iter_mut()) {
|
||||||
|
if inside(a) {
|
||||||
|
a.energy = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("terrain") => {
|
||||||
|
if let (Some((x0, y0, x1, y1)), Some(kind)) = (Self::rect(&t, 1), num(5)) {
|
||||||
|
if (0..=6).contains(&kind) {
|
||||||
|
self.paint_terrain(x0, y0, x1, y1, kind as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("res") => {
|
||||||
|
if let (Some((x0, y0, x1, y1)), Some(kind), Some(amt)) =
|
||||||
|
(Self::rect(&t, 1), num(5), num(6))
|
||||||
|
{
|
||||||
|
if (0..=3).contains(&kind) {
|
||||||
|
let amt = amt.clamp(1, 50) as u16;
|
||||||
|
for y in y0..=y1 {
|
||||||
|
for x in x0..=x1 {
|
||||||
|
let i = y * W + x;
|
||||||
|
let free = self.walkable(x as i32, y as i32)
|
||||||
|
&& (x, y) != self.home
|
||||||
|
&& !self.houses.contains(&(x, y));
|
||||||
|
if kind == 0 {
|
||||||
|
self.res[i] = (0, 0);
|
||||||
|
} else if free {
|
||||||
|
self.res[i] = (kind as u8, amt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("spawn") => {
|
||||||
|
if let (Some(what), Some((x0, y0, x1, y1))) = (t.get(1), Self::rect(&t, 2)) {
|
||||||
|
let mut left = 25;
|
||||||
|
for y in y0..=y1 {
|
||||||
|
for x in x0..=x1 {
|
||||||
|
if left == 0 || !self.walkable(x as i32, y as i32) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
left -= 1;
|
||||||
|
let a = Animal {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
energy: 70.0,
|
||||||
|
dead: false,
|
||||||
|
};
|
||||||
|
match *what {
|
||||||
|
"person" => {
|
||||||
|
self.spawn_person();
|
||||||
|
let p = self.people.last_mut().unwrap();
|
||||||
|
(p.x, p.y) = (x, y);
|
||||||
|
}
|
||||||
|
"rabbit" => self.rabbits.push(a),
|
||||||
|
"wolf" => self.wolves.push(a),
|
||||||
|
_ => left += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("house") => {
|
||||||
|
if let Some((x0, y0, x1, y1)) = Self::rect(&t, 1) {
|
||||||
|
for y in y0..=y1 {
|
||||||
|
for x in x0..=x1 {
|
||||||
|
if self.houses.len() < 30
|
||||||
|
&& self.walkable(x as i32, y as i32)
|
||||||
|
&& self.res[y * W + x].0 == 0
|
||||||
|
&& (x, y) != self.home
|
||||||
|
&& !self.houses.contains(&(x, y))
|
||||||
|
{
|
||||||
|
self.houses.push((x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("remove") => {
|
||||||
|
if let (Some((x0, y0, x1, y1)), Some(what)) = (Self::rect(&t, 1), t.get(5)) {
|
||||||
|
let inside = |x: usize, y: usize| x >= x0 && x <= x1 && y >= y0 && y <= y1;
|
||||||
|
let all = *what == "all";
|
||||||
|
if all || *what == "people" {
|
||||||
|
self.people.retain(|p| !inside(p.x, p.y));
|
||||||
|
}
|
||||||
|
if all || *what == "rabbits" {
|
||||||
|
self.rabbits.retain(|a| !inside(a.x, a.y));
|
||||||
|
}
|
||||||
|
if all || *what == "wolves" {
|
||||||
|
self.wolves.retain(|a| !inside(a.x, a.y));
|
||||||
|
}
|
||||||
|
if all || *what == "houses" {
|
||||||
|
self.houses.retain(|h| !inside(h.0, h.1));
|
||||||
|
}
|
||||||
|
if all || *what == "res" {
|
||||||
|
for y in y0..=y1 {
|
||||||
|
for x in x0..=x1 {
|
||||||
|
self.res[y * W + x] = (0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint_terrain(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, kind: u8) {
|
||||||
|
let land = matches!(kind, SAND | GRASS | FOREST | HILLS);
|
||||||
|
for y in y0..=y1 {
|
||||||
|
for x in x0..=x1 {
|
||||||
|
if !land && (x, y) == self.home {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.tiles[y * W + x] = kind;
|
||||||
|
if !land {
|
||||||
|
self.res[y * W + x] = (0, 0);
|
||||||
|
self.houses.retain(|h| *h != (x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !land {
|
||||||
|
let ok = |tiles: &[u8], x: usize, y: usize| {
|
||||||
|
matches!(tiles[y * W + x], SAND | GRASS | FOREST | HILLS)
|
||||||
|
};
|
||||||
|
let tiles = self.tiles.clone();
|
||||||
|
self.rabbits.retain(|a| ok(&tiles, a.x, a.y));
|
||||||
|
self.wolves.retain(|a| ok(&tiles, a.x, a.y));
|
||||||
|
let home = self.home;
|
||||||
|
for p in self.people.iter_mut().filter(|p| !ok(&tiles, p.x, p.y)) {
|
||||||
|
(p.x, p.y) = home;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.terrain_rev += 1;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn terrain_json(&self) -> String {
|
pub fn terrain_json(&self) -> String {
|
||||||
let tiles: String = self.tiles.iter().map(|t| (b'0' + t) as char).collect();
|
let tiles: String = self.tiles.iter().map(|t| (b'0' + t) as char).collect();
|
||||||
format!("{{\"w\":{},\"h\":{},\"tiles\":\"{}\"}}", W, H, tiles)
|
format!("{{\"w\":{},\"h\":{},\"tiles\":\"{}\"}}", W, H, tiles)
|
||||||
|
|
@ -606,7 +901,10 @@ impl World {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
s,
|
s,
|
||||||
"{{\"tick\":{},\"day\":{},\"hour\":{:.2},\"daylight\":{:.3},\"stock\":{{\"food\":{},\"wood\":{},\"stone\":{},\"tools\":{}}},\"home\":[{},{}],",
|
"{{\"paused\":{},\"speed\":{},\"rev\":{},\"tick\":{},\"day\":{},\"hour\":{:.2},\"daylight\":{:.3},\"stock\":{{\"food\":{},\"wood\":{},\"stone\":{},\"tools\":{}}},\"home\":[{},{}],",
|
||||||
|
self.paused,
|
||||||
|
self.speed,
|
||||||
|
self.terrain_rev,
|
||||||
self.tick,
|
self.tick,
|
||||||
self.tick / DAY + 1,
|
self.tick / DAY + 1,
|
||||||
(self.tick % DAY) as f32 / DAY as f32 * 24.0,
|
(self.tick % DAY) as f32 / DAY as f32 * 24.0,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue