diff --git a/.github/workflows/build_milla.yml b/.github/workflows/build_rust.yml similarity index 86% rename from .github/workflows/build_milla.yml rename to .github/workflows/build_rust.yml index ca269a33dda18..1292c93dcb5b4 100644 --- a/.github/workflows/build_milla.yml +++ b/.github/workflows/build_rust.yml @@ -1,13 +1,13 @@ -name: Build MILLA +name: Build Rust library on: issue_comment: types: created jobs: - build-milla: + build-rust: if: | github.event.issue.pull_request && - (github.event.comment.body == '!build_milla') && + (github.event.comment.body == '!build_rust') && ((github.event.sender.id == github.event.issue.user.id) || (github.event.comment.author_association == 'COLLABORATOR') || (github.event.comment.author_association == 'MEMBER') || @@ -53,7 +53,7 @@ jobs: ref: ${{ env.PR_BRANCH }} token: ${{ env.GH_TOKEN }} - - name: Build MILLA + - name: Build rustlibs env: BASE_BRANCH: ${{ github.event.repository.default_branch }} BASE_REPOSITORY: ${{ github.repository }} @@ -65,7 +65,7 @@ jobs: git pull origin "$PR_BRANCH" --depth=$((ahead_by + 1)) git remote add upstream "https://github.com/$BASE_REPOSITORY.git" git fetch upstream "$BASE_BRANCH" --depth=$((behind_by + 1)) - cd milla + cd rust # Get dependencies. rustup target add i686-unknown-linux-gnu @@ -79,13 +79,13 @@ jobs: cargo build --release --target i686-pc-windows-gnu # Copy the built targets to their checked-in locations. - cp target/i686-unknown-linux-gnu/release/libmilla.so ../tools/ci/libmilla_ci.so - cp target/i686-pc-windows-gnu/release/milla.dll ../milla.dll + cp target/i686-unknown-linux-gnu/release/librustlibs.so ../tools/ci/librustlibs_ci.so + cp target/i686-pc-windows-gnu/release/rustlibs.dll ../rustlibs.dll - git commit -a -m "Build MILLA" --allow-empty + git commit -a -m "Build Rust library" --allow-empty git push origin - name: Notify Failure if: failure() && env.FAIL_NOTIFIED != 'true' run: | - gh pr comment ${{ github.event.issue.html_url }} -b 'Building MILLA failed, see the action run log for details: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' + gh pr comment ${{ github.event.issue.html_url }} -b 'Building Rust library failed, see the action run log for details: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' diff --git a/.gitignore b/.gitignore index e1a2a1e9325b5..5374441056573 100644 --- a/.gitignore +++ b/.gitignore @@ -73,7 +73,7 @@ $RECYCLE.BIN *.swp # Rust output. -/milla/target/* +/rust/target/* # mkdocs output. site diff --git a/code/__DEFINES/milla.dm b/code/__DEFINES/rust.dm similarity index 57% rename from code/__DEFINES/milla.dm rename to code/__DEFINES/rust.dm index bf5f2ddca0904..07242e955f503 100644 --- a/code/__DEFINES/milla.dm +++ b/code/__DEFINES/rust.dm @@ -1,59 +1,62 @@ -// milla.dm - DM API for milla extension library +// DM API for Rust extension modules +// Current modules: +// - MILLA, an asynchronous replacement for BYOND atmos +// - Mapmanip, a parse-time DMM file reader and modifier -// Default automatic MILLA detection. +// Default automatic library detection. // Look for it in the build location first, then in `.`, then in standard places. -/* This comment bypasses grep checks */ /var/__milla +/* This comment bypasses grep checks */ /var/__rustlib -/proc/__detect_milla() +/proc/__detect_rustlib() if(world.system_type == UNIX) #ifdef CIBUILDING - // CI override, use libmilla_ci.so if possible. - if(fexists("./tools/ci/libmilla_ci.so")) - return __milla = "tools/ci/libmilla_ci.so" + // CI override, use librustlibs_ci.so if possible. + if(fexists("./tools/ci/librustlibs_ci.so")) + return __rustlib = "tools/ci/librustlibs_ci.so" #endif // First check if it's built in the usual place. - if(fexists("./milla/target/i686-unknown-linux-gnu/release/libmilla.so")) - return __milla = "./milla/target/i686-unknown-linux-gnu/release/libmilla.so" + if(fexists("./rust/target/i686-unknown-linux-gnu/release/librustlibs.so")) + return __rustlib = "./rust/target/i686-unknown-linux-gnu/release/librustlibs.so" // Then check in the current directory. - if(fexists("./libmilla.so")) - return __milla = "./libmilla.so" + if(fexists("./librustlibs.so")) + return __rustlib = "./librustlibs.so" // And elsewhere. - return __milla = "libmilla.so" + return __rustlib = "librustlibs.so" else // First check if it's built in the usual place. - if(fexists("./milla/target/i686-pc-windows-msvc/release/milla.dll")) - return __milla = "./milla/target/i686-pc-windows-msvc/release/milla.dll" + if(fexists("./rust/target/i686-pc-windows-msvc/release/rustlibs.dll")) + return __rustlib = "./rust/target/i686-pc-windows-msvc/release/rustlibs.dll" // Then check in the current directory. - if(fexists("./milla.dll")) - return __milla = "./milla.dll" + if(fexists("./rustlibs.dll")) + return __rustlib = "./rustlibs.dll" // And elsewhere. - return __milla = "milla.dll" + return __rustlib = "rustlibs.dll" -#define MILLA (__milla || __detect_milla()) +#define RUSTLIB (__rustlib || __detect_rustlib()) -#define MILLA_CALL(func, args...) call_ext(MILLA, "byond:[#func]_ffi")(args) +#define RUSTLIB_CALL(func, args...) call_ext(RUSTLIB, "byond:[#func]_ffi")(args) /proc/milla_init_z(z) - return MILLA_CALL(initialize, z) + return RUSTLIB_CALL(milla_initialize, z) /proc/is_milla_synchronous(tick) - return MILLA_CALL(is_synchronous, tick) + return RUSTLIB_CALL(milla_is_synchronous, tick) /proc/set_tile_atmos(turf/T, airtight_north, airtight_east, airtight_south, airtight_west, atmos_mode, environment_id, oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature, innate_heat_capacity) - return MILLA_CALL(set_tile, T, airtight_north, airtight_east, airtight_south, airtight_west, atmos_mode, environment_id, oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature, innate_heat_capacity) + return RUSTLIB_CALL(milla_set_tile, T, airtight_north, airtight_east, airtight_south, airtight_west, atmos_mode, environment_id, oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature, innate_heat_capacity) /proc/get_tile_atmos(turf/T, list/L) - return MILLA_CALL(get_tile, T, L) + return RUSTLIB_CALL(milla_get_tile, T, L) /proc/spawn_milla_tick_thread() - return MILLA_CALL(spawn_tick_thread) + return RUSTLIB_CALL(milla_spawn_tick_thread) /proc/get_milla_tick_time() - return MILLA_CALL(get_tick_time) + return RUSTLIB_CALL(milla_get_tick_time) /proc/get_interesting_atmos_tiles() - return MILLA_CALL(get_interesting_tiles) + return RUSTLIB_CALL(milla_get_interesting_tiles) /proc/reduce_superconductivity(turf/T, list/superconductivity) var/north = superconductivity[1] @@ -61,10 +64,10 @@ var/south = superconductivity[3] var/west = superconductivity[4] - return MILLA_CALL(reduce_superconductivity, T, north, east, south, west) + return RUSTLIB_CALL(milla_reduce_superconductivity, T, north, east, south, west) /proc/reset_superconductivity(turf/T) - return MILLA_CALL(reset_superconductivity, T) + return RUSTLIB_CALL(milla_reset_superconductivity, T) /proc/set_tile_airtight(turf/T, list/airtight) var/north = airtight[1] @@ -72,16 +75,19 @@ var/south = airtight[3] var/west = airtight[4] - return MILLA_CALL(set_tile_airtight, T, north, east, south, west) + return RUSTLIB_CALL(milla_set_tile_airtight, T, north, east, south, west) /proc/get_random_interesting_tile() - return MILLA_CALL(get_random_interesting_tile) + return RUSTLIB_CALL(milla_get_random_interesting_tile) /proc/create_environment(oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature) - return MILLA_CALL(create_environment, oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature) + return RUSTLIB_CALL(milla_create_environment, oxygen, carbon_dioxide, nitrogen, toxins, sleeping_agent, agent_b, temperature) -#undef MILLA -#undef MILLA_CALL +/proc/mapmanip_read_dmm(mapname) + return RUSTLIB_CALL(mapmanip_read_dmm_file, mapname) + +#undef RUSTLIB +#undef RUSTLIB_CALL // Indexes for Tiles and InterestingTiles // Must match the order in milla/src/model.rs diff --git a/code/game/objects/effects/map_effects/mapmanip.dm b/code/game/objects/effects/map_effects/mapmanip.dm new file mode 100644 index 0000000000000..d0be4103dd90b --- /dev/null +++ b/code/game/objects/effects/map_effects/mapmanip.dm @@ -0,0 +1,26 @@ +/obj/effect/map_effect/marker/mapmanip + name = "mapmanip marker" + layer = POINT_LAYER + +/obj/effect/map_effect/marker/mapmanip/Initialize(mapload) + . = ..() + qdel(src) + +/obj/effect/map_effect/marker/mapmanip/submap/extract + name = "mapmanip marker, extract submap" + icon = 'icons/effects/map_effects_96x96.dmi' + icon_state = "mapmanip_extract" + pixel_x = -32 + pixel_y = -32 + +/obj/effect/map_effect/marker/mapmanip/submap/insert + name = "mapmanip marker, insert submap" + icon = 'icons/effects/map_effects_96x96.dmi' + icon_state = "mapmanip_insert" + pixel_x = -32 + pixel_y = -32 + +/obj/effect/map_effect/marker_helper/mapmanip/submap/edge + name = "mapmanip helper marker, edge of submap" + icon = 'icons/effects/mapping_helpers.dmi' + icon_state = "mapmanip_submap_edge" diff --git a/code/modules/awaymissions/maploader/reader.dm b/code/modules/awaymissions/maploader/reader.dm index b4a654396e735..198621034bf46 100644 --- a/code/modules/awaymissions/maploader/reader.dm +++ b/code/modules/awaymissions/maploader/reader.dm @@ -35,7 +35,15 @@ GLOBAL_DATUM_INIT(_preloader, /datum/dmm_suite/preloader, new()) if(lastchar == "/" || lastchar == "\\") log_debug("Attempted to load map template without filename (Attempted [tfile])") return - tfile = wrap_file2text(tfile) + + // use rustlib to read, parse, process, mapmanip etc + // this will "crash"/stacktrace on fail + tfile = mapmanip_read_dmm(fname) + // if rustlib for whatever reason fails and returns null + // try to load it the old dm way instead + if(!tfile) + tfile = wrap_file2text(fname) + if(!length(tfile)) throw EXCEPTION("Map path '[fname]' does not exist!") diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 9a316f1a34922..db5fd2d0c0f53 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -133,26 +133,35 @@ Status of your pull request will be communicated via PR labels. This includes: All PRs which modify maps are expected to follow all of our [mapping requirements](./mapping/requirements.md). -## Modifying MILLA +## Modifying Rust Code -Our atmos engine, MILLA, is in the `milla/` directory. It's written in Rust for -performance reasons, which means it's not compiled the same way as the rest of -the code. If you're on Windows, you get a pre-built copy by default. If you're -on Linux, you built one already to run the server. +Some parts of Paradise are written in [Rust][] for performance or reliability +reasons: -If you make changes to MILLA, you'll want to rebuild. This will be very similar -to RUSTG: https://github.com/ParadiseSS13/rust-g The only difference is that you -run `cargo` from the `milla/` directory, and don't need to specify -`--all-features` (though it doesn't hurt). +- Our atmos engine, MILLA, is in the `rust/src/milla/` directory. +- The `mapmanip` library, an Aurora Station module used for automating DMM + modification, is in the `rust/src/mapmanip` library. + +The Rust parts of our codebase are compiled into a single library, +separate from the rest of the code. If you're on Windows, you get a pre-built +copy by default. If you're on Linux, you built one already to run the server. + +If you make changes to the Rust library, you'll want to rebuild. This will be +very similar to [rust-g][]. The only difference is that you run `cargo` from the +`rust/` directory, and don't need to specify `--all-features` (though it doesn't +hurt). The server will automatically detect that you have a local build, and use that over the default Windows one. -When you're ready to make a PR, please DO NOT modify `milla.dll` or -`tools/ci/libmilla_ci.so`. Leave "Allow edits and access to secrets by -maintainers" enabled, and post a comment on your PR saying `!build_milla`. A bot +When you're ready to make a PR, please DO NOT modify `rustlibs.dll` or +`tools/ci/librustlibs_ci.so`. Leave "Allow edits and access to secrets by +maintainers" enabled, and post a comment on your PR saying `!build_rust`. A bot will automatically build them for you and update your branch. +[Rust]: https://www.rust-lang.org/ +[rust-g]: https://github.com/ParadiseSS13/rust-g + ## Other Notes - Bloated code may be necessary to add a certain feature, which means there has diff --git a/docs/mapping/images/mapmanip_contents.png b/docs/mapping/images/mapmanip_contents.png new file mode 100644 index 0000000000000..1db7e6b998a9d Binary files /dev/null and b/docs/mapping/images/mapmanip_contents.png differ diff --git a/docs/mapping/images/mapmanip_error.png b/docs/mapping/images/mapmanip_error.png new file mode 100644 index 0000000000000..7b7a7608f4ca4 Binary files /dev/null and b/docs/mapping/images/mapmanip_error.png differ diff --git a/docs/mapping/images/mapmanip_inplace.png b/docs/mapping/images/mapmanip_inplace.png new file mode 100644 index 0000000000000..bdc7eb4d5e5c1 Binary files /dev/null and b/docs/mapping/images/mapmanip_inplace.png differ diff --git a/docs/mapping/images/mapmanip_markers.png b/docs/mapping/images/mapmanip_markers.png new file mode 100644 index 0000000000000..d1d732b219ccd Binary files /dev/null and b/docs/mapping/images/mapmanip_markers.png differ diff --git a/docs/mapping/images/mapmanip_noops1.png b/docs/mapping/images/mapmanip_noops1.png new file mode 100644 index 0000000000000..229c744fed932 Binary files /dev/null and b/docs/mapping/images/mapmanip_noops1.png differ diff --git a/docs/mapping/images/mapmanip_noops2.png b/docs/mapping/images/mapmanip_noops2.png new file mode 100644 index 0000000000000..73dfe22290fd3 Binary files /dev/null and b/docs/mapping/images/mapmanip_noops2.png differ diff --git a/docs/mapping/images/mapmanip_noops3.png b/docs/mapping/images/mapmanip_noops3.png new file mode 100644 index 0000000000000..f858899c87954 Binary files /dev/null and b/docs/mapping/images/mapmanip_noops3.png differ diff --git a/docs/mapping/images/mapmanip_noops4.png b/docs/mapping/images/mapmanip_noops4.png new file mode 100644 index 0000000000000..7556a52eeb7aa Binary files /dev/null and b/docs/mapping/images/mapmanip_noops4.png differ diff --git a/docs/mapping/images/mapmanip_results.png b/docs/mapping/images/mapmanip_results.png new file mode 100644 index 0000000000000..8279a034da6e0 Binary files /dev/null and b/docs/mapping/images/mapmanip_results.png differ diff --git a/docs/mapping/submaps.md b/docs/mapping/submaps.md new file mode 100644 index 0000000000000..97168a1b0d86f --- /dev/null +++ b/docs/mapping/submaps.md @@ -0,0 +1,181 @@ +# Guide to Submaps + +> [!NOTE] +> +> This guide was originally written by developer Dreamix for Aurora Station, the +> original codebase the `mapmanip` library was written in. Despite the difference +> in appearance of the screenshots, the guide is still applicable. + +Submaps are a mechanism to allow copy-pasting parts of maps (submaps) into other +maps, implemented with the `mapmanip` library. `mapmanip` is a library for map +manipulation, and happens before the map is actually loaded by the server. This +is implemented using Rust, instead of DM code. Map manipulations are configured +and defined by adding a `.jsonc` file corresponding to a `.dmm` map. Currently, +the only available map manipulation is submap extraction/insertion. + +## But Why + +This allows for adding variations to maps, beyond just using `/obj/random/...` +items. Variations of bigger parts of maps, like, for example, whole rooms. Or +even whole layouts, making it so a map looks different every time it is visited. + +## How To Do It + +1. Firstly, define two markers for each submap. The types need to be unique, and + the types are used later to define where to extract the submap from, and where + to insert it. Here, we want to add some variation to the small warehouse storage + room. + + ```dm + /obj/effect/map_effect/marker/mapmanip/submap/extract/station/boxstation/warehouse_small_storage + name = "Boxstation, Cargo Warehouse Small Storage" + + /obj/effect/map_effect/marker/mapmanip/submap/insert/station/boxstation/warehouse_small_storage + name = "Boxstation, Cargo Warehouse Small Storage" + ``` + +2. Figure out the area that the submap will be inserted into on the map, and add + the insert marker there. In this case the map is + `maps/stations/boxstation.dmm`, and the size of that area is 3x5 + tiles, being the little side warehouse. Here, we want the submaps to only be the + "cargo" or "contents" of the warehouse. So this room is already fully + functional, with lights, cameras, pipes, etc. Do note that the "submap edge" + helper markers are just visual helpers, and they have no function. + + ![](./images/mapmanip_inplace.png) + +3. Create a new map file that will contain the submap variants to extract from. + In this case it is `maps/stations/submaps/warehouse_small_storage.dmm`. + Add all the possible variants here, each having the same extract marker. They + must all be the chosen size, in this case being 3x5 tiles. We want the submaps + to only contain "cargo" or "contents" of the warehouse, so both the turf and + area are set to "noop". How are "noop" areas and turfs handled, see the next + part of this guide. + + ![](./images/mapmanip_contents.png) + +4. Create the mapmanip configuration file, that will actually define the map + manipulation, and make it happen. In this case it is + `maps/stations/boxstation.jsonc`. Do notice how the path and + name of this config file is exactly the same as of the map + `.../boxstation.dmm`, just with a different extension. Each + submap manipulation has these vars defined: + + - `type` - Map manipulation type. For submap manipulations it is "SubmapExtractInsert". + - `submap_size_...` - Size of the submap, width and height. + - `submaps_dmm` - This is the relative path to the DMM file containing the + submaps. Relative, meaning to the `.dmm` that is being loaded. In this + case, it is put in a separate folder for better organization, but it could + be in the same folder as the `.dmm` file that is being manipulated. + - `marker_...` - Typepaths to the insert and extract markers, as defined in + the first step of this guide, and as added into the maps in step two and + three. + - `submaps_can_repeat` - Decides whether submaps can repeat when inserting + them into different places on the map. If there are more extract markers + than insert markers, it is safe to set it to `true`. Otherwise it should + be `false`, or else there may not be enough submaps to insert, and map + manipulation will fail. + + ```json + [ + { + // Boxstation warehouse, intended for randomized items + "type": "SubmapExtractInsert", + "submap_size_x": 3, + "submap_size_y": 5, + "submaps_dmm": "stations/submaps/warehouse_small_storage.dmm", + "marker_extract": "/obj/effect/map_effect/marker/mapmanip/submap/extract/station/boxstation/warehouse_small_storage", + "marker_insert": "/obj/effect/map_effect/marker/mapmanip/submap/insert/station/boxstation/warehouse_small_storage", + "submaps_can_repeat": true // doesn't matter, as there's only one insert marker + } + ] + ``` + +5. Run the server locally and observe the results. + + ![](./images/mapmanip_results.png) + +## Areas and Turfs + +Areas and turfs, and specifically their "noop" types, have special meaning in +submap manipulation. They determine whether the submap manipulation is more like +"replacing" or "appending". The "noop" (short for "no-operation") types for +turfs is `/turf/template_noop`, and for areas it is `/area/template_noop`. + +If neither area nor turf are noop: submap tile atoms replace map tile atoms +entirely, including turf and area. This could be used for submaps that are fully +functional rooms, like a medbay submap and a brig submap, with their own areas +defined in their submaps. + +![](./images/mapmanip_noops1.png) + +If both area and turf are noop: All atoms on a submap tile are inserted/appended +into the map tile, with the exception for area and turf. This can be used for +places like a warehouse, where only the actual cargo is in submaps, and on the +map the warehouse is fully functional and implemented (just empty). + +![](./images/mapmanip_noops2.png) + +If only turf is noop, while area is non-noop: All atoms are inserted/appended +into the map tile, but the resulting area used is of the submap tile. +Admittedly, I cannot think of a good use case for this specific feature, but I +am including it here for completeness. + +![](./images/mapmanip_noops3.png) + +If only area is noop, while turf is non-noop: Submap tile atoms replace map tile +atoms entirely, with the exception of area - resulting area is of the map, not +the submap. This could be used to have different warehouses that use the same +submaps, in different parts of a station or outpost, and so using different +areas. + +![](./images/mapmanip_noops4.png) + +## Errors and Debugging + +If a map manipulation fails, it should do both these things: + +- Return `null`, and in that case the map is loaded without any map + manipulations, as a fallback. +- Generate a stack trace, that can be caught. To do so, must run with debugging, + and have breakpoints on runtime errors. It should show a stack trace like + below, with information about what happened. + + ![](./images/mapmanip_error.png) + +## Other Notes + +- The `.jsonc` config file is using the JSONC format, which is a bit strict to + how it should look, and especially cares about commas. JSONC is "JSON with + comments". Exactly the same as JSON, with the only difference being that it + allows `// comments`. + +- There may be multiple different submap insert/extract manipulations defined + for one map. Every submap manipulation is going to require its own set of + markers. In that case the `.jsonc` would look roughly like this. + + ![](./images/mapmanip_markers.png) + +- If a map has multiple submap operations defined, they can be all in the same + submap dmm file, or can be in different ones. They can also be in the same + folder as the main map, or in some subfolder. Use whatever is best for + organization. + +- Submaps can be "recursive", where a submap may contain a insert marker for + another submap. The contained submaps won't be automatically inserted, + however, and they still require their own manipulations in the config. + +## Possible Uses + +- Warehouse with different cargo every round. One round it could be empty, + another it could have a few crates with guns and armor, another time it could + be full of building materials. +- Armory with a few different possible loadouts. One with lots of weak SMGs and + strong armor, another variant with just a few strong rifles and weak armor. +- Big crew quarters with 20 rooms, but randomly filled with submaps with only 5 variants. +- Hallway on a planetary outpost with one variant where it is fine and "normal", + and another variant where it is blocked by some rocks, forcing people to find + another way through. +- Alternative possible layouts for the whole map, where rooms and departments + are in different places every round. +- The sky is the limit, literally. diff --git a/icons/effects/map_effects_96x96.dmi b/icons/effects/map_effects_96x96.dmi new file mode 100644 index 0000000000000..fa0c418f37408 Binary files /dev/null and b/icons/effects/map_effects_96x96.dmi differ diff --git a/icons/effects/mapping_helpers.dmi b/icons/effects/mapping_helpers.dmi index d22a0eb914c3f..4fcf4dbf0a8e4 100644 Binary files a/icons/effects/mapping_helpers.dmi and b/icons/effects/mapping_helpers.dmi differ diff --git a/milla.dll b/milla.dll deleted file mode 100755 index 7fe9470ef3dda..0000000000000 Binary files a/milla.dll and /dev/null differ diff --git a/milla/Cargo.toml b/milla/Cargo.toml deleted file mode 100644 index 268df469456a1..0000000000000 --- a/milla/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "milla" -version = "1.0.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib"] - -[dependencies] -atomic_float = "1.0.0" -bitflags = "2.5.0" -byondapi = { git = "https://github.com/spacestation13/byondapi-rs.git", version = "0.4.7" } -eyre = "0.6.12" -rand = { version = "0.8.5", features = ["small_rng"] } -scc = "2.1.1" -thread-priority = "1.1.0" diff --git a/milla/src/logging.rs b/milla/src/logging.rs deleted file mode 100644 index 9a5d642910492..0000000000000 --- a/milla/src/logging.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::backtrace::Backtrace; -use std::fs::File; -use std::io::Write; - -/// Simple logging function, appends to ./milla_log.txt -#[allow(dead_code)] -pub(crate) fn write_log>(x: T) { - let mut f = File::options() - .create(true) - .append(true) - .open("./milla_log.txt") - .unwrap(); - writeln!(&mut f, "{}", String::from_utf8_lossy(x.as_ref())).unwrap(); -} - -/// Panic handler that dumps info out to ./milla_panic.txt (overwriting) if we crash. -pub(crate) fn setup_panic_handler() { - std::panic::set_hook(Box::new(|info| { - let backtrace = Backtrace::force_capture(); - std::fs::write( - "./milla_panic.txt", - format!("Panic {:#?}\n{:#?}", info, backtrace), - ) - .unwrap(); - })) -} diff --git a/mkdocs.yml b/mkdocs.yml index 6545bbd06e0d0..e3533b9ca27e4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -86,6 +86,7 @@ nav: - 'Mapping Quickstart': './mapping/quickstart.md' - 'Mapping Requirements': './mapping/requirements.md' - 'Design Guidelines': './mapping/design.md' + - 'Guide to Submaps': './mapping/submaps.md' - 'References': - 'Glossary': './references/glossary.md' diff --git a/paradise.dme b/paradise.dme index 83b948a60f8bd..07772ff3aeb2d 100644 --- a/paradise.dme +++ b/paradise.dme @@ -89,7 +89,6 @@ #include "code\__DEFINES\mecha_defines.dm" #include "code\__DEFINES\mecha_hides.dm" #include "code\__DEFINES\medal.dm" -#include "code\__DEFINES\milla.dm" #include "code\__DEFINES\misc_defines.dm" #include "code\__DEFINES\mob_defines.dm" #include "code\__DEFINES\mod.dm" @@ -111,6 +110,7 @@ #include "code\__DEFINES\revolution_defines.dm" #include "code\__DEFINES\role_preferences.dm" #include "code\__DEFINES\rolebans.dm" +#include "code\__DEFINES\rust.dm" #include "code\__DEFINES\rust_g.dm" #include "code\__DEFINES\shuttle_defines.dm" #include "code\__DEFINES\sight.dm" @@ -1019,6 +1019,7 @@ #include "code\game\objects\effects\effect_system\effects_smoke.dm" #include "code\game\objects\effects\effect_system\effects_sparks.dm" #include "code\game\objects\effects\effect_system\effects_water.dm" +#include "code\game\objects\effects\map_effects\mapmanip.dm" #include "code\game\objects\effects\spawners\airlock_spawner.dm" #include "code\game\objects\effects\spawners\bombspawner.dm" #include "code\game\objects\effects\spawners\decorative_spawners.dm" diff --git a/milla/Cargo.lock b/rust/Cargo.lock similarity index 57% rename from milla/Cargo.lock rename to rust/Cargo.lock index eee0b6908eb62..a3c486a8d9492 100644 --- a/milla/Cargo.lock +++ b/rust/Cargo.lock @@ -2,6 +2,31 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -17,16 +42,50 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c4b08ed8a30ff7320117c190eb4d73d47f0ac0c930ab853b8224cef7cd9a5e7" +[[package]] +name = "attribute-derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c124f12ade4e670107b132722d0ad1a5c9790bcbc1b265336369ea05626b4498" +dependencies = [ + "attribute-derive-macro", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b217a07446e0fb086f83401a98297e2d81492122f5874db5391bd270a185f88" +dependencies = [ + "collection_literals", + "interpolator", + "proc-macro-error", + "proc-macro-utils", + "proc-macro2", + "quote", + "quote-use", + "syn 2.0.60", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "bindgen" version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags", + "bitflags 2.5.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -36,16 +95,38 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.60", "which", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "builtins-proc-macro" +version = "0.0.0" +source = "git+https://github.com/SpaceManiac/SpacemanDMM?rev=6c5a751516ae0e8add4b2aa4388a1e84e96e7082#6c5a751516ae0e8add4b2aa4388a1e84e96e7082" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byondapi" version = "0.4.7" @@ -67,7 +148,7 @@ source = "git+https://github.com/spacestation13/byondapi-rs.git#aa4addd95c33b666 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.60", ] [[package]] @@ -82,6 +163,32 @@ dependencies = [ "walkdir", ] +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cexpr" version = "0.6.0" @@ -108,6 +215,72 @@ dependencies = [ "libloading", ] +[[package]] +name = "collection_literals" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" + +[[package]] +name = "color_space" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52fdfaf2bee6357023bf7f95b15a8ef0b82759d2bce705cc45efcae9ae10f0ff" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "dmm-tools" +version = "0.1.0" +source = "git+https://github.com/SpaceManiac/SpacemanDMM?rev=6c5a751516ae0e8add4b2aa4388a1e84e96e7082#6c5a751516ae0e8add4b2aa4388a1e84e96e7082" +dependencies = [ + "ahash", + "bumpalo", + "bytemuck", + "dreammaker", + "either", + "indexmap 1.9.3", + "inflate", + "lodepng", + "ndarray", + "rand", +] + [[package]] name = "doxygen-rs" version = "0.4.2" @@ -117,6 +290,29 @@ dependencies = [ "phf", ] +[[package]] +name = "dreammaker" +version = "0.1.0" +source = "git+https://github.com/SpaceManiac/SpacemanDMM?rev=6c5a751516ae0e8add4b2aa4388a1e84e96e7082#6c5a751516ae0e8add4b2aa4388a1e84e96e7082" +dependencies = [ + "ahash", + "bitflags 1.3.2", + "builtins-proc-macro", + "color_space", + "derivative", + "get-size", + "get-size-derive", + "indexmap 1.9.3", + "interval-tree", + "lodepng", + "ordered-float", + "phf", + "serde", + "serde_derive", + "termcolor", + "toml", +] + [[package]] name = "either" version = "1.11.0" @@ -149,6 +345,42 @@ dependencies = [ "once_cell", ] +[[package]] +name = "flate2" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "get-size" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b61e2dab7eedce93a83ab3468b919873ff16bac5a3e704011ff836d22b2120" + +[[package]] +name = "get-size-derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a1bcfb855c1f340d5913ab542e36f25a1c56f57de79022928297632435dec2" +dependencies = [ + "attribute-derive", + "quote", + "syn 2.0.60", +] + [[package]] name = "getrandom" version = "0.2.14" @@ -166,6 +398,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -187,6 +425,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -194,15 +442,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] +[[package]] +name = "inflate" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" +dependencies = [ + "adler32", +] + +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "interval-tree" +version = "0.8.0" +source = "git+https://github.com/SpaceManiac/SpacemanDMM?rev=6c5a751516ae0e8add4b2aa4388a1e84e96e7082#6c5a751516ae0e8add4b2aa4388a1e84e96e7082" + [[package]] name = "inventory" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -212,6 +489,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "lazy_static" version = "1.4.0" @@ -246,12 +529,34 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "lodepng" +version = "3.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2dea7cda68e381418c985fd8f32a9c279a21ae8c715f2376adb20c27a0fad3" +dependencies = [ + "crc32fast", + "flate2", + "libc", + "rgb", +] + [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.2" @@ -259,23 +564,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] -name = "milla" -version = "1.0.0" +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "atomic_float", - "bitflags", - "byondapi", - "eyre", - "rand", - "scc", - "thread-priority", + "adler2", ] [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "ndarray" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] [[package]] name = "nom" @@ -287,6 +601,33 @@ dependencies = [ "minimal-lexical", ] +[[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.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "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 = "num_enum" version = "0.7.2" @@ -305,7 +646,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.60", ] [[package]] @@ -314,6 +655,15 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + [[package]] name = "phf" version = "0.11.2" @@ -344,7 +694,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.60", ] [[package]] @@ -369,7 +719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.60", ] [[package]] @@ -381,6 +731,41 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + [[package]] name = "proc-macro2" version = "1.0.81" @@ -399,6 +784,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quote-use" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b5abe3fe82fdeeb93f44d66a7b444dedf2e4827defb0a8e69c437b2de2ef94" +dependencies = [ + "quote", + "quote-use-macros", + "syn 2.0.60", +] + +[[package]] +name = "quote-use-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ea44c7e20f16017a76a245bb42188517e13d16dcb1aa18044bc406cdc3f4af" +dependencies = [ + "derive-where", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "rand" version = "0.8.5" @@ -429,11 +837,17 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "regex" -version = "1.10.4" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -458,6 +872,15 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -479,19 +902,46 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", "windows-sys", ] +[[package]] +name = "rustlibs" +version = "1.0.0" +dependencies = [ + "atomic_float", + "bitflags 2.5.0", + "byondapi", + "diff", + "dmm-tools", + "eyre", + "fxhash", + "itertools 0.10.5", + "rand", + "regex", + "scc", + "serde", + "serde_json", + "thread-priority", + "walkdir", +] + [[package]] name = "rustversion" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "same-file" version = "1.0.6" @@ -522,6 +972,38 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "1.3.0" @@ -534,6 +1016,23 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.60" @@ -545,13 +1044,22 @@ dependencies = [ "unicode-ident", ] +[[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-priority" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d3b04d33c9633b8662b167b847c7ab521f83d1ae20f2321b65b5b925e532e36" dependencies = [ - "bitflags", + "bitflags 2.5.0", "cfg-if", "libc", "log", @@ -559,6 +1067,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.6.5" @@ -571,7 +1088,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -582,6 +1099,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -722,3 +1245,23 @@ checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000000000..06e948f3bec0a --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "rustlibs" +version = "1.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] + +[dependencies] +atomic_float = "1.0.0" +bitflags = "2.5.0" +byondapi = { git = "https://github.com/spacestation13/byondapi-rs.git", version = "0.4.7" } +eyre = "0.6.12" +rand = { version = "0.8.5", features = ["small_rng"] } +scc = "2.1.1" +thread-priority = "1.1.0" + +# spacemandmm, also used by strongmandmm +dmmtools = { git = "https://github.com/SpaceManiac/SpacemanDMM", rev = "6c5a751516ae0e8add4b2aa4388a1e84e96e7082", package = "dmm-tools" } +# diffs between two strings/texts/files, used in tests +diff = "0.1" +# general utility lib for iterator operations +itertools = "0.10.5" +# fast hashmap +fxhash = "0.2.1" +# interface for serialization +serde = { version = "1.0", features = ["derive"] } +# json implementation for serde +serde_json = { version = "1.0" } +# utility function for walking through a dir recursively +walkdir = "2.5.0" +# regex +regex = "1.10.5" diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000000000..66ed4b0b1bbb4 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,3 @@ +mod logging; +mod mapmanip; +mod milla; diff --git a/rust/src/logging/mod.rs b/rust/src/logging/mod.rs new file mode 100644 index 0000000000000..fc5312ae5941b --- /dev/null +++ b/rust/src/logging/mod.rs @@ -0,0 +1,24 @@ +/// Call stack trace dm method with message. +pub(crate) fn dm_call_stack_trace(msg: String) { + let msg = byondapi::value::ByondValue::try_from(msg).unwrap(); + // this is really ugly, cause we want to get id/ref to a proc name string + // that is already allocated, and don't want to allocate a new string entirely + byondapi::global_call::call_global_id( + { + static STRING_ID: std::sync::OnceLock = std::sync::OnceLock::new(); + *STRING_ID.get_or_init(|| byondapi::byond_string::str_id_of("_stack_trace").unwrap()) + }, + &[msg], + ) + .unwrap(); +} + +/// Panic handler, called on unhandled errors. +/// Writes panic info to a text file, and calls dm stack trace proc as well. +pub(crate) fn setup_panic_handler() { + std::panic::set_hook(Box::new(|info| { + let msg = format!("Panic \n {:#?}", info); + let _ = std::fs::write("./rustlibs_panic.txt", msg.clone()); + dm_call_stack_trace(msg); + })) +} diff --git a/rust/src/mapmanip/.gitignore b/rust/src/mapmanip/.gitignore new file mode 100644 index 0000000000000..99912a8671488 --- /dev/null +++ b/rust/src/mapmanip/.gitignore @@ -0,0 +1 @@ +test-out/* diff --git a/rust/src/mapmanip/core/map_to_string.rs b/rust/src/mapmanip/core/map_to_string.rs new file mode 100644 index 0000000000000..f931d97ac206a --- /dev/null +++ b/rust/src/mapmanip/core/map_to_string.rs @@ -0,0 +1,7 @@ +/// Turns spacemandmm map object to string. +pub fn map_to_string(map: &dmmtools::dmm::Map) -> eyre::Result { + let mut v = vec![]; + map.to_writer(&mut v)?; + let s = String::from_utf8(v)?; + Ok(s) +} diff --git a/rust/src/mapmanip/core/mod.rs b/rust/src/mapmanip/core/mod.rs new file mode 100644 index 0000000000000..7aa0dd994c7b6 --- /dev/null +++ b/rust/src/mapmanip/core/mod.rs @@ -0,0 +1,147 @@ +pub mod to_dict_map; +pub use to_dict_map::to_dict_map; +pub mod to_grid_map; +pub use to_grid_map::to_grid_map; + +pub mod map_to_string; +pub use map_to_string::map_to_string; + +use dmmtools::dmm; +use dmmtools::dmm::Coord3; + +/// A representation of a single tile on a map that may or may not be mapped to an existing key. +#[derive(Clone, Debug, Default)] +pub struct Tile { + /// A key temporarily assigned to the tile until an appropriate new/existing + /// key can be assigned to it, or if it is already being used by a different + /// prefab list in the file. + pub key_suggestion: dmm::Key, + /// The prefabs on the tile. + pub prefabs: Vec, +} + +impl Tile { + /// Return the first /area that exists in the prefab. + pub fn get_area(&self) -> Option<&dmm::Prefab> { + self.prefabs + .iter() + .find(|prefab| prefab.path.starts_with("/area/")) + } + + /// Remove and return the first /area that exists in the prefab. + pub fn remove_area(&mut self) -> Option { + let area = self.get_area().cloned(); + if area.is_some() { + self.prefabs + .retain(|prefab| !prefab.path.starts_with("/area/")) + } + area + } + + /// Return the first /turf that exists in the prefab. + pub fn get_turf(&self) -> Option<&dmm::Prefab> { + self.prefabs + .iter() + .find(|prefab| prefab.path.starts_with("/turf/")) + } + + /// Remove and return the first /turf that exists in the prefab. + pub fn remove_turf(&mut self) -> Option { + let turf = self.get_turf().cloned(); + if turf.is_some() { + self.prefabs + .retain(|prefab| !prefab.path.starts_with("/turf/")) + } + turf + } +} + +/// Thin abstraction over a flat vec, to provide a simple hashmap-like interface, +/// and to translate between 3D dmm coords (start at 1), and 1D flat vec coords (start at 0). +/// The translation is so that it looks better in logs/errors/etc, +/// where shown coords would correspond to coords seen in game or in strongdmm. +#[derive(Clone, Debug)] +pub struct TileGrid { + pub size: Coord3, + pub grid: Vec, +} + +impl TileGrid { + pub fn new(size_x: i32, size_y: i32, size_z: i32) -> TileGrid { + Self { + size: Coord3::new(size_x, size_y, size_z), + grid: vec![Tile::default(); (size_x * size_y * size_z) as usize], + } + } + + pub fn len(&self) -> usize { + self.grid.len() + } + + fn coord_to_index(&self, coord: &Coord3) -> usize { + let coord = Coord3::new(coord.x - 1, coord.y - 1, coord.z - 1); + ((coord.x) + (coord.y * self.size.x) + (coord.z * self.size.x * self.size.y)) as usize + } + + fn index_to_coord(&self, index: usize) -> Coord3 { + let index = index as i32; + Coord3::new( + (index % self.size.x) + 1, + ((index / self.size.x) % self.size.y) + 1, + (index / (self.size.x * self.size.y)) + 1, + ) + } + + pub fn get_mut(&mut self, coord: &Coord3) -> Option<&mut Tile> { + let index = self.coord_to_index(coord); + self.grid.get_mut(index) + } + + pub fn get(&self, coord: &Coord3) -> Option<&Tile> { + self.grid.get(self.coord_to_index(coord)) + } + + pub fn insert(&mut self, coord: &Coord3, tile: Tile) { + *self.get_mut(coord).unwrap() = tile; + } + + pub fn iter(&self) -> impl Iterator { + self.grid + .iter() + .enumerate() + .map(|(i, t)| (self.index_to_coord(i), t)) + } + + pub fn keys(&self) -> impl Iterator + '_ { + self.grid + .iter() + .enumerate() + .map(|(i, _t)| self.index_to_coord(i)) + } + + pub fn values(&self) -> impl Iterator { + self.grid.iter() + } + + pub fn values_mut(&mut self) -> impl Iterator { + self.grid.iter_mut() + } +} + +/// This is analogous to `dmmtools::dmm::Map`, but instead of being structured like dmm maps are, +/// where they have a dictionary of keys-to-prefabs and a separate grid of keys, +/// this is only a direct coord-to-prefab grid. +/// It is not memory efficient, but it allows for much greater flexibility of manipulation. +#[derive(Clone, Debug)] +pub struct GridMap { + /// + pub size: dmm::Coord3, + /// + pub grid: crate::mapmanip::core::TileGrid, +} + +impl GridMap { + pub fn from_file(path: &std::path::Path) -> Option { + Some(to_grid_map(&dmm::Map::from_file(&path).ok()?)) + } +} diff --git a/rust/src/mapmanip/core/to_dict_map.rs b/rust/src/mapmanip/core/to_dict_map.rs new file mode 100644 index 0000000000000..6920e6ab6db65 --- /dev/null +++ b/rust/src/mapmanip/core/to_dict_map.rs @@ -0,0 +1,71 @@ +use crate::mapmanip::GridMap; +use dmmtools::dmm::{self, Coord3}; +use eyre::ContextCompat; +use fxhash::FxHashMap; +use std::collections::BTreeSet; + +fn coord3_to_index(coord: dmm::Coord3, size: dmm::Coord3) -> (usize, usize, usize) { + ( + coord.z as usize - 1, + (size.y - coord.y) as usize, + coord.x as usize - 1, + ) +} + +fn int_to_key(i: u16) -> dmm::Key { + // Unsafe is used here to convert basic int to key type, as `dmm::Key` interior var is private. + // This is "safe", as key will always be `u16` to maintain compability with the `dmm` format. + // Could be at some point be made safe when the key type is made public in the `dmm_tools` crate. + unsafe { std::mem::transmute::(i) } +} + +pub fn to_dict_map(grid_map: &GridMap) -> eyre::Result { + let mut dict_map = dmm::Map::new( + grid_map.size.x as usize, + grid_map.size.y as usize, + grid_map.size.z as usize, + "".to_string(), + "".to_string(), + ); + dict_map.dictionary.clear(); + + let mut used_dict_keys = BTreeSet::::new(); + + let mut dictionary_reverse = FxHashMap::, dmm::Key>::default(); + + for tile in grid_map.grid.values() { + if !dictionary_reverse.contains_key(&tile.prefabs) { + if used_dict_keys.contains(&tile.key_suggestion) { + let next_free_key = (0..65534) + .map(int_to_key) + .filter(|k| !used_dict_keys.contains(k)) + .next() + .wrap_err(format!("ran out of free keys"))?; + dictionary_reverse.insert(tile.prefabs.clone(), next_free_key); + used_dict_keys.insert(next_free_key); + } else { + dictionary_reverse.insert(tile.prefabs.clone(), tile.key_suggestion.clone()); + used_dict_keys.insert(tile.key_suggestion.clone()); + } + } + } + + for x in 1..(grid_map.size.x + 1) { + for y in 1..(grid_map.size.y + 1) { + for z in 1..(grid_map.size.z + 1) { + let coord = Coord3::new(x, y, z); + if let Some(tile) = grid_map.grid.get(&coord) { + let key = dictionary_reverse.get(&tile.prefabs).unwrap().clone(); + dict_map.dictionary.insert(key, tile.prefabs.clone()); + dict_map.grid[coord3_to_index(coord, grid_map.size)] = key; + } else { + eyre::bail!("to_dict_map fail; grid map has no coord: {coord:?}"); + } + } + } + } + + dict_map.adjust_key_length(); + + Ok(dict_map) +} diff --git a/rust/src/mapmanip/core/to_grid_map.rs b/rust/src/mapmanip/core/to_grid_map.rs new file mode 100644 index 0000000000000..ec8c703f8d822 --- /dev/null +++ b/rust/src/mapmanip/core/to_grid_map.rs @@ -0,0 +1,37 @@ +use super::Tile; +use crate::mapmanip::core::GridMap; +use dmmtools::dmm::{self, Coord3}; +use std::ops::Index; + +fn tuple_to_size(xyz: (usize, usize, usize)) -> Coord3 { + Coord3::new(xyz.0 as i32, xyz.1 as i32, xyz.2 as i32) +} + +pub fn to_grid_map(dict_map: &dmm::Map) -> GridMap { + let mut grid_map = GridMap { + size: tuple_to_size(dict_map.dim_xyz()), + grid: crate::mapmanip::core::TileGrid::new( + dict_map.dim_xyz().0 as i32, + dict_map.dim_xyz().1 as i32, + dict_map.dim_xyz().2 as i32, + ), + }; + + for x in 1..grid_map.size.x + 1 { + for y in 1..grid_map.size.y + 1 { + for z in 1..grid_map.size.z + 1 { + let coord = dmm::Coord3::new(x, y, z); + let key = dict_map.index(coord); + let prefabs = dict_map.dictionary[key].clone(); + let tile = Tile { + key_suggestion: *key, + prefabs, + }; + let coord = dmm::Coord3::new(coord.x, coord.y, coord.z); + grid_map.grid.insert(&coord, tile); + } + } + } + + grid_map +} diff --git a/rust/src/mapmanip/mod.rs b/rust/src/mapmanip/mod.rs new file mode 100644 index 0000000000000..70bc79b8cf34c --- /dev/null +++ b/rust/src/mapmanip/mod.rs @@ -0,0 +1,282 @@ +use core::map_to_string; +use core::to_grid_map; +use core::GridMap; + +use byondapi::byond_string; +use byondapi::value::ByondValue; +use eyre::Context; +use eyre::ContextCompat; +use itertools::Itertools; +use rand::prelude::IteratorRandom; +use serde::{Deserialize, Serialize}; +use tools::extract_submap; +use tools::insert_submap; + +use crate::logging::dm_call_stack_trace; +use crate::logging::setup_panic_handler; + +mod core; +mod tools; + +#[cfg(test)] +mod test; + +/// +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum MapManipulation { + SubmapExtractInsert { + submap_size_x: i64, + submap_size_y: i64, + submap_size_z: i64, + submaps_dmm: String, + marker_extract: String, + marker_insert: String, + submaps_can_repeat: bool, + }, +} + +pub fn mapmanip_config_parse(config_path: &std::path::Path) -> eyre::Result> { + // read + let config = std::fs::read_to_string(config_path) + .wrap_err(format!("mapmanip config read err: {config_path:?}"))?; + + // strip comments + // as the jsonc format is "json with comments" + // but serde_json lib can only handle actual json + let re = regex::Regex::new(r"\/\/.*")?; + let config = re.replace_all(&config, ""); + + // parse + let config = serde_json::from_str::>(&config) + .wrap_err(format!("mapmanip config json parse err: {config_path:?}"))?; + + Ok(config) +} + +pub fn mapmanip( + map_dir_path: &std::path::Path, + map: dmmtools::dmm::Map, + config: &Vec, +) -> eyre::Result { + // convert to gridmap + let mut map = to_grid_map(&map); + + // go through all the manipulations in `.jsonc` config for this `.dmm` + for (n, manipulation) in config.iter().enumerate() { + // readable index for errors + let n = n + 1; + let config_len = config.len(); + + match manipulation { + MapManipulation::SubmapExtractInsert { + submap_size_x, + submap_size_y, + submap_size_z, + submaps_dmm, + marker_extract, + marker_insert, + submaps_can_repeat, + } => mapmanip_submap_extract_insert( + map_dir_path, + &mut map, + *submap_size_x, + *submap_size_y, + *submap_size_z, + submaps_dmm, + marker_extract, + marker_insert, + *submaps_can_repeat, + ) + .wrap_err(format!( + "submap extract insert fail; + submaps path: {submaps_dmm:?}; + markers: {marker_extract}, {marker_insert};" + )), + } + .wrap_err(format!("mapmanip fail; manip n is: {n}/{config_len}"))?; + } + + Ok(core::to_dict_map(&map).wrap_err("failed on `to_dict_map`")?) +} + +fn mapmanip_submap_extract_insert( + map_dir_path: &std::path::Path, + map: &mut GridMap, + submap_size_x: i64, + submap_size_y: i64, + submap_size_z: i64, + submaps_dmm: &String, + marker_extract: &String, + marker_insert: &String, + submaps_can_repeat: bool, +) -> eyre::Result<()> { + let submap_size = dmmtools::dmm::Coord3::new( + submap_size_x.try_into().wrap_err("invalid submap_size_x")?, + submap_size_y.try_into().wrap_err("invalid submap_size_y")?, + submap_size_z.try_into().wrap_err("invalid submap_size_z")?, + ); + + // get the submaps map + let submaps_dmm: std::path::PathBuf = submaps_dmm.try_into().wrap_err("invalid path")?; + let submaps_dmm = map_dir_path.join(submaps_dmm); + let submaps_map = GridMap::from_file(&submaps_dmm) + .wrap_err(format!("can't read and parse submap dmm: {submaps_dmm:?}"))?; + + // find all the submap extract markers + let mut marker_extract_coords = vec![]; + for (coord, tile) in submaps_map.grid.iter() { + if tile.prefabs.iter().any(|p| p.path == *marker_extract) { + marker_extract_coords.push(coord); + } + } + + // find all the insert markers + let mut marker_insert_coords = vec![]; + for (coord, tile) in map.grid.iter() { + if tile.prefabs.iter().any(|p| p.path == *marker_insert) { + marker_insert_coords.push(coord); + } + } + + // do all the extracts-inserts + for insert_coord in marker_insert_coords { + // pick a submap + let (extract_coord_index, extract_coord) = marker_extract_coords + .iter() + .cloned() + .enumerate() + .choose(&mut rand::thread_rng()) + .wrap_err(format!( + "can't pick a submap to extract; no more extract markers in the submaps dmm; marker type: {marker_extract}" + ))?; + + // if submaps should not be repeating, remove this one from the list + if !submaps_can_repeat { + marker_extract_coords.remove(extract_coord_index); + } + + // extract that submap from the submap dmm + let extracted = extract_submap(&submaps_map, extract_coord, submap_size) + .wrap_err(format!("submap extraction failed; from {extract_coord}"))?; + + // and insert the submap into the manipulated map + insert_submap(&extracted, insert_coord, map) + .wrap_err(format!("submap insertion failed; at {insert_coord}"))?; + } + + Ok(()) +} + +/// +#[byondapi::bind] +fn mapmanip_read_dmm_file(path: ByondValue) { + internal_mapmanip_read_dmm_file(path) +} + +pub(crate) fn internal_mapmanip_read_dmm_file(path: ByondValue) -> eyre::Result { + setup_panic_handler(); + + let path: String = path + .get_string() + .wrap_err(format!("path arg is not a string: {:?}", path))?; + let path: std::path::PathBuf = path + .clone() + .try_into() + .wrap_err(format!("path arg is not a valid file path: {}", path))?; + + // just return null if path is bad for whatever reason + if !path.is_file() || !path.exists() { + return Ok(ByondValue::null()); + } + + // read file and parse with spacemandmm + let mut dmm = dmmtools::dmm::Map::from_file(&path).wrap_err(format!( + "spacemandmm parsing error; dmm file path: {path:?}; see error from spacemandmm below for more information" + ))?; + + // do mapmanip if defined for this dmm + let path_mapmanip_config = { + let mut p = path.clone(); + p.set_extension("jsonc"); + p + }; + if path_mapmanip_config.exists() { + // get path for dir of this dmm + let path_dir = path.parent().wrap_err("no parent")?; + // parse config + let config = crate::mapmanip::mapmanip_config_parse(&path_mapmanip_config).wrap_err( + format!("config parse fail; path: {:?}", path_mapmanip_config), + )?; + // do actual map manipulation + dmm = crate::mapmanip::mapmanip(path_dir, dmm, &config) + .wrap_err(format!("mapmanip fail; dmm file path: {path:?}"))?; + } + + // convert the map back to a string + let dmm = crate::mapmanip::core::map_to_string(&dmm).wrap_err(format!( + "error in converting map back to string; dmm file path: {path:?}" + ))?; + + // and return it + Ok(ByondValue::new_str(dmm)?) +} + +/// +#[no_mangle] +pub unsafe extern "C" fn read_dmm_file_ffi( + argc: byondapi::sys::u4c, + argv: *mut byondapi::value::ByondValue, +) -> byondapi::value::ByondValue { + setup_panic_handler(); + let args = unsafe { ::byondapi::parse_args(argc, argv) }; + match crate::mapmanip::internal_mapmanip_read_dmm_file( + args.get(0).map(ByondValue::clone).unwrap_or_default(), + ) { + Ok(val) => val, + Err(info) => { + dm_call_stack_trace(format!("Rustlibs ERROR read_dmm_file_ffi() \n {info:#?}")); + ByondValue::null() + } + } +} + +/// To be used by the `tools/rustlib_tools/mapmanip.ps1` script. +/// Not to be called from the game server, so bad error-handling is fine. +/// This should run map manipulations on every `.dmm` map that has a `.jsonc` config file, +/// and write it to a `.mapmanipout.dmm` file in the same location. +#[no_mangle] +pub unsafe extern "C" fn all_mapmanip_configs_execute_ffi() { + let mapmanip_configs = walkdir::WalkDir::new("./_maps") + .into_iter() + .map(|d| d.unwrap().path().to_owned()) + .filter(|p| p.extension().is_some()) + .filter(|p| p.extension().unwrap() == "jsonc") + .collect_vec(); + assert_ne!(mapmanip_configs.len(), 0); + + for config_path in mapmanip_configs { + let dmm_path = { + let mut p = config_path.clone(); + p.set_extension("dmm"); + p + }; + + let path_dir: &std::path::Path = dmm_path.parent().unwrap(); + + let mut dmm = dmmtools::dmm::Map::from_file(&dmm_path).unwrap(); + + let config = crate::mapmanip::mapmanip_config_parse(&config_path).unwrap(); + + dmm = crate::mapmanip::mapmanip(path_dir, dmm, &config).unwrap(); + + let dmm = map_to_string(&dmm).unwrap(); + + let dmm_out_path = { + let mut p = dmm_path.clone(); + p.set_extension("mapmanipout.dmm"); + p + }; + std::fs::write(dmm_out_path, dmm).unwrap(); + } +} diff --git a/rust/src/mapmanip/test-in/_tiny_test_map.dmm b/rust/src/mapmanip/test-in/_tiny_test_map.dmm new file mode 100644 index 0000000000000..48ad3798f2d9a --- /dev/null +++ b/rust/src/mapmanip/test-in/_tiny_test_map.dmm @@ -0,0 +1,359 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/simulated/floor/marble, +/area/space) +"b" = ( +/obj/random/mre, +/turf/simulated/floor/shuttle, +/area/space) +"f" = ( +/obj/random/energy, +/turf/simulated/wall, +/area/space) +"j" = ( +/obj/random/energy_antag, +/turf/simulated/wall, +/area/space) +"k" = ( +/obj/random/action_figure, +/obj/random/action_figure, +/turf/simulated/floor/lino/diamond, +/area/space) +"n" = ( +/obj/random/mre, +/turf/simulated/floor/marble, +/area/space) +"o" = ( +/obj/random/mre, +/turf/simulated/wall, +/area/space) +"p" = ( +/obj/random/handgun, +/turf/simulated/floor/marble, +/area/space) +"q" = ( +/obj/random/action_figure, +/turf/simulated/floor/shuttle, +/area/space) +"r" = ( +/obj/random/action_figure, +/obj/aiming_overlay, +/turf/simulated/floor/lino/diamond, +/area/space) +"s" = ( +/obj/random/action_figure, +/turf/simulated/floor/lino/diamond, +/area/space) +"u" = ( +/obj/random/handgun, +/turf/simulated/wall, +/area/space) +"y" = ( +/obj/random/glowstick, +/turf/simulated/floor/marble, +/area/space) +"z" = ( +/obj/random/firstaid, +/turf/simulated/floor/marble, +/area/space) +"B" = ( +/turf/simulated/floor/lino/diamond, +/area/space) +"E" = ( +/turf/simulated/wall, +/area/space) +"F" = ( +/turf/simulated/floor/shuttle, +/area/space) +"G" = ( +/obj/random/finances, +/turf/simulated/wall, +/area/space) +"H" = ( +/obj/random/gift, +/turf/simulated/floor/marble, +/area/space) +"J" = ( +/obj/random/chameleon, +/turf/simulated/floor/shuttle, +/area/space) +"Q" = ( +/obj/aiming_overlay, +/turf/simulated/floor/marble, +/area/space) +"S" = ( +/obj/aiming_overlay, +/turf/simulated/floor/lino/diamond, +/area/space) +"U" = ( +/obj/random/mre, +/turf/simulated/floor/lino/diamond, +/area/space) +"W" = ( +/obj/random/chameleon, +/turf/simulated/floor/lino/diamond, +/area/space) +"Y" = ( +/obj/random/action_figure, +/turf/simulated/floor/marble, +/area/space) +"Z" = ( +/obj/random/chameleon, +/turf/simulated/floor/marble, +/area/space) + +(1,1,1) = {" +E +j +j +E +E +E +E +E +E +E +E +E +G +G +a +"} +(2,1,1) = {" +f +B +B +B +B +B +B +B +B +B +B +B +B +B +z +"} +(3,1,1) = {" +f +B +B +B +B +Q +a +a +a +a +a +a +a +B +z +"} +(4,1,1) = {" +E +B +B +a +a +Q +B +B +B +B +B +B +B +B +a +"} +(5,1,1) = {" +E +B +s +Y +B +S +B +B +B +B +B +s +B +F +a +"} +(6,1,1) = {" +E +s +s +Y +s +S +s +B +B +B +B +s +B +F +a +"} +(7,1,1) = {" +E +B +B +a +B +r +s +k +k +B +B +s +B +F +a +"} +(8,1,1) = {" +E +W +a +a +B +S +B +B +k +k +B +s +F +F +a +"} +(9,1,1) = {" +o +W +n +U +U +S +U +U +U +s +k +U +b +U +n +"} +(10,1,1) = {" +E +W +Z +B +B +B +B +B +B +B +k +B +F +B +a +"} +(11,1,1) = {" +E +B +Z +W +B +B +B +B +B +B +q +F +F +B +a +"} +(12,1,1) = {" +E +B +B +W +W +B +B +F +F +F +F +s +B +B +a +"} +(13,1,1) = {" +u +B +B +B +B +J +J +J +B +B +B +s +B +B +H +"} +(14,1,1) = {" +u +B +B +B +F +F +B +W +W +B +B +B +s +B +H +"} +(15,1,1) = {" +a +p +p +a +a +a +a +a +a +a +a +a +y +y +a +"} diff --git a/rust/src/mapmanip/test-in/extracted.dmm b/rust/src/mapmanip/test-in/extracted.dmm new file mode 100644 index 0000000000000..4df41ac838ea5 --- /dev/null +++ b/rust/src/mapmanip/test-in/extracted.dmm @@ -0,0 +1,115 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/simulated/floor/lino/diamond, +/area/space) +"b" = ( +/obj/random/action_figure, +/obj/aiming_overlay, +/turf/simulated/floor/lino/diamond, +/area/space) +"k" = ( +/obj/random/action_figure, +/turf/simulated/floor/lino/diamond, +/area/space) +"p" = ( +/turf/simulated/floor/marble, +/area/space) +"u" = ( +/obj/aiming_overlay, +/turf/simulated/floor/marble, +/area/space) +"G" = ( +/obj/random/mre, +/turf/simulated/floor/lino/diamond, +/area/space) +"H" = ( +/obj/random/chameleon, +/turf/simulated/floor/shuttle, +/area/space) +"L" = ( +/obj/random/action_figure, +/obj/random/action_figure, +/turf/simulated/floor/lino/diamond, +/area/space) +"N" = ( +/turf/simulated/floor/shuttle, +/area/space) +"R" = ( +/obj/random/chameleon, +/turf/simulated/floor/lino/diamond, +/area/space) +"T" = ( +/obj/aiming_overlay, +/turf/simulated/floor/lino/diamond, +/area/space) + +(1,1,1) = {" +p +u +a +a +a +"} +(2,1,1) = {" +a +T +a +a +a +"} +(3,1,1) = {" +k +T +k +a +a +"} +(4,1,1) = {" +a +b +k +L +L +"} +(5,1,1) = {" +a +T +a +a +L +"} +(6,1,1) = {" +G +T +G +G +G +"} +(7,1,1) = {" +a +a +a +a +a +"} +(8,1,1) = {" +a +a +a +a +a +"} +(9,1,1) = {" +R +a +a +N +N +"} +(10,1,1) = {" +a +H +H +H +a +"} diff --git a/rust/src/mapmanip/test-in/inserted.dmm b/rust/src/mapmanip/test-in/inserted.dmm new file mode 100644 index 0000000000000..dad09b97a8646 --- /dev/null +++ b/rust/src/mapmanip/test-in/inserted.dmm @@ -0,0 +1,355 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/simulated/floor/marble, +/area/space) +"b" = ( +/obj/random/mre, +/turf/simulated/floor/shuttle, +/area/space) +"f" = ( +/obj/random/energy, +/turf/simulated/wall, +/area/space) +"j" = ( +/obj/random/energy_antag, +/turf/simulated/wall, +/area/space) +"k" = ( +/obj/random/action_figure, +/obj/random/action_figure, +/turf/simulated/floor/lino/diamond, +/area/space) +"n" = ( +/obj/random/mre, +/turf/simulated/floor/marble, +/area/space) +"o" = ( +/obj/random/mre, +/turf/simulated/wall, +/area/space) +"p" = ( +/obj/random/handgun, +/turf/simulated/floor/marble, +/area/space) +"r" = ( +/obj/random/action_figure, +/obj/aiming_overlay, +/turf/simulated/floor/lino/diamond, +/area/space) +"s" = ( +/obj/random/action_figure, +/turf/simulated/floor/lino/diamond, +/area/space) +"u" = ( +/obj/random/handgun, +/turf/simulated/wall, +/area/space) +"y" = ( +/obj/random/glowstick, +/turf/simulated/floor/marble, +/area/space) +"z" = ( +/obj/random/firstaid, +/turf/simulated/floor/marble, +/area/space) +"B" = ( +/turf/simulated/floor/lino/diamond, +/area/space) +"E" = ( +/turf/simulated/wall, +/area/space) +"F" = ( +/turf/simulated/floor/shuttle, +/area/space) +"G" = ( +/obj/random/finances, +/turf/simulated/wall, +/area/space) +"H" = ( +/obj/random/gift, +/turf/simulated/floor/marble, +/area/space) +"J" = ( +/obj/random/chameleon, +/turf/simulated/floor/shuttle, +/area/space) +"Q" = ( +/obj/aiming_overlay, +/turf/simulated/floor/marble, +/area/space) +"S" = ( +/obj/aiming_overlay, +/turf/simulated/floor/lino/diamond, +/area/space) +"U" = ( +/obj/random/mre, +/turf/simulated/floor/lino/diamond, +/area/space) +"W" = ( +/obj/random/chameleon, +/turf/simulated/floor/lino/diamond, +/area/space) +"Y" = ( +/obj/random/action_figure, +/turf/simulated/floor/marble, +/area/space) +"Z" = ( +/obj/random/chameleon, +/turf/simulated/floor/marble, +/area/space) + +(1,1,1) = {" +E +j +j +E +E +E +E +E +E +E +E +E +G +G +a +"} +(2,1,1) = {" +f +B +B +B +B +B +B +B +B +B +B +B +B +B +z +"} +(3,1,1) = {" +f +B +B +B +B +Q +a +a +a +a +a +a +a +B +z +"} +(4,1,1) = {" +E +B +B +a +a +Q +B +B +B +B +B +B +B +B +a +"} +(5,1,1) = {" +E +B +s +Y +B +S +B +B +B +B +B +s +B +F +a +"} +(6,1,1) = {" +E +s +s +Y +s +S +s +a +Q +B +B +B +B +F +a +"} +(7,1,1) = {" +E +B +B +a +B +r +s +B +S +B +B +B +B +F +a +"} +(8,1,1) = {" +E +W +a +a +B +S +B +s +S +s +B +B +F +F +a +"} +(9,1,1) = {" +o +W +n +U +U +S +U +B +r +s +k +k +b +U +n +"} +(10,1,1) = {" +E +W +Z +B +B +B +B +B +S +B +B +k +F +B +a +"} +(11,1,1) = {" +E +B +Z +W +B +B +B +U +S +U +U +U +F +B +a +"} +(12,1,1) = {" +E +B +B +W +W +B +B +B +B +B +B +B +B +B +a +"} +(13,1,1) = {" +u +B +B +B +B +J +J +B +B +B +B +B +B +B +H +"} +(14,1,1) = {" +u +B +B +B +F +F +B +W +B +B +F +F +s +B +H +"} +(15,1,1) = {" +a +p +p +a +a +a +a +B +J +J +J +B +y +y +a +"} diff --git a/rust/src/mapmanip/test.rs b/rust/src/mapmanip/test.rs new file mode 100644 index 0000000000000..5f1076d913ab8 --- /dev/null +++ b/rust/src/mapmanip/test.rs @@ -0,0 +1,163 @@ +use dmmtools::dmm::{self, Coord3}; +use itertools::Itertools; + +use super::all_mapmanip_configs_execute_ffi; + +fn print_diff(left: &str, right: &str) { + for (i, diff) in diff::lines(&left, &right).iter().enumerate() { + match diff { + diff::Result::Left(l) => println!("{} diff - : {}", i, l), + diff::Result::Both(l, r) => { + assert_eq!(l, r); + } + diff::Result::Right(r) => println!("{} diff + : {}", i, r), + } + } +} + +fn all_test_dmm() -> Vec { + std::fs::read_dir("src/mapmanip/test-in") + .unwrap() + .map(|r| r.unwrap().path()) + .filter(|p| p.extension().unwrap() == "dmm") + .sorted() + .collect_vec() +} + +#[test] +fn to_grid_and_back() { + for path in all_test_dmm() { + println!("path: {}", path.display()); + + let dict_map_original = dmmtools::dmm::Map::from_file(&path).unwrap(); + let grid_map = crate::mapmanip::core::to_grid_map(&dict_map_original); + let dict_map_again = crate::mapmanip::core::to_dict_map(&grid_map).unwrap(); + let map_str_original = crate::mapmanip::core::map_to_string(&dict_map_original).unwrap(); + let map_str_from_grid = crate::mapmanip::core::map_to_string(&dict_map_again).unwrap(); + + dict_map_again + .to_file(&std::path::Path::new("src/mapmanip/test-out").join(path.file_name().unwrap())) + .unwrap(); + + print_diff(&map_str_original, &map_str_from_grid); + + if map_str_original != map_str_from_grid { + assert!(false); + } + } +} + +#[test] +fn extract() { + let path_src = std::path::Path::new("src/mapmanip/test-in/_tiny_test_map.dmm"); + let path_xtr = std::path::Path::new("src/mapmanip/test-in/extracted.dmm"); + let path_xtr_out = std::path::Path::new("src/mapmanip/test-out/extracted_out.dmm"); + + let dict_map_src = dmmtools::dmm::Map::from_file(&path_src).unwrap(); + let dict_map_xtr_expected = dmmtools::dmm::Map::from_file(&path_xtr).unwrap(); + + let grid_map_src = crate::mapmanip::core::to_grid_map(&dict_map_src); + let grid_map_xtr = crate::mapmanip::tools::extract_submap( + &grid_map_src, + Coord3::new(4, 7, 1), + Coord3::new(10, 5, 1), + ) + .unwrap(); + let grid_map_xtr_expected = crate::mapmanip::core::to_grid_map(&dict_map_xtr_expected); + + let dict_map_xtr = crate::mapmanip::core::to_dict_map(&grid_map_xtr).unwrap(); + dict_map_xtr.to_file(path_xtr_out).unwrap(); + + assert_eq!( + grid_map_xtr_expected.grid.keys().collect::>(), + grid_map_xtr.grid.keys().collect::>(), + ); + + for key in grid_map_xtr_expected.grid.keys() { + let tile_xtr_expected = grid_map_xtr_expected.grid.get(&key).unwrap(); + let tile_xtr = grid_map_xtr.grid.get(&key).unwrap(); + assert_eq!(tile_xtr_expected.prefabs, tile_xtr.prefabs); + } +} + +#[test] +fn insert() { + let path_xtr = std::path::Path::new("src/mapmanip/test-in/extracted.dmm"); + let path_dst = std::path::Path::new("src/mapmanip/test-in/_tiny_test_map.dmm"); + let path_dst_expected = std::path::Path::new("src/mapmanip/test-in/inserted.dmm"); + + let grid_map_dst_expected = + crate::mapmanip::core::GridMap::from_file(&path_dst_expected).unwrap(); + let grid_map_xtr = crate::mapmanip::core::GridMap::from_file(&path_xtr).unwrap(); + let mut grid_map_dst = crate::mapmanip::core::GridMap::from_file(&path_dst).unwrap(); + crate::mapmanip::tools::insert_submap(&grid_map_xtr, Coord3::new(6, 4, 1), &mut grid_map_dst) + .unwrap(); + + assert_eq!( + grid_map_dst_expected.grid.keys().collect::>(), + grid_map_dst.grid.keys().collect::>(), + ); + + for key in grid_map_dst_expected.grid.keys() { + let tile_dst_expected = grid_map_dst_expected.grid.get(&key).unwrap(); + let tile_dst = grid_map_dst.grid.get(&key).unwrap(); + assert_eq!(tile_dst_expected.prefabs, tile_dst.prefabs); + } +} + +#[test] +fn keys_deduplicated() { + // make sure that if multiple tiles have the same key_suggestion + // they get assigned different keys + + let path_src = std::path::Path::new("src/mapmanip/test-in/_tiny_test_map.dmm"); + let dict_map_src = dmmtools::dmm::Map::from_file(&path_src).unwrap(); + let grid_map_src = crate::mapmanip::core::to_grid_map(&dict_map_src); + + let mut grid_map_out = crate::mapmanip::core::to_grid_map(&dict_map_src); + for tile in grid_map_out.grid.values_mut() { + tile.key_suggestion = dmm::Key::default(); + } + let dict_map_out = crate::mapmanip::core::to_dict_map(&grid_map_out).unwrap(); + let grid_map_out = crate::mapmanip::core::to_grid_map(&dict_map_out); + + for key in grid_map_src.grid.keys() { + let tile_src = grid_map_src.grid.get(&key).unwrap(); + let tile_out = grid_map_out.grid.get(&key).unwrap(); + assert_eq!(tile_src.prefabs, tile_out.prefabs); + } + + assert_eq!(dict_map_src.dictionary.len(), dict_map_out.dictionary.len()) +} + +#[test] +fn mapmanip_configs_parse() { + let foo = vec![crate::mapmanip::MapManipulation::SubmapExtractInsert { + submap_size_x: 1, + submap_size_y: 2, + submap_size_z: 3, + submaps_dmm: "a".to_owned(), + marker_extract: "b".to_owned(), + marker_insert: "c".to_owned(), + submaps_can_repeat: true, + }]; + dbg!(serde_json::to_string(&foo).unwrap()); + + let mapmanip_configs = walkdir::WalkDir::new("../../maps") + .into_iter() + .map(|d| d.unwrap().path().to_owned()) + .filter(|p| p.extension().is_some()) + .filter(|p| p.extension().unwrap() == "jsonc") + .collect_vec(); + assert_ne!(mapmanip_configs.len(), 0); + for config in mapmanip_configs { + let _ = crate::mapmanip::mapmanip_config_parse(&config); + } +} + +#[test] +fn mapmanip_configs_execute() { + // this is only "unsafe" cause that function is `extern "C"` + // it does not do anything actually unsafe + unsafe { all_mapmanip_configs_execute_ffi() } +} diff --git a/rust/src/mapmanip/tools/extract_submap.rs b/rust/src/mapmanip/tools/extract_submap.rs new file mode 100644 index 0000000000000..30e7e98063846 --- /dev/null +++ b/rust/src/mapmanip/tools/extract_submap.rs @@ -0,0 +1,37 @@ +use crate::mapmanip::core::GridMap; +use dmmtools::dmm; +use dmmtools::dmm::Coord3; +use eyre::ContextCompat; + +/// Returns part of map of `xtr_size` and at `xtr_coord` from `src_map`. +pub fn extract_submap( + src_map: &GridMap, + xtr_coord: dmm::Coord3, + xtr_size: dmm::Coord3, +) -> eyre::Result { + let mut dst_map = GridMap { + size: xtr_size, + grid: crate::mapmanip::core::TileGrid::new(xtr_size.x, xtr_size.y, xtr_size.z), + }; + + for x in 1..(xtr_size.x + 1) { + for y in 1..(xtr_size.y + 1) { + for z in 1..(xtr_size.z + 1) { + let src_x = xtr_coord.x + x - 1; + let src_y = xtr_coord.y + y - 1; + let src_z = xtr_coord.z + z - 1; + + let tile = src_map + .grid + .get(&Coord3::new(src_x, src_y, src_z)) + .wrap_err(format!( + "cannot extract submap; coords out of bounds; x: {src_x}; y: {src_y}; z: {src_z}" + ))?; + + dst_map.grid.insert(&Coord3::new(x, y, z), tile.clone()); + } + } + } + + Ok(dst_map) +} diff --git a/rust/src/mapmanip/tools/insert_submap.rs b/rust/src/mapmanip/tools/insert_submap.rs new file mode 100644 index 0000000000000..e840f9920f33c --- /dev/null +++ b/rust/src/mapmanip/tools/insert_submap.rs @@ -0,0 +1,75 @@ +use crate::mapmanip::GridMap; +use dmmtools::dmm; +use dmmtools::dmm::Coord3; +use eyre::ContextCompat; + +/// Takes `src_map` and puts it at `coord` in `dst_map`. +/// Noop area and turf have special handling: `/area/template_noop` and `/turf/template_noop`. +/// If a `src` tile has noop area, then it uses the mapped in `dst` area instead of replacing it. +/// If a `src` tile has noop turf, then it uses the mapped in `dst` turf instead of replacing it, +/// and additionally also merges `src` objects and mobs with the `dst` mapped in ones. +pub fn insert_submap( + src_map: &GridMap, + coord: dmm::Coord3, + dst_map: &mut GridMap, +) -> eyre::Result<()> { + for x in 1..(src_map.size.x + 1) { + for y in 1..(src_map.size.y + 1) { + for z in 1..(src_map.size.z + 1) { + let coord_src = Coord3::new(x, y, z); + let coord_dst = Coord3::new(coord.x + x - 1, coord.y + y - 1, coord.z + z - 1); + + // get src tile + let mut tile_src = src_map + .grid + .get(&coord_src) + .wrap_err(format!( + "src submap coord out of bounds: {coord_src}; {}; {};", + src_map.size, + src_map.grid.len(), + ))? + .clone(); + + // remove area and turf from src tile + let tile_src_area = tile_src.remove_area().wrap_err("submap tile has no area")?; + let tile_src_turf = tile_src.remove_turf().wrap_err("submap tile has no turf")?; + + let tile_src_area_is_noop = { tile_src_area.path == "/area/template_noop" }; + let tile_src_turf_is_noop = { tile_src_turf.path == "/turf/template_noop" }; + + // get dst tile + let tile_dst = dst_map.grid.get_mut(&coord_dst).wrap_err(format!( + "cannot insert submap tile; coord out of bounds; x: {x}; y: {y};" + ))?; + + // remove area and turf from dst tile + let tile_dst_area = tile_dst.remove_area().wrap_err("map tile has no area")?; + let tile_dst_turf = tile_dst.remove_turf().wrap_err("map tile has no turf")?; + + // get new area + let new_area = if tile_src_area_is_noop { + tile_dst_area + } else { + tile_src_area + }; + + // get new turf, AND, append/replace other atoms into dst tile + let new_turf = if tile_src_turf_is_noop { + tile_dst.prefabs.append(&mut tile_src.prefabs); + tile_dst_turf + } else { + tile_dst.prefabs = tile_src.prefabs; + tile_src_turf + }; + + // push selected new turf and area to dst tile + // do note that in dmm file format + // turf and area have to be the two last elements in a prefab + tile_dst.prefabs.push(new_turf); + tile_dst.prefabs.push(new_area); + } + } + } + + Ok(()) +} diff --git a/rust/src/mapmanip/tools/mod.rs b/rust/src/mapmanip/tools/mod.rs new file mode 100644 index 0000000000000..c3688d31986b2 --- /dev/null +++ b/rust/src/mapmanip/tools/mod.rs @@ -0,0 +1,5 @@ +mod extract_submap; +pub use extract_submap::extract_submap; + +mod insert_submap; +pub use insert_submap::insert_submap; diff --git a/milla/src/api.rs b/rust/src/milla/api.rs similarity index 96% rename from milla/src/api.rs rename to rust/src/milla/api.rs index 53e63d4a17a91..b5393a58859a9 100644 --- a/milla/src/api.rs +++ b/rust/src/milla/api.rs @@ -1,9 +1,9 @@ -use crate::constants::*; -use crate::conversion; use crate::logging; -use crate::model::*; -use crate::statics::*; -use crate::tick; +use crate::milla::constants::*; +use crate::milla::conversion; +use crate::milla::model::*; +use crate::milla::statics::*; +use crate::milla::tick; use byondapi::global_call::call_global; use byondapi::map::byond_xyz; use byondapi::prelude::*; @@ -14,7 +14,7 @@ use std::time::Instant; /// BYOND API for ensuring the buffers are usable. #[byondapi::bind] -fn initialize(byond_z: ByondValue) { +fn milla_initialize(byond_z: ByondValue) { logging::setup_panic_handler(); let z = f32::try_from(byond_z)? as i32 - 1; internal_initialize(z)?; @@ -36,7 +36,7 @@ pub(crate) fn internal_initialize(z: i32) -> Result<(), eyre::Error> { /// BYOND API for defining an environment that a tile can be exposed to. #[byondapi::bind] -fn create_environment( +fn milla_create_environment( oxygen: ByondValue, carbon_dioxide: ByondValue, nitrogen: ByondValue, @@ -96,7 +96,7 @@ pub(crate) fn internal_create_environment( /// BYOND API for setting the atmos details of a tile. #[byondapi::bind] -fn set_tile( +fn milla_set_tile( turf: ByondValue, airtight_north: ByondValue, airtight_east: ByondValue, @@ -143,7 +143,7 @@ fn set_tile( /// BYOND API for setting the directions a tile is airtight in. /// Like set_tile, just with a smaller set of fields. #[byondapi::bind] -fn set_tile_airtight( +fn milla_set_tile_airtight( turf: ByondValue, airtight_north: ByondValue, airtight_east: ByondValue, @@ -239,7 +239,7 @@ pub(crate) fn internal_set_tile( }; } } - 3 => tile.mode = AtmosMode::NoDecay, + 3 => tile.mode = AtmosMode::NoDecay, _ => return Err(eyre!("Invalid atmos_mode: {}", value)), } } @@ -278,7 +278,7 @@ pub(crate) fn internal_set_tile( /// BYOND API for fetching the atmos details of a tile. #[byondapi::bind] -fn get_tile(turf: ByondValue, list: ByondValue) { +fn milla_get_tile(turf: ByondValue, list: ByondValue) { logging::setup_panic_handler(); let (x, y, z) = byond_xyz(&turf)?.coordinates(); let tile = internal_get_tile(x as i32 - 1, y as i32 - 1, z as i32 - 1)?; @@ -308,7 +308,7 @@ pub(crate) fn internal_get_tile(x: i32, y: i32, z: i32) -> Result { /// * Turfs that just passed the threshold for showing plasma or sleeping gas. /// * Turfs with strong airflow out. #[byondapi::bind] -fn get_interesting_tiles() { +fn milla_get_interesting_tiles() { logging::setup_panic_handler(); let interesting_tiles = INTERESTING_TILES.lock().unwrap(); let byond_interesting_tiles = interesting_tiles @@ -320,7 +320,7 @@ fn get_interesting_tiles() { /// BYOND API for getting a single random interesting tile. #[byondapi::bind] -fn get_random_interesting_tile() { +fn milla_get_random_interesting_tile() { logging::setup_panic_handler(); let interesting_tiles = INTERESTING_TILES.lock().unwrap(); let length = interesting_tiles.len() as f32; @@ -336,7 +336,7 @@ fn get_random_interesting_tile() { /// BYOND API for capping the superconductivity of a tile. #[byondapi::bind] -fn reduce_superconductivity( +fn milla_reduce_superconductivity( turf: ByondValue, north: ByondValue, east: ByondValue, @@ -404,7 +404,7 @@ pub(crate) fn internal_reduce_superconductivity( /// BYOND API for resetting the superconductivity of a tile. #[byondapi::bind] -fn reset_superconductivity(turf: ByondValue) { +fn milla_reset_superconductivity(turf: ByondValue) { let (x, y, z) = byond_xyz(&turf)?.coordinates(); internal_reset_superconductivity(x as i32 - 1, y as i32 - 1, z as i32 - 1)?; Ok(Default::default()) @@ -436,7 +436,7 @@ pub(crate) fn internal_reset_superconductivity(x: i32, y: i32, z: i32) -> Result /// BYOND API for starting an atmos tick. #[byondapi::bind] -fn spawn_tick_thread() { +fn milla_spawn_tick_thread() { logging::setup_panic_handler(); thread::spawn(|| -> Result<(), eyre::Error> { let now = Instant::now(); @@ -454,7 +454,7 @@ fn spawn_tick_thread() { /// BYOND API for asking how long the prior tick took. #[byondapi::bind] -fn get_tick_time() { +fn milla_get_tick_time() { logging::setup_panic_handler(); Ok(ByondValue::from( TICK_TIME.load(std::sync::atomic::Ordering::Relaxed) as f32, diff --git a/milla/src/constants.rs b/rust/src/milla/constants.rs similarity index 100% rename from milla/src/constants.rs rename to rust/src/milla/constants.rs diff --git a/milla/src/conversion.rs b/rust/src/milla/conversion.rs similarity index 100% rename from milla/src/conversion.rs rename to rust/src/milla/conversion.rs diff --git a/milla/src/lib.rs b/rust/src/milla/mod.rs similarity index 97% rename from milla/src/lib.rs rename to rust/src/milla/mod.rs index 7c2e81ff85d9d..d0b90b51541f0 100644 --- a/milla/src/lib.rs +++ b/rust/src/milla/mod.rs @@ -6,11 +6,9 @@ //! MILLA takes the majority of atmos out of BYOND and puts it here, in Rust code. //! It stores its own model of the air distribution, and BYOND will call in to view and make //! adjustments, as well as to trigger atmos ticks. - mod api; mod constants; mod conversion; -mod logging; mod model; mod simulate; mod statics; diff --git a/milla/src/model.rs b/rust/src/milla/model.rs similarity index 99% rename from milla/src/model.rs rename to rust/src/milla/model.rs index 8f559783e27fd..13044a6d879c1 100644 --- a/milla/src/model.rs +++ b/rust/src/milla/model.rs @@ -1,4 +1,4 @@ -use crate::constants::*; +use crate::milla::constants::*; use atomic_float::AtomicF32; use bitflags::bitflags; use byondapi::map::{byond_locatexyz, ByondXYZ}; diff --git a/milla/src/simulate.rs b/rust/src/milla/simulate.rs similarity index 99% rename from milla/src/simulate.rs rename to rust/src/milla/simulate.rs index b8c88dc80aa6d..b663b77f70818 100644 --- a/milla/src/simulate.rs +++ b/rust/src/milla/simulate.rs @@ -1,5 +1,5 @@ -use crate::constants::*; -use crate::model::*; +use crate::milla::constants::*; +use crate::milla::model::*; use byondapi::map::ByondXYZ; use eyre::eyre; use scc::Bag; @@ -437,7 +437,7 @@ pub(crate) fn apply_tile_mode( my_inactive_tile.thermal_energy *= 1.0 - SPACE_COOLING_FACTOR; } } - AtmosMode::NoDecay => {} // No special interactions + AtmosMode::NoDecay => {} // No special interactions } Ok(()) } diff --git a/milla/src/statics.rs b/rust/src/milla/statics.rs similarity index 95% rename from milla/src/statics.rs rename to rust/src/milla/statics.rs index 76a0305abfec0..4ecdd702c1b8b 100644 --- a/milla/src/statics.rs +++ b/rust/src/milla/statics.rs @@ -1,4 +1,4 @@ -use crate::model::*; +use crate::milla::model::*; use std::sync::{atomic::AtomicUsize, Mutex, OnceLock}; /// The buffers that contain the atmos model. diff --git a/milla/src/tick.rs b/rust/src/milla/tick.rs similarity index 99% rename from milla/src/tick.rs rename to rust/src/milla/tick.rs index b8a6991102f7b..bc9afafff1f8c 100644 --- a/milla/src/tick.rs +++ b/rust/src/milla/tick.rs @@ -1,7 +1,7 @@ -use crate::constants::*; -use crate::model::*; -use crate::simulate; -use crate::statics::*; +use crate::milla::constants::*; +use crate::milla::model::*; +use crate::milla::simulate; +use crate::milla::statics::*; use scc::Bag; use std::sync::RwLock; use std::thread; diff --git a/rustlibs.dll b/rustlibs.dll new file mode 100644 index 0000000000000..2390f2853c5a5 Binary files /dev/null and b/rustlibs.dll differ diff --git a/tools/ci/libmilla_ci.so b/tools/ci/libmilla_ci.so deleted file mode 100755 index 8517035dea129..0000000000000 Binary files a/tools/ci/libmilla_ci.so and /dev/null differ diff --git a/tools/ci/librustlibs_ci.so b/tools/ci/librustlibs_ci.so new file mode 100644 index 0000000000000..0b7d30ea2864b Binary files /dev/null and b/tools/ci/librustlibs_ci.so differ diff --git a/tools/rustlibs_tools/mapmanip.ps1 b/tools/rustlibs_tools/mapmanip.ps1 new file mode 100644 index 0000000000000..1c79d6df01a58 --- /dev/null +++ b/tools/rustlibs_tools/mapmanip.ps1 @@ -0,0 +1,41 @@ + +# if you want to run this script but it opens in notepad +# you may want to right click it and "run with powershell" + +# script explanation +echo "*****" +echo "This script will run map manipulations on every `.dmm` map that has a `.jsonc` config file," +echo "and write it to a `.mapmanipout.dmm` file in the same location." +echo "Make sure to not commit these files to the repo." +echo "This script will not show any error messages if map manipulations have failed." +echo "Should launch the actual server to get stacktraces and the like." +echo "*****" + +# find path to rustlibs.dll +if (Test-Path "./rust/target/i686-pc-windows-msvc/release/rustlibs.dll") { + $BapiPath = "./rust/target/i686-pc-windows-msvc/release/rustlibs.dll" +} +elseif (Test-Path "./rust/target/i686-pc-windows-msvc/debug/rustlibs.dll") { + $BapiPath = "./rust/target/i686-pc-windows-msvc/debug/rustlibs.dll" +} +elseif (Test-Path "./rustlibs.dll") { + $BapiPath = "./rustlibs.dll" +} +else { + echo "Cannot find rustlibs." +} + +# run ffi function from rustlibs.dll +echo "Executing..." +$BapiDllFunction = "all_mapmanip_configs_execute_ffi" +$BapiExecutionTime = Measure-Command { + # `rundll` runs a function from a dll + # the very sad limitation is that it does not give any output from that function + rundll32.exe $BapiPath, $BapiDllFunction +} + +# done +echo "Done!" +echo ("Took {0} seconds, or {1} milliseconds in total." -f $BapiExecutionTime.Seconds, $BapiExecutionTime.Milliseconds) +echo "*****" +Read-Host -Prompt "Press Enter to exit..." diff --git a/tools/tgs_scripts/PreCompile.sh b/tools/tgs_scripts/PreCompile.sh index 2b93c8df538ef..b23f1fc2a22a4 100755 --- a/tools/tgs_scripts/PreCompile.sh +++ b/tools/tgs_scripts/PreCompile.sh @@ -45,8 +45,8 @@ env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --features all - mv target/i686-unknown-linux-gnu/release/librust_g.so "$1/librust_g.so" cd ../../ -echo "Deploying MILLA..." -cd $1/milla +echo "Deploying Rustlibs..." +cd $1/rust env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --features all --target=i686-unknown-linux-gnu -mv target/i686-unknown-linux-gnu/release/libmilla.so "$1/libmilla.so" +mv target/i686-unknown-linux-gnu/release/librustlibs.so "$1/librustlibs.so" cd ..