├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ └── rust.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── Hack-Regular.ttf ├── README.md ├── bwapi_wrapper ├── Cargo.toml ├── README.md ├── Windows.h ├── build.rs ├── src │ ├── bindings.rs │ ├── command.rs │ ├── lib.rs │ ├── position.rs │ ├── prelude.rs │ ├── tech_type.rs │ ├── unit_type.rs │ ├── upgrade_type.rs │ └── weapon_type.rs └── wrapper.h ├── example_bot ├── .cargo │ └── config.toml ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── resources └── test │ ├── (2)Benzene.scx_frame0_buffer.bin │ ├── (2)Destination.scx_frame0_buffer.bin │ ├── (2)Heartbreak Ridge.scx_frame0_buffer.bin │ ├── (3)Neo Moon Glaive.scx_frame0_buffer.bin │ ├── (3)Tau Cross.scx_frame0_buffer.bin │ ├── (4)Andromeda.scx_frame0_buffer.bin │ ├── (4)Circuit Breaker.scx_frame0_buffer.bin │ ├── (4)Electric Circuit.scx_frame0_buffer.bin │ ├── (4)Empire of the Sun.scm_frame0_buffer.bin │ ├── (4)Fighting Spirit.scx_frame0_buffer.bin │ ├── (4)Icarus.scm_frame0_buffer.bin │ ├── (4)Jade.scx_frame0_buffer.bin │ ├── (4)La Mancha1.1.scx_frame0_buffer.bin │ ├── (4)Python.scx_frame0_buffer.bin │ └── (4)Roadrunner.scx_frame0_buffer.bin └── src ├── aimodule.rs ├── bullet.rs ├── bwem.rs ├── bwem ├── area.rs ├── base.rs ├── cp.rs ├── defs.rs ├── graph.rs ├── map.rs ├── neutral.rs └── tiles.rs ├── can_do.rs ├── client.rs ├── command.rs ├── force.rs ├── game.rs ├── lib.rs ├── player.rs ├── predicate.rs ├── projected.rs ├── region.rs ├── shm.rs ├── sma └── mod.rs ├── types.rs └── unit.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '15 17 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'cpp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Build 20 | run: cargo build 21 | - name: Run tests 22 | run: cargo test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .vscode 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "bwapi"] 2 | path = bwapi 3 | url = https://github.com/bwapi/bwapi.git 4 | [submodule "bwapi_wrapper/bwapi"] 5 | path = bwapi_wrapper/bwapi 6 | url = https://github.com/bwapi/bwapi.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: windows 2 | language: rust 3 | rust: stable 4 | cache: cargo 5 | env: LIBCLANG_PATH="C:\\Program Files\\LLVM\\\\bin" -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsbwapi" 3 | version.workspace = true 4 | authors = ["Dennis Waldherr "] 5 | description = "BWAPI client to write AI players for Starcraft Broodwar" 6 | edition = "2021" 7 | license = "MIT OR Apache-2.0" 8 | homepage = "https://github.com/Bytekeeper/rsbwapi/" 9 | repository = "https://github.com/Bytekeeper/rsbwapi/" 10 | readme = "README.md" 11 | 12 | [workspace] 13 | members = ["bwapi_wrapper", "example_bot"] 14 | 15 | [workspace.package] 16 | version = "0.3.4" 17 | 18 | [workspace.dependencies] 19 | num-traits = "0.2" 20 | num-derive = "0.4" 21 | derive_more = { version = "1.0.0", features = ["full"] } 22 | image = "0.25" 23 | imageproc = "0.25" 24 | 25 | 26 | [package.metadata.docs.rs] 27 | targets = ["x86_64-pc-windows-msvc"] 28 | 29 | 30 | [dependencies] 31 | num-traits.workspace = true 32 | num-derive.workspace = true 33 | derive_more.workspace = true 34 | bwapi_wrapper = { path = "bwapi_wrapper", version = "0.3.3" } 35 | winapi = { version = "0.3", features = ["winbase", "memoryapi", "handleapi"] } 36 | memchr = "2.7" 37 | rstar = "0.12" 38 | itertools = "0.13" 39 | ahash = "0.8" 40 | image = { workspace = true, optional = true } 41 | imageproc = { workspace = true, optional = true } 42 | rusttype = { version = "0.9", optional = true } 43 | 44 | metered = { version = "0.9", optional = true} 45 | serde = { version = "1.0", optional = true} 46 | 47 | 48 | [features] 49 | metrics = ["metered", "serde"] 50 | debug_draw = ["image", "imageproc", "rusttype"] 51 | 52 | [dev-dependencies] 53 | inflate = "0.4" 54 | image.workspace = true 55 | imageproc.workspace = true 56 | rusttype = "0.9" 57 | 58 | [profile.dev.package."*"] 59 | opt-level=3 60 | -------------------------------------------------------------------------------- /Hack-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/Hack-Regular.ttf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation](https://docs.rs/rsbwapi/badge.svg)](https://docs.rs/rsbwapi) 2 | 3 | # rsbwapi 4 | 5 | `rsbwapi` is a Rust library for building artificial intelligence (AI) for the popular real-time strategy game Starcraft Broodwar. The library allows you to create and control Starcraft Broodwar bots through the Broodwar API (BWAPI). 6 | 7 | If you're not familiar with BWAPI, it is a community-developed project that provides a C++ interface to interact with Starcraft Broodwar. You can find more information about BWAPI [here](https://github.com/bwapi/bwapi). 8 | 9 | To get started with `rsbwapi`, check out the documentation on [docs.rs](https://docs.rs/rsbwapi). Also check out the simple [ExampleBot](https://github.com/Bytekeeper/rsbwapi/tree/master/example_bot). Or take a look at my bot [Styx2](https://github.com/Bytekeeper/Styx2). 10 | 11 | You may want to join the [SSCAIT Discord](https://discord.gg/frDVAwk), which is a community of Starcraft AI enthusiasts who run regular bot tournaments. You can also check out the [Basil Ladder](https://www.basil-ladder.net/) which is based on SSCAIT but runs a lot more games. 12 | 13 | For more information on Starcraft AI development, you can visit the [SSCAIT website](http://www.sscaitournament.com/). There should be enough information to get you started. 14 | 15 | If you have any questions or feedback, feel free to create an issue on the `rsbwapi` GitHub repository. 16 | -------------------------------------------------------------------------------- /bwapi_wrapper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bwapi_wrapper" 3 | version.workspace = true 4 | authors = ["Dennis Waldherr "] 5 | edition = "2021" 6 | homepage = "https://github.com/Bytekeeper/rsbwapi/" 7 | license = "LGPL-3.0-or-later" 8 | repository = "https://github.com/Bytekeeper/rsbwapi/" 9 | description = "Bindings to BWAPI" 10 | exclude = ["bwapi/apps", "bwapi/Documentation", "bwapi/Release_Binary"] 11 | readme = "README.md" 12 | 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [package.metadata.docs.rs] 17 | targets = ["x86_64-pc-windows-msvc"] 18 | 19 | [dependencies] 20 | num-traits.workspace = true 21 | num-derive.workspace = true 22 | derive_more.workspace = true 23 | 24 | 25 | [build-dependencies] 26 | # bindgen = "0.69" 27 | # regex = "1" 28 | 29 | -------------------------------------------------------------------------------- /bwapi_wrapper/README.md: -------------------------------------------------------------------------------- 1 | # This is not the crate you are looking for 2 | This crate provides a binding to some of the data structs of BWAPI and some additional required types. 3 | 4 | It can be used to create a client for [BWAPI](https://bwapi.github.io/) (no AI Module mind you). And it is! With the [rsbwapi](https://crates.io/crates/rsbwapi) crate. 5 | Use that instead. -------------------------------------------------------------------------------- /bwapi_wrapper/Windows.h: -------------------------------------------------------------------------------- 1 | typedef void *HANDLE; -------------------------------------------------------------------------------- /bwapi_wrapper/build.rs: -------------------------------------------------------------------------------- 1 | // use bindgen::Formatter; 2 | // use regex::Regex; 3 | // use std::env; 4 | // use std::fs::File; 5 | // use std::io::Write; 6 | // use std::path::PathBuf; 7 | 8 | fn main() { 9 | // To "fix" it not working on github and to reduce build requirements - this will not be called 10 | // in every build. 11 | /* 12 | should_replace1(); 13 | should_replace2(); 14 | 15 | // The bindgen::Builder is the main entry point 16 | // to bindgen, and lets you build up options for 17 | // the resulting bindings. 18 | let bindings = bindgen::Builder::default() 19 | .clang_arg("-xc++") 20 | .clang_arg("-std=c++14") 21 | .clang_arg("-Ibwapi/bwapi/include") 22 | .clang_arg("-I.") 23 | .default_enum_style(bindgen::EnumVariation::Rust { 24 | non_exhaustive: false, 25 | }) 26 | .allowlist_type("BWAPI::.*") 27 | // .whitelist_type("BWAPI::.*GameTable") 28 | // .whitelist_type("BWAPI::.*Enum") 29 | .ignore_methods() 30 | .ignore_functions() 31 | .opaque_type("std::.*") 32 | .formatter(Formatter::Rustfmt) 33 | // .derive_default(true) 34 | .derive_eq(true) 35 | .derive_hash(true) 36 | // .disable_name_namespacing() 37 | // The input header we would like to generate 38 | // bindings for. 39 | .header("wrapper.h") 40 | // Finish the builder and generate the bindings. 41 | .generate() 42 | // Unwrap the Result and panic on failure. 43 | .expect("Unable to generate bindings"); 44 | 45 | // Write the bindings to the $OUT_DIR/bindings.rs file. 46 | todo!("Target src/"); 47 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 48 | let result = bindings.to_string(); 49 | let mut file = File::create(out_path.join("bindings.rs")).unwrap(); 50 | let re = Regex::new(r"#\s*\[\s*derive\s*\((?P[^)]+)\)\s*\]\s*pub\s+enum").unwrap(); 51 | let changed = re.replace_all(&result, "#[derive($d, FromPrimitive)]\npub enum"); 52 | assert_ne!(changed, result, "Could not add FromPrimitive to bindings!"); 53 | file.write_all(changed.as_bytes()) 54 | .expect("Couldn't write bindings!"); 55 | */ 56 | } 57 | 58 | // fn should_replace1() { 59 | // // GIVEN 60 | // let test = "# [ derive ( Debug , Copy , Clone , PartialEq , Eq , Hash ) ] pub enum std_deque__bindgen_ty_1"; 61 | 62 | // // WHEN 63 | // let re = Regex::new(r"#\s*\[\s*derive\s*\((?P[^)]+)\)\s*\]\s*pub\s+enum").unwrap(); 64 | // let changed = re.replace_all(test, "#[derive($d, FromPrimitive)]\npub enum"); 65 | 66 | // // THEN 67 | // assert_eq!("#[derive( Debug , Copy , Clone , PartialEq , Eq , Hash , FromPrimitive)]\npub enum std_deque__bindgen_ty_1", changed); 68 | // } 69 | 70 | // fn should_replace2() { 71 | // // GIVEN 72 | // let test = "# [ derive ( Debug , Copy , Clone , PartialEq , Eq , Hash ) ] pub enum BWAPI_Text_Size_Enum # [ derive ( Debug , Copy , Clone , PartialEq , Eq , Hash ) ] pub enum BWAPIC_CommandType_Enum "; 73 | 74 | // // WHEN 75 | // let re = Regex::new(r"#\s*\[\s*derive\s*\((?P[^)]+)\)\s*\]\s*pub\s+enum").unwrap(); 76 | // let changed = re.replace_all(test, "#[derive($d, FromPrimitive)]\npub enum"); 77 | 78 | // // THEN 79 | // assert_eq!("#[derive( Debug , Copy , Clone , PartialEq , Eq , Hash , FromPrimitive)]\npub enum BWAPI_Text_Size_Enum #[derive( Debug , Copy , Clone , PartialEq , Eq , Hash , FromPrimitive)]\npub enum BWAPIC_CommandType_Enum ", changed); 80 | // } 81 | -------------------------------------------------------------------------------- /bwapi_wrapper/src/command.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use crate::TypeFrom; 3 | 4 | impl UnitCommand { 5 | pub fn get_type(&self) -> UnitCommandType { 6 | UnitCommandType::new(self.type_._base as i32) 7 | } 8 | 9 | pub fn get_unit_type(&self) -> UnitType { 10 | match self.get_type() { 11 | UnitCommandType::Build 12 | | UnitCommandType::Build_Addon 13 | | UnitCommandType::Train 14 | | UnitCommandType::Morph => UnitType::new(self.extra), 15 | _ => UnitType::None, 16 | } 17 | } 18 | 19 | pub fn get_target_position(&self) -> Position { 20 | match self.get_type() { 21 | UnitCommandType::Build | UnitCommandType::Land | UnitCommandType::Place_COP => { 22 | TilePosition { 23 | x: self.x, 24 | y: self.y, 25 | } 26 | .to_position() 27 | } 28 | _ => Position { 29 | x: self.x, 30 | y: self.y, 31 | }, 32 | } 33 | } 34 | 35 | pub fn get_target_tile_position(&self) -> TilePosition { 36 | match self.get_type() { 37 | UnitCommandType::Build | UnitCommandType::Land | UnitCommandType::Place_COP => { 38 | TilePosition { 39 | x: self.x, 40 | y: self.y, 41 | } 42 | } 43 | _ => Position { 44 | x: self.x, 45 | y: self.y, 46 | } 47 | .to_tile_position(), 48 | } 49 | } 50 | 51 | pub fn get_tech_type(&self) -> TechType { 52 | match self.get_type() { 53 | UnitCommandType::Research 54 | | UnitCommandType::Use_Tech 55 | | UnitCommandType::Use_Tech_Position 56 | | UnitCommandType::Use_Tech_Unit => TechType::new(self.extra), 57 | _ => TechType::None, 58 | } 59 | } 60 | 61 | pub fn get_upgrade_type(&self) -> UpgradeType { 62 | if self.get_type() == UnitCommandType::Upgrade { 63 | UpgradeType::new(self.extra) 64 | } else { 65 | UpgradeType::None 66 | } 67 | } 68 | 69 | pub fn get_slot(&self) -> Option { 70 | if self.get_type() == UnitCommandType::Cancel_Train_Slot { 71 | Some(self.extra) 72 | } else { 73 | None 74 | } 75 | } 76 | 77 | pub fn is_queued(&self) -> bool { 78 | match self.get_type() { 79 | UnitCommandType::Attack_Move 80 | | UnitCommandType::Attack_Unit 81 | | UnitCommandType::Move 82 | | UnitCommandType::Patrol 83 | | UnitCommandType::Hold_Position 84 | | UnitCommandType::Stop 85 | | UnitCommandType::Follow 86 | | UnitCommandType::Gather 87 | | UnitCommandType::Return_Cargo 88 | | UnitCommandType::Repair 89 | | UnitCommandType::Load 90 | | UnitCommandType::Unload_All 91 | | UnitCommandType::Unload_All_Position 92 | | UnitCommandType::Right_Click_Position 93 | | UnitCommandType::Right_Click_Unit => self.extra != 0, 94 | _ => false, 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /bwapi_wrapper/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | use crate::prelude::{UnitType, UpgradeType}; 5 | 6 | pub mod command; 7 | pub mod tech_type; 8 | pub mod unit_type; 9 | pub mod upgrade_type; 10 | pub mod weapon_type; 11 | 12 | pub mod position; 13 | pub mod prelude; 14 | 15 | #[allow(clippy::all)] 16 | mod bindings { 17 | use num_derive::FromPrimitive; 18 | include!("bindings.rs"); 19 | // include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 20 | } 21 | pub use bindings::*; 22 | 23 | pub trait TypeFrom { 24 | fn new(i: i32) -> Self; 25 | } 26 | 27 | impl TypeFrom for T { 28 | fn new(i: i32) -> Self { 29 | Self::from_i32(i).unwrap() 30 | } 31 | } 32 | 33 | impl UnitType { 34 | pub fn is_successor_of(&self, type_: UnitType) -> bool { 35 | if type_ == *self { 36 | return true; 37 | } 38 | match type_ { 39 | UnitType::Zerg_Hatchery => *self == UnitType::Zerg_Lair || *self == UnitType::Zerg_Hive, 40 | UnitType::Zerg_Lair => *self == UnitType::Zerg_Hive, 41 | UnitType::Zerg_Spire => *self == UnitType::Zerg_Greater_Spire, 42 | _ => false, 43 | } 44 | } 45 | } 46 | 47 | impl Default for UnitType { 48 | fn default() -> Self { 49 | Self::None 50 | } 51 | } 52 | 53 | // DEFAULTS 54 | const DEFAULT_ORE_COST_BASE: [i32; UpgradeType::MAX as usize] = 55 | // same as default gas cost base 56 | [ 57 | 100, 100, 150, 150, 150, 100, 150, 100, 100, 100, 100, 100, 100, 100, 100, 200, 150, 100, 58 | 200, 150, 100, 150, 200, 150, 200, 150, 150, 100, 200, 150, 150, 150, 150, 150, 150, 200, 59 | 200, 200, 150, 150, 150, 100, 200, 100, 150, 0, 0, 100, 100, 150, 150, 150, 150, 200, 100, 60 | 0, 0, 0, 0, 0, 0, 0, 0, 61 | ]; 62 | 63 | const DEFAULT_TIME_COST_BASE: [i32; UpgradeType::MAX as usize] = [ 64 | 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 65 | 1500, 1500, 0, 2500, 2500, 2500, 2500, 2500, 2400, 2000, 2000, 1500, 1500, 1500, 1500, 2500, 66 | 2500, 2500, 2000, 2500, 2500, 2500, 2000, 2000, 2500, 2500, 2500, 1500, 2500, 0, 0, 2500, 2500, 67 | 2500, 2500, 2500, 2000, 2000, 2000, 0, 0, 0, 0, 0, 0, 0, 0, 68 | ]; 69 | 70 | mod upgrade_internals { 71 | use crate::prelude::{UnitType, UpgradeType}; 72 | use crate::BWAPI_UnitTypes_Enum_Enum::*; 73 | 74 | pub(crate) const REQUIREMENTS: [[UnitType; UpgradeType::MAX as usize]; 3] = [ 75 | // Level 1 76 | [ 77 | None, 78 | None, 79 | None, 80 | None, 81 | None, 82 | None, 83 | None, 84 | None, 85 | None, 86 | None, 87 | None, 88 | None, 89 | None, 90 | None, 91 | None, 92 | None, 93 | None, 94 | None, 95 | None, 96 | None, 97 | None, 98 | None, 99 | None, 100 | None, 101 | None, 102 | None, 103 | None, 104 | None, 105 | Zerg_Hive, 106 | None, 107 | None, 108 | None, 109 | None, 110 | None, 111 | None, 112 | None, 113 | None, 114 | None, 115 | None, 116 | None, 117 | None, 118 | None, 119 | None, 120 | None, 121 | None, 122 | None, 123 | None, 124 | None, 125 | None, 126 | None, 127 | None, 128 | None, 129 | None, 130 | None, 131 | Terran_Armory, 132 | None, 133 | None, 134 | None, 135 | None, 136 | None, 137 | None, 138 | None, 139 | None, 140 | ], 141 | // Level 2 142 | [ 143 | Terran_Science_Facility, 144 | Terran_Science_Facility, 145 | Terran_Science_Facility, 146 | Zerg_Lair, 147 | Zerg_Lair, 148 | Protoss_Templar_Archives, 149 | Protoss_Fleet_Beacon, 150 | Terran_Science_Facility, 151 | Terran_Science_Facility, 152 | Terran_Science_Facility, 153 | Zerg_Lair, 154 | Zerg_Lair, 155 | Zerg_Lair, 156 | Protoss_Templar_Archives, 157 | Protoss_Fleet_Beacon, 158 | Protoss_Cybernetics_Core, 159 | None, 160 | None, 161 | None, 162 | None, 163 | None, 164 | None, 165 | None, 166 | None, 167 | None, 168 | None, 169 | None, 170 | None, 171 | None, 172 | None, 173 | None, 174 | None, 175 | None, 176 | None, 177 | None, 178 | None, 179 | None, 180 | None, 181 | None, 182 | None, 183 | None, 184 | None, 185 | None, 186 | None, 187 | None, 188 | None, 189 | None, 190 | None, 191 | None, 192 | None, 193 | None, 194 | None, 195 | None, 196 | None, 197 | None, 198 | None, 199 | None, 200 | None, 201 | None, 202 | None, 203 | None, 204 | None, 205 | None, 206 | ], 207 | // Level 3 208 | [ 209 | Terran_Science_Facility, 210 | Terran_Science_Facility, 211 | Terran_Science_Facility, 212 | Zerg_Hive, 213 | Zerg_Hive, 214 | Protoss_Templar_Archives, 215 | Protoss_Fleet_Beacon, 216 | Terran_Science_Facility, 217 | Terran_Science_Facility, 218 | Terran_Science_Facility, 219 | Zerg_Hive, 220 | Zerg_Hive, 221 | Zerg_Hive, 222 | Protoss_Templar_Archives, 223 | Protoss_Fleet_Beacon, 224 | Protoss_Cybernetics_Core, 225 | None, 226 | None, 227 | None, 228 | None, 229 | None, 230 | None, 231 | None, 232 | None, 233 | None, 234 | None, 235 | None, 236 | None, 237 | None, 238 | None, 239 | None, 240 | None, 241 | None, 242 | None, 243 | None, 244 | None, 245 | None, 246 | None, 247 | None, 248 | None, 249 | None, 250 | None, 251 | None, 252 | None, 253 | None, 254 | None, 255 | None, 256 | None, 257 | None, 258 | None, 259 | None, 260 | None, 261 | None, 262 | None, 263 | None, 264 | None, 265 | None, 266 | None, 267 | None, 268 | None, 269 | None, 270 | None, 271 | None, 272 | ], 273 | ]; 274 | } 275 | 276 | impl UpgradeType { 277 | pub fn mineral_price(&self, level: i32) -> i32 { 278 | DEFAULT_ORE_COST_BASE[*self as usize] + 0.max(level - 1) * self.mineral_price_factor() 279 | } 280 | 281 | pub fn gas_price(&self, level: i32) -> i32 { 282 | self.mineral_price(level) 283 | } 284 | 285 | pub fn upgrade_time(&self, level: i32) -> i32 { 286 | DEFAULT_TIME_COST_BASE[*self as usize] + 0.max(level - 1) * self.upgrade_time_factor() 287 | } 288 | 289 | pub fn whats_required(&self, level: i32) -> UnitType { 290 | if (1..=3).contains(&level) { 291 | upgrade_internals::REQUIREMENTS[level as usize - 1][*self as usize] 292 | } else { 293 | UnitType::None 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /bwapi_wrapper/src/position.rs: -------------------------------------------------------------------------------- 1 | use crate::UnitType; 2 | use core::ops::{Div, DivAssign, Index, IndexMut, Mul, MulAssign}; 3 | use derive_more::{Add, AddAssign, Display, From, Sub, SubAssign}; 4 | 5 | #[derive( 6 | Default, Debug, Display, Copy, Clone, Eq, PartialEq, Add, Sub, AddAssign, SubAssign, From, Hash, 7 | )] 8 | #[display("Position<{}> ({}, {})", N, x, y)] 9 | pub struct ScaledPosition { 10 | pub x: i32, 11 | pub y: i32, 12 | } 13 | 14 | pub type Position = ScaledPosition<1>; 15 | pub type WalkPosition = ScaledPosition<8>; 16 | pub type TilePosition = ScaledPosition<32>; 17 | 18 | #[derive(Debug, Copy, Clone, PartialEq, Add, Sub, AddAssign, SubAssign, From)] 19 | pub struct Vector2D { 20 | pub x: f64, 21 | pub y: f64, 22 | } 23 | 24 | pub type PositionTuple = (i32, i32); 25 | pub const ORIGIN: Position = Position { x: 0, y: 0 }; 26 | pub const WALK_POSITION_8_DIR: [WalkPosition; 8] = dir_8(1, 1); 27 | pub const WALK_POSITION_4_DIR: [WalkPosition; 4] = dir_4(); 28 | 29 | pub const fn dir_4() -> [ScaledPosition; 4] { 30 | [ 31 | ScaledPosition::::new(0, -1), 32 | ScaledPosition::::new(-1, 0), 33 | ScaledPosition::::new(1, 0), 34 | ScaledPosition::::new(0, 1), 35 | ] 36 | } 37 | 38 | pub const fn dir_8(dx: i32, dy: i32) -> [ScaledPosition; 8] { 39 | [ 40 | ScaledPosition::::new(-dx, -dy), 41 | ScaledPosition::::new(0, -dy), 42 | ScaledPosition::::new(dx, -dy), 43 | ScaledPosition::::new(dx, 0), 44 | ScaledPosition::::new(dx, dy), 45 | ScaledPosition::::new(0, dy), 46 | ScaledPosition::::new(-dx, dy), 47 | ScaledPosition::::new(-dx, 0), 48 | ] 49 | } 50 | 51 | const fn pos_to_pos(pos: ScaledPosition) -> ScaledPosition { 52 | ScaledPosition { 53 | x: pos.x * I / O, 54 | y: pos.y * I / O, 55 | } 56 | } 57 | 58 | impl Position { 59 | pub const fn to_tile_position(self) -> TilePosition { 60 | pos_to_pos(self) 61 | } 62 | 63 | pub const fn to_walk_position(self) -> WalkPosition { 64 | pos_to_pos(self) 65 | } 66 | 67 | pub fn get_approx_distance>(&self, other: P) -> i32 { 68 | let p = other.into(); 69 | let mut max = (self.x - p.x).abs(); 70 | let mut min = (self.y - p.y).abs(); 71 | 72 | if max < min { 73 | core::mem::swap(&mut max, &mut min); 74 | } 75 | 76 | if min <= (max >> 2) { 77 | return max; 78 | } 79 | 80 | let min_calc = (3 * min) >> 3; 81 | (min_calc >> 5) + min_calc + max - (max >> 4) - (max >> 6) 82 | } 83 | } 84 | 85 | impl TilePosition { 86 | pub const fn to_position(self) -> Position { 87 | pos_to_pos(self) 88 | } 89 | 90 | pub const fn to_walk_position(self) -> WalkPosition { 91 | pos_to_pos(self) 92 | } 93 | 94 | pub const fn center(self) -> Position { 95 | Position::new(self.x * 32 + 16, self.y * 32 + 16) 96 | } 97 | } 98 | 99 | impl WalkPosition { 100 | pub const fn to_tile_position(self) -> TilePosition { 101 | pos_to_pos(self) 102 | } 103 | 104 | pub const fn to_position(self) -> Position { 105 | pos_to_pos(self) 106 | } 107 | 108 | pub const fn center(self) -> Position { 109 | Position::new(self.x * 8 + 4, self.y * 8 + 4) 110 | } 111 | } 112 | 113 | impl Vector2D { 114 | pub const fn new(x: f64, y: f64) -> Self { 115 | Self { x, y } 116 | } 117 | } 118 | 119 | pub trait PositionValidator { 120 | fn is_valid(&self, pos: ScaledPosition) -> bool; 121 | } 122 | 123 | impl ScaledPosition { 124 | pub fn new_checked(validator: impl PositionValidator, x: i32, y: i32) -> Option { 125 | let pos = Self::new(x, y); 126 | if validator.is_valid(pos) { 127 | Some(pos) 128 | } else { 129 | None 130 | } 131 | } 132 | 133 | pub const fn new(x: i32, y: i32) -> Self { 134 | Self { x, y } 135 | } 136 | 137 | pub fn is_valid(self, validator: &impl PositionValidator) -> bool { 138 | validator.is_valid(self) 139 | } 140 | 141 | pub const fn distance_squared(&self, other: Self) -> u32 { 142 | let dx = self.x - other.x; 143 | let dy = self.y - other.y; 144 | (dx * dx + dy * dy) as u32 145 | } 146 | 147 | pub fn distance(&self, other: Self) -> f64 { 148 | (self.distance_squared(other) as f64).sqrt() 149 | } 150 | 151 | pub fn chebyshev_distance(&self, other: Self) -> u32 { 152 | (self.x - other.x).abs().max((self.y - other.y).abs()) as u32 153 | } 154 | } 155 | 156 | impl From for TilePosition { 157 | fn from(ut: UnitType) -> Self { 158 | ut.tile_size() 159 | } 160 | } 161 | 162 | impl From> for PositionTuple { 163 | fn from(pos: ScaledPosition) -> Self { 164 | (pos.x, pos.y) 165 | } 166 | } 167 | 168 | impl Mul for ScaledPosition { 169 | type Output = Self; 170 | 171 | fn mul(self, other: i32) -> Self::Output { 172 | Self::Output { 173 | x: self.x * other, 174 | y: self.y * other, 175 | } 176 | } 177 | } 178 | 179 | impl Mul> for i32 { 180 | type Output = ScaledPosition; 181 | 182 | fn mul(self, other: ScaledPosition) -> Self::Output { 183 | Self::Output { 184 | x: self * other.x, 185 | y: self * other.y, 186 | } 187 | } 188 | } 189 | 190 | impl MulAssign for ScaledPosition { 191 | fn mul_assign(&mut self, rhs: i32) { 192 | self.x *= rhs; 193 | self.y *= rhs; 194 | } 195 | } 196 | 197 | impl DivAssign for ScaledPosition { 198 | fn div_assign(&mut self, rhs: i32) { 199 | self.x /= rhs; 200 | self.y /= rhs; 201 | } 202 | } 203 | 204 | impl Div for ScaledPosition { 205 | type Output = Self; 206 | fn div(self, other: i32) -> Self::Output { 207 | Self::Output { 208 | x: self.x / other, 209 | y: self.y / other, 210 | } 211 | } 212 | } 213 | 214 | impl Sub for ScaledPosition { 215 | type Output = Self; 216 | 217 | fn sub(self, other: PositionTuple) -> Self::Output { 218 | Self::Output { 219 | x: self.x - other.0, 220 | y: self.y - other.1, 221 | } 222 | } 223 | } 224 | 225 | impl Add for ScaledPosition { 226 | type Output = Self; 227 | 228 | fn add(self, other: PositionTuple) -> Self::Output { 229 | Self::Output { 230 | x: self.x + other.0, 231 | y: self.y + other.1, 232 | } 233 | } 234 | } 235 | 236 | impl Add for ScaledPosition { 237 | type Output = Self; 238 | 239 | fn add(self, other: i32) -> Self::Output { 240 | Self::Output { 241 | x: self.x + other, 242 | y: self.y + other, 243 | } 244 | } 245 | } 246 | 247 | impl Sub for ScaledPosition { 248 | type Output = Self; 249 | 250 | fn sub(self, other: i32) -> Self::Output { 251 | Self::Output { 252 | x: self.x - other, 253 | y: self.y - other, 254 | } 255 | } 256 | } 257 | 258 | pub trait PositionIndexed { 259 | // empty 260 | } 261 | 262 | impl, const N: i32, const M: usize> Index> for Vec<[T; M]> { 263 | type Output = T; 264 | 265 | fn index(&self, index: ScaledPosition) -> &Self::Output { 266 | &(self as &Vec<[T; M]>)[index.y as usize][index.x as usize] 267 | } 268 | } 269 | 270 | impl, const N: i32, const M: usize> IndexMut> 271 | for Vec<[T; M]> 272 | { 273 | fn index_mut(&mut self, index: ScaledPosition) -> &mut Self::Output { 274 | &mut (self as &mut Vec<[T; M]>)[index.y as usize][index.x as usize] 275 | } 276 | } 277 | 278 | impl, const N: i32, const M: usize> Index> for [[T; M]] { 279 | type Output = T; 280 | 281 | fn index(&self, index: ScaledPosition) -> &Self::Output { 282 | &(self as &[[T; M]])[index.y as usize][index.x as usize] 283 | } 284 | } 285 | 286 | impl, const N: i32, const M: usize> IndexMut> 287 | for [[T; M]] 288 | { 289 | fn index_mut(&mut self, index: ScaledPosition) -> &mut Self::Output { 290 | &mut (self as &mut [[T; M]])[index.y as usize][index.x as usize] 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /bwapi_wrapper/src/prelude.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | pub use crate::position::*; 4 | pub type CoordinateType = BWAPI_CoordinateType_Enum; 5 | pub type Race = crate::BWAPI_Races_Enum_Enum; 6 | pub type UnitType = crate::BWAPI_UnitTypes_Enum_Enum; 7 | pub type WeaponType = crate::BWAPI_WeaponTypes_Enum_Enum; 8 | pub type TechType = BWAPI_TechTypes_Enum_Enum; 9 | pub type UnitSizeType = BWAPI_UnitSizeTypes_Enum_Enum; 10 | pub type ExplosionType = BWAPI_ExplosionTypes_Enum_Enum; 11 | pub type DamageType = BWAPI_DamageTypes_Enum_Enum; 12 | 13 | pub type UpgradeType = crate::BWAPI_UpgradeTypes_Enum_Enum; 14 | pub type Order = BWAPI_Orders_Enum_Enum; 15 | pub type Flag = BWAPI_Flag_Enum; 16 | pub type UnitCommandType = BWAPI_UnitCommandTypes_Enum_Enum; 17 | pub type Error = BWAPI_Errors_Enum_Enum; 18 | pub type Key = BWAPI_Key; 19 | pub type MouseButton = BWAPI_MouseButton; 20 | pub type UnitCommand = BWAPIC_UnitCommand; 21 | -------------------------------------------------------------------------------- /bwapi_wrapper/src/tech_type.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | pub(crate) struct TechTypeData { 3 | pub(crate) mineral_price: i32, 4 | pub(crate) gas_price: i32, 5 | pub(crate) what_uses: &'static [UnitType], 6 | pub(crate) research_time: i32, 7 | pub(crate) energy_cost: i32, 8 | pub(crate) weapon: WeaponType, 9 | pub(crate) targets_unit: bool, 10 | pub(crate) targets_position: bool, 11 | pub(crate) order: Order, 12 | pub(crate) what_researches: UnitType, 13 | pub(crate) required_unit: UnitType, 14 | pub(crate) race: Race, 15 | pub(crate) name: &'static str, 16 | } 17 | pub(crate) static TECH_TYPE_DATA: [TechTypeData; 38] = [ 18 | TechTypeData { 19 | mineral_price: 100, 20 | gas_price: 100, 21 | what_uses: &[ 22 | UnitType::Terran_Marine, 23 | UnitType::Terran_Firebat, 24 | UnitType::Hero_Jim_Raynor_Marine, 25 | UnitType::Hero_Gui_Montag, 26 | ], 27 | research_time: 1200, 28 | energy_cost: 0, 29 | weapon: WeaponType::None, 30 | targets_unit: false, 31 | targets_position: false, 32 | order: Order::None, 33 | what_researches: UnitType::Terran_Academy, 34 | required_unit: UnitType::None, 35 | race: Race::Terran, 36 | name: "Stim_Packs", 37 | }, 38 | TechTypeData { 39 | mineral_price: 200, 40 | gas_price: 200, 41 | what_uses: &[ 42 | UnitType::Terran_Ghost, 43 | UnitType::Hero_Alexei_Stukov, 44 | UnitType::Hero_Infested_Duran, 45 | UnitType::Hero_Samir_Duran, 46 | UnitType::Hero_Sarah_Kerrigan, 47 | ], 48 | research_time: 1500, 49 | energy_cost: 100, 50 | weapon: WeaponType::Lockdown, 51 | targets_unit: true, 52 | targets_position: false, 53 | order: Order::CastLockdown, 54 | what_researches: UnitType::Terran_Covert_Ops, 55 | required_unit: UnitType::None, 56 | race: Race::Terran, 57 | name: "Lockdown", 58 | }, 59 | TechTypeData { 60 | mineral_price: 200, 61 | gas_price: 200, 62 | what_uses: &[UnitType::Terran_Science_Vessel, UnitType::Hero_Magellan], 63 | research_time: 1800, 64 | energy_cost: 100, 65 | weapon: WeaponType::EMP_Shockwave, 66 | targets_unit: true, 67 | targets_position: true, 68 | order: Order::CastEMPShockwave, 69 | what_researches: UnitType::Terran_Science_Facility, 70 | required_unit: UnitType::None, 71 | race: Race::Terran, 72 | name: "EMP_Shockwave", 73 | }, 74 | TechTypeData { 75 | mineral_price: 100, 76 | gas_price: 100, 77 | what_uses: &[UnitType::Terran_Vulture, UnitType::Hero_Jim_Raynor_Vulture], 78 | research_time: 1200, 79 | energy_cost: 0, 80 | weapon: WeaponType::Spider_Mines, 81 | targets_unit: false, 82 | targets_position: true, 83 | order: Order::PlaceMine, 84 | what_researches: UnitType::Terran_Machine_Shop, 85 | required_unit: UnitType::None, 86 | race: Race::Terran, 87 | name: "Spider_Mines", 88 | }, 89 | TechTypeData { 90 | mineral_price: 0, 91 | gas_price: 0, 92 | what_uses: &[UnitType::Terran_Comsat_Station], 93 | research_time: 0, 94 | energy_cost: 50, 95 | weapon: WeaponType::None, 96 | targets_unit: true, 97 | targets_position: true, 98 | order: Order::CastScannerSweep, 99 | what_researches: UnitType::None, 100 | required_unit: UnitType::None, 101 | race: Race::Terran, 102 | name: "Scanner_Sweep", 103 | }, 104 | TechTypeData { 105 | mineral_price: 150, 106 | gas_price: 150, 107 | what_uses: &[ 108 | UnitType::Terran_Siege_Tank_Tank_Mode, 109 | UnitType::Terran_Siege_Tank_Siege_Mode, 110 | UnitType::Hero_Edmund_Duke_Tank_Mode, 111 | UnitType::Hero_Edmund_Duke_Siege_Mode, 112 | ], 113 | research_time: 1200, 114 | energy_cost: 0, 115 | weapon: WeaponType::None, 116 | targets_unit: false, 117 | targets_position: false, 118 | order: Order::None, 119 | what_researches: UnitType::Terran_Machine_Shop, 120 | required_unit: UnitType::None, 121 | race: Race::Terran, 122 | name: "Tank_Siege_Mode", 123 | }, 124 | TechTypeData { 125 | mineral_price: 0, 126 | gas_price: 0, 127 | what_uses: &[UnitType::Terran_Science_Vessel, UnitType::Hero_Magellan], 128 | research_time: 0, 129 | energy_cost: 100, 130 | weapon: WeaponType::None, 131 | targets_unit: true, 132 | targets_position: false, 133 | order: Order::CastDefensiveMatrix, 134 | what_researches: UnitType::None, 135 | required_unit: UnitType::None, 136 | race: Race::Terran, 137 | name: "Defensive_Matrix", 138 | }, 139 | TechTypeData { 140 | mineral_price: 200, 141 | gas_price: 200, 142 | what_uses: &[UnitType::Terran_Science_Vessel, UnitType::Hero_Magellan], 143 | research_time: 1200, 144 | energy_cost: 75, 145 | weapon: WeaponType::Irradiate, 146 | targets_unit: true, 147 | targets_position: false, 148 | order: Order::CastIrradiate, 149 | what_researches: UnitType::Terran_Science_Facility, 150 | required_unit: UnitType::None, 151 | race: Race::Terran, 152 | name: "Irradiate", 153 | }, 154 | TechTypeData { 155 | mineral_price: 100, 156 | gas_price: 100, 157 | what_uses: &[ 158 | UnitType::Terran_Battlecruiser, 159 | UnitType::Hero_Gerard_DuGalle, 160 | UnitType::Hero_Hyperion, 161 | UnitType::Hero_Norad_II, 162 | ], 163 | research_time: 1800, 164 | energy_cost: 150, 165 | weapon: WeaponType::Yamato_Gun, 166 | targets_unit: true, 167 | targets_position: false, 168 | order: Order::FireYamatoGun, 169 | what_researches: UnitType::Terran_Physics_Lab, 170 | required_unit: UnitType::None, 171 | race: Race::Terran, 172 | name: "Yamato_Gun", 173 | }, 174 | TechTypeData { 175 | mineral_price: 150, 176 | gas_price: 150, 177 | what_uses: &[UnitType::Terran_Wraith, UnitType::Hero_Tom_Kazansky], 178 | research_time: 1500, 179 | energy_cost: 25, 180 | weapon: WeaponType::None, 181 | targets_unit: false, 182 | targets_position: false, 183 | order: Order::None, 184 | what_researches: UnitType::Terran_Control_Tower, 185 | required_unit: UnitType::None, 186 | race: Race::Terran, 187 | name: "Cloaking_Field", 188 | }, 189 | TechTypeData { 190 | mineral_price: 100, 191 | gas_price: 100, 192 | what_uses: &[ 193 | UnitType::Terran_Ghost, 194 | UnitType::Hero_Alexei_Stukov, 195 | UnitType::Hero_Infested_Duran, 196 | UnitType::Hero_Samir_Duran, 197 | UnitType::Hero_Sarah_Kerrigan, 198 | UnitType::Hero_Infested_Kerrigan, 199 | ], 200 | research_time: 1200, 201 | energy_cost: 25, 202 | weapon: WeaponType::None, 203 | targets_unit: false, 204 | targets_position: false, 205 | order: Order::None, 206 | what_researches: UnitType::Terran_Covert_Ops, 207 | required_unit: UnitType::None, 208 | race: Race::Terran, 209 | name: "Personnel_Cloaking", 210 | }, 211 | TechTypeData { 212 | mineral_price: 100, 213 | gas_price: 100, 214 | what_uses: &[ 215 | UnitType::Zerg_Zergling, 216 | UnitType::Zerg_Hydralisk, 217 | UnitType::Zerg_Drone, 218 | UnitType::Zerg_Defiler, 219 | UnitType::Zerg_Infested_Terran, 220 | UnitType::Hero_Unclean_One, 221 | UnitType::Hero_Hunter_Killer, 222 | UnitType::Hero_Devouring_One, 223 | UnitType::Zerg_Lurker, 224 | ], 225 | research_time: 1200, 226 | energy_cost: 0, 227 | weapon: WeaponType::None, 228 | targets_unit: false, 229 | targets_position: false, 230 | order: Order::None, 231 | what_researches: UnitType::Zerg_Hatchery, 232 | required_unit: UnitType::None, 233 | race: Race::Zerg, 234 | name: "Burrowing", 235 | }, 236 | TechTypeData { 237 | mineral_price: 0, 238 | gas_price: 0, 239 | what_uses: &[UnitType::Zerg_Queen, UnitType::Hero_Matriarch], 240 | research_time: 0, 241 | energy_cost: 0, 242 | weapon: WeaponType::None, 243 | targets_unit: true, 244 | targets_position: false, 245 | order: Order::CastInfestation, 246 | what_researches: UnitType::None, 247 | required_unit: UnitType::None, 248 | race: Race::Zerg, 249 | name: "Infestation", 250 | }, 251 | TechTypeData { 252 | mineral_price: 100, 253 | gas_price: 100, 254 | what_uses: &[UnitType::Zerg_Queen, UnitType::Hero_Matriarch], 255 | research_time: 1200, 256 | energy_cost: 150, 257 | weapon: WeaponType::Spawn_Broodlings, 258 | targets_unit: true, 259 | targets_position: false, 260 | order: Order::CastSpawnBroodlings, 261 | what_researches: UnitType::Zerg_Queens_Nest, 262 | required_unit: UnitType::None, 263 | race: Race::Zerg, 264 | name: "Spawn_Broodlings", 265 | }, 266 | TechTypeData { 267 | mineral_price: 0, 268 | gas_price: 0, 269 | what_uses: &[UnitType::Zerg_Defiler, UnitType::Hero_Unclean_One], 270 | research_time: 0, 271 | energy_cost: 100, 272 | weapon: WeaponType::Dark_Swarm, 273 | targets_unit: true, 274 | targets_position: true, 275 | order: Order::CastDarkSwarm, 276 | what_researches: UnitType::None, 277 | required_unit: UnitType::None, 278 | race: Race::Zerg, 279 | name: "Dark_Swarm", 280 | }, 281 | TechTypeData { 282 | mineral_price: 200, 283 | gas_price: 200, 284 | what_uses: &[UnitType::Zerg_Defiler, UnitType::Hero_Unclean_One], 285 | research_time: 1500, 286 | energy_cost: 150, 287 | weapon: WeaponType::Plague, 288 | targets_unit: true, 289 | targets_position: true, 290 | order: Order::CastPlague, 291 | what_researches: UnitType::Zerg_Defiler_Mound, 292 | required_unit: UnitType::None, 293 | race: Race::Zerg, 294 | name: "Plague", 295 | }, 296 | TechTypeData { 297 | mineral_price: 100, 298 | gas_price: 100, 299 | what_uses: &[ 300 | UnitType::Zerg_Defiler, 301 | UnitType::Hero_Unclean_One, 302 | UnitType::Hero_Infested_Kerrigan, 303 | UnitType::Hero_Infested_Duran, 304 | ], 305 | research_time: 1500, 306 | energy_cost: 0, 307 | weapon: WeaponType::Consume, 308 | targets_unit: true, 309 | targets_position: false, 310 | order: Order::CastConsume, 311 | what_researches: UnitType::Zerg_Defiler_Mound, 312 | required_unit: UnitType::None, 313 | race: Race::Zerg, 314 | name: "Consume", 315 | }, 316 | TechTypeData { 317 | mineral_price: 100, 318 | gas_price: 100, 319 | what_uses: &[ 320 | UnitType::Zerg_Queen, 321 | UnitType::Hero_Matriarch, 322 | UnitType::Hero_Infested_Kerrigan, 323 | ], 324 | research_time: 1200, 325 | energy_cost: 75, 326 | weapon: WeaponType::Ensnare, 327 | targets_unit: true, 328 | targets_position: true, 329 | order: Order::CastEnsnare, 330 | what_researches: UnitType::Zerg_Queens_Nest, 331 | required_unit: UnitType::None, 332 | race: Race::Zerg, 333 | name: "Ensnare", 334 | }, 335 | TechTypeData { 336 | mineral_price: 0, 337 | gas_price: 0, 338 | what_uses: &[UnitType::Zerg_Queen, UnitType::Hero_Matriarch], 339 | research_time: 0, 340 | energy_cost: 75, 341 | weapon: WeaponType::Parasite, 342 | targets_unit: true, 343 | targets_position: false, 344 | order: Order::CastParasite, 345 | what_researches: UnitType::None, 346 | required_unit: UnitType::None, 347 | race: Race::Zerg, 348 | name: "Parasite", 349 | }, 350 | TechTypeData { 351 | mineral_price: 200, 352 | gas_price: 200, 353 | what_uses: &[ 354 | UnitType::Protoss_High_Templar, 355 | UnitType::Hero_Tassadar, 356 | UnitType::Hero_Infested_Kerrigan, 357 | ], 358 | research_time: 1800, 359 | energy_cost: 75, 360 | weapon: WeaponType::Psionic_Storm, 361 | targets_unit: true, 362 | targets_position: true, 363 | order: Order::CastPsionicStorm, 364 | what_researches: UnitType::Protoss_Templar_Archives, 365 | required_unit: UnitType::None, 366 | race: Race::Protoss, 367 | name: "Psionic_Storm", 368 | }, 369 | TechTypeData { 370 | mineral_price: 150, 371 | gas_price: 150, 372 | what_uses: &[UnitType::Protoss_High_Templar, UnitType::Hero_Tassadar], 373 | research_time: 1200, 374 | energy_cost: 100, 375 | weapon: WeaponType::None, 376 | targets_unit: true, 377 | targets_position: false, 378 | order: Order::CastHallucination, 379 | what_researches: UnitType::Protoss_Templar_Archives, 380 | required_unit: UnitType::None, 381 | race: Race::Protoss, 382 | name: "Hallucination", 383 | }, 384 | TechTypeData { 385 | mineral_price: 150, 386 | gas_price: 150, 387 | what_uses: &[UnitType::Protoss_Arbiter, UnitType::Hero_Danimoth], 388 | research_time: 1800, 389 | energy_cost: 150, 390 | weapon: WeaponType::None, 391 | targets_unit: true, 392 | targets_position: true, 393 | order: Order::CastRecall, 394 | what_researches: UnitType::Protoss_Arbiter_Tribunal, 395 | required_unit: UnitType::None, 396 | race: Race::Protoss, 397 | name: "Recall", 398 | }, 399 | TechTypeData { 400 | mineral_price: 150, 401 | gas_price: 150, 402 | what_uses: &[UnitType::Protoss_Arbiter, UnitType::Hero_Danimoth], 403 | research_time: 1500, 404 | energy_cost: 100, 405 | weapon: WeaponType::Stasis_Field, 406 | targets_unit: true, 407 | targets_position: true, 408 | order: Order::CastStasisField, 409 | what_researches: UnitType::Protoss_Arbiter_Tribunal, 410 | required_unit: UnitType::None, 411 | race: Race::Protoss, 412 | name: "Stasis_Field", 413 | }, 414 | TechTypeData { 415 | mineral_price: 0, 416 | gas_price: 0, 417 | what_uses: &[UnitType::Protoss_High_Templar], 418 | research_time: 0, 419 | energy_cost: 0, 420 | weapon: WeaponType::None, 421 | targets_unit: true, 422 | targets_position: false, 423 | order: Order::None, 424 | what_researches: UnitType::None, 425 | required_unit: UnitType::None, 426 | race: Race::Protoss, 427 | name: "Archon_Warp", 428 | }, 429 | TechTypeData { 430 | mineral_price: 100, 431 | gas_price: 100, 432 | what_uses: &[UnitType::Terran_Medic], 433 | research_time: 1200, 434 | energy_cost: 50, 435 | weapon: WeaponType::Restoration, 436 | targets_unit: true, 437 | targets_position: false, 438 | order: Order::CastRestoration, 439 | what_researches: UnitType::Terran_Academy, 440 | required_unit: UnitType::None, 441 | race: Race::Terran, 442 | name: "Restoration", 443 | }, 444 | TechTypeData { 445 | mineral_price: 200, 446 | gas_price: 200, 447 | what_uses: &[UnitType::Protoss_Corsair, UnitType::Hero_Raszagal], 448 | research_time: 1200, 449 | energy_cost: 125, 450 | weapon: WeaponType::Disruption_Web, 451 | targets_unit: true, 452 | targets_position: true, 453 | order: Order::CastDisruptionWeb, 454 | what_researches: UnitType::Protoss_Fleet_Beacon, 455 | required_unit: UnitType::None, 456 | race: Race::Protoss, 457 | name: "Disruption_Web", 458 | }, 459 | TechTypeData { 460 | mineral_price: 0, 461 | gas_price: 0, 462 | what_uses: &[], 463 | research_time: 0, 464 | energy_cost: 0, 465 | weapon: WeaponType::None, 466 | targets_unit: false, 467 | targets_position: false, 468 | order: Order::None, 469 | what_researches: UnitType::None, 470 | required_unit: UnitType::None, 471 | race: Race::None, 472 | name: "Unused_26", 473 | }, 474 | TechTypeData { 475 | mineral_price: 200, 476 | gas_price: 200, 477 | what_uses: &[UnitType::Protoss_Dark_Archon], 478 | research_time: 1800, 479 | energy_cost: 150, 480 | weapon: WeaponType::Mind_Control, 481 | targets_unit: true, 482 | targets_position: false, 483 | order: Order::CastMindControl, 484 | what_researches: UnitType::Protoss_Templar_Archives, 485 | required_unit: UnitType::None, 486 | race: Race::Protoss, 487 | name: "Mind_Control", 488 | }, 489 | TechTypeData { 490 | mineral_price: 0, 491 | gas_price: 0, 492 | what_uses: &[UnitType::Protoss_Dark_Templar], 493 | research_time: 0, 494 | energy_cost: 0, 495 | weapon: WeaponType::None, 496 | targets_unit: true, 497 | targets_position: false, 498 | order: Order::None, 499 | what_researches: UnitType::None, 500 | required_unit: UnitType::None, 501 | race: Race::Protoss, 502 | name: "Dark_Archon_Meld", 503 | }, 504 | TechTypeData { 505 | mineral_price: 100, 506 | gas_price: 100, 507 | what_uses: &[UnitType::Protoss_Dark_Archon], 508 | research_time: 1800, 509 | energy_cost: 50, 510 | weapon: WeaponType::Feedback, 511 | targets_unit: true, 512 | targets_position: false, 513 | order: Order::CastFeedback, 514 | what_researches: UnitType::None, 515 | required_unit: UnitType::None, 516 | race: Race::Protoss, 517 | name: "Feedback", 518 | }, 519 | TechTypeData { 520 | mineral_price: 100, 521 | gas_price: 100, 522 | what_uses: &[UnitType::Terran_Medic], 523 | research_time: 1800, 524 | energy_cost: 75, 525 | weapon: WeaponType::Optical_Flare, 526 | targets_unit: true, 527 | targets_position: false, 528 | order: Order::CastOpticalFlare, 529 | what_researches: UnitType::Terran_Academy, 530 | required_unit: UnitType::None, 531 | race: Race::Terran, 532 | name: "Optical_Flare", 533 | }, 534 | TechTypeData { 535 | mineral_price: 100, 536 | gas_price: 100, 537 | what_uses: &[UnitType::Protoss_Dark_Archon], 538 | research_time: 1500, 539 | energy_cost: 100, 540 | weapon: WeaponType::Maelstrom, 541 | targets_unit: true, 542 | targets_position: true, 543 | order: Order::CastMaelstrom, 544 | what_researches: UnitType::Protoss_Templar_Archives, 545 | required_unit: UnitType::None, 546 | race: Race::Protoss, 547 | name: "Maelstrom", 548 | }, 549 | TechTypeData { 550 | mineral_price: 200, 551 | gas_price: 200, 552 | what_uses: &[UnitType::Zerg_Hydralisk], 553 | research_time: 1800, 554 | energy_cost: 0, 555 | weapon: WeaponType::None, 556 | targets_unit: false, 557 | targets_position: false, 558 | order: Order::None, 559 | what_researches: UnitType::Zerg_Hydralisk_Den, 560 | required_unit: UnitType::Zerg_Lair, 561 | race: Race::Zerg, 562 | name: "Lurker_Aspect", 563 | }, 564 | TechTypeData { 565 | mineral_price: 0, 566 | gas_price: 0, 567 | what_uses: &[], 568 | research_time: 0, 569 | energy_cost: 0, 570 | weapon: WeaponType::None, 571 | targets_unit: false, 572 | targets_position: false, 573 | order: Order::None, 574 | what_researches: UnitType::None, 575 | required_unit: UnitType::None, 576 | race: Race::None, 577 | name: "Unused_33", 578 | }, 579 | TechTypeData { 580 | mineral_price: 0, 581 | gas_price: 0, 582 | what_uses: &[UnitType::Terran_Medic], 583 | research_time: 0, 584 | energy_cost: 1, 585 | weapon: WeaponType::None, 586 | targets_unit: true, 587 | targets_position: true, 588 | order: Order::MedicHeal, 589 | what_researches: UnitType::None, 590 | required_unit: UnitType::None, 591 | race: Race::Terran, 592 | name: "Healing", 593 | }, 594 | TechTypeData { 595 | mineral_price: 0, 596 | gas_price: 0, 597 | what_uses: &[], 598 | research_time: 0, 599 | energy_cost: 0, 600 | weapon: WeaponType::None, 601 | targets_unit: false, 602 | targets_position: false, 603 | order: Order::None, 604 | what_researches: UnitType::None, 605 | required_unit: UnitType::None, 606 | race: Race::None, 607 | name: "None", 608 | }, 609 | TechTypeData { 610 | mineral_price: 0, 611 | gas_price: 0, 612 | what_uses: &[UnitType::Terran_Ghost], 613 | research_time: 0, 614 | energy_cost: 0, 615 | weapon: WeaponType::Nuclear_Strike, 616 | targets_unit: true, 617 | targets_position: true, 618 | order: Order::NukePaint, 619 | what_researches: UnitType::None, 620 | required_unit: UnitType::None, 621 | race: Race::Terran, 622 | name: "Nuclear_Strike", 623 | }, 624 | TechTypeData { 625 | mineral_price: 0, 626 | gas_price: 0, 627 | what_uses: &[], 628 | research_time: 0, 629 | energy_cost: 0, 630 | weapon: WeaponType::Unknown, 631 | targets_unit: false, 632 | targets_position: false, 633 | order: Order::Unknown, 634 | what_researches: UnitType::Unknown, 635 | required_unit: UnitType::None, 636 | race: Race::Unknown, 637 | name: "Unknown", 638 | }, 639 | ]; 640 | impl TechType { 641 | fn d(&self) -> &TechTypeData { 642 | &TECH_TYPE_DATA[*self as usize] 643 | } 644 | pub fn mineral_price(&self) -> i32 { 645 | self.d().mineral_price 646 | } 647 | pub fn gas_price(&self) -> i32 { 648 | self.d().gas_price 649 | } 650 | pub fn what_uses(&self) -> &'static [UnitType] { 651 | self.d().what_uses 652 | } 653 | pub fn research_time(&self) -> i32 { 654 | self.d().research_time 655 | } 656 | pub fn energy_cost(&self) -> i32 { 657 | self.d().energy_cost 658 | } 659 | pub fn get_weapon(&self) -> WeaponType { 660 | self.d().weapon 661 | } 662 | pub fn targets_unit(&self) -> bool { 663 | self.d().targets_unit 664 | } 665 | pub fn targets_position(&self) -> bool { 666 | self.d().targets_position 667 | } 668 | pub fn get_order(&self) -> Order { 669 | self.d().order 670 | } 671 | pub fn what_researches(&self) -> UnitType { 672 | self.d().what_researches 673 | } 674 | pub fn required_unit(&self) -> UnitType { 675 | self.d().required_unit 676 | } 677 | pub fn get_race(&self) -> Race { 678 | self.d().race 679 | } 680 | pub fn name(&self) -> &'static str { 681 | self.d().name 682 | } 683 | } 684 | -------------------------------------------------------------------------------- /bwapi_wrapper/wrapper.h: -------------------------------------------------------------------------------- 1 | #include "BWAPI.h" 2 | #include "BWAPI/Client.h" 3 | #include "BWAPI/UnitType.h" 4 | #include "BWAPI/Input.h" -------------------------------------------------------------------------------- /example_bot/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # target="i686-pc-windows-msvc" 3 | target="i686-pc-windows-gnu" 4 | # target="x86_64-pc-windows-gnu" 5 | 6 | [target.i686-pc-windows-msvc] 7 | linker = "lld" 8 | rustflags = [ 9 | "-Lnative=/home/dante/.xwin/crt/lib/x86", 10 | "-Lnative=/home/dante/.xwin/sdk/lib/um/x86", 11 | "-Lnative=/home/dante/.xwin/sdk/lib/ucrt/x86" 12 | ] 13 | -------------------------------------------------------------------------------- /example_bot/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "aho-corasick" 18 | version = "0.7.18" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 21 | dependencies = [ 22 | "memchr", 23 | ] 24 | 25 | [[package]] 26 | name = "ansi_term" 27 | version = "0.12.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 30 | dependencies = [ 31 | "winapi", 32 | ] 33 | 34 | [[package]] 35 | name = "as-slice" 36 | version = "0.1.5" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" 39 | dependencies = [ 40 | "generic-array 0.12.4", 41 | "generic-array 0.13.3", 42 | "generic-array 0.14.6", 43 | "stable_deref_trait", 44 | ] 45 | 46 | [[package]] 47 | name = "atty" 48 | version = "0.2.14" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 51 | dependencies = [ 52 | "hermit-abi", 53 | "libc", 54 | "winapi", 55 | ] 56 | 57 | [[package]] 58 | name = "autocfg" 59 | version = "1.1.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 62 | 63 | [[package]] 64 | name = "bindgen" 65 | version = "0.53.3" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "c72a978d268b1d70b0e963217e60fdabd9523a941457a6c42a7315d15c7e89e5" 68 | dependencies = [ 69 | "bitflags", 70 | "cexpr", 71 | "cfg-if 0.1.10", 72 | "clang-sys", 73 | "clap", 74 | "env_logger", 75 | "lazy_static", 76 | "lazycell", 77 | "log", 78 | "peeking_take_while", 79 | "proc-macro2", 80 | "quote", 81 | "regex", 82 | "rustc-hash", 83 | "shlex", 84 | "which", 85 | ] 86 | 87 | [[package]] 88 | name = "bitflags" 89 | version = "1.3.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 92 | 93 | [[package]] 94 | name = "bwapi_wrapper" 95 | version = "0.2.3" 96 | dependencies = [ 97 | "bindgen", 98 | "derive_more", 99 | "num-derive", 100 | "num-traits", 101 | "regex", 102 | ] 103 | 104 | [[package]] 105 | name = "byteorder" 106 | version = "1.4.3" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 109 | 110 | [[package]] 111 | name = "cc" 112 | version = "1.0.73" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 115 | 116 | [[package]] 117 | name = "cexpr" 118 | version = "0.4.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" 121 | dependencies = [ 122 | "nom", 123 | ] 124 | 125 | [[package]] 126 | name = "cfg-if" 127 | version = "0.1.10" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 130 | 131 | [[package]] 132 | name = "cfg-if" 133 | version = "1.0.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 136 | 137 | [[package]] 138 | name = "clang-sys" 139 | version = "0.29.3" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" 142 | dependencies = [ 143 | "glob", 144 | "libc", 145 | "libloading", 146 | ] 147 | 148 | [[package]] 149 | name = "clap" 150 | version = "2.34.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 153 | dependencies = [ 154 | "ansi_term", 155 | "atty", 156 | "bitflags", 157 | "strsim", 158 | "textwrap", 159 | "unicode-width", 160 | "vec_map", 161 | ] 162 | 163 | [[package]] 164 | name = "convert_case" 165 | version = "0.4.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 168 | 169 | [[package]] 170 | name = "derive_more" 171 | version = "0.99.17" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 174 | dependencies = [ 175 | "convert_case", 176 | "proc-macro2", 177 | "quote", 178 | "rustc_version", 179 | "syn", 180 | ] 181 | 182 | [[package]] 183 | name = "either" 184 | version = "1.8.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 187 | 188 | [[package]] 189 | name = "env_logger" 190 | version = "0.7.1" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 193 | dependencies = [ 194 | "atty", 195 | "humantime", 196 | "log", 197 | "regex", 198 | "termcolor", 199 | ] 200 | 201 | [[package]] 202 | name = "example_bot" 203 | version = "0.1.0" 204 | dependencies = [ 205 | "rsbwapi", 206 | ] 207 | 208 | [[package]] 209 | name = "generic-array" 210 | version = "0.12.4" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 213 | dependencies = [ 214 | "typenum", 215 | ] 216 | 217 | [[package]] 218 | name = "generic-array" 219 | version = "0.13.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" 222 | dependencies = [ 223 | "typenum", 224 | ] 225 | 226 | [[package]] 227 | name = "generic-array" 228 | version = "0.14.6" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 231 | dependencies = [ 232 | "typenum", 233 | "version_check", 234 | ] 235 | 236 | [[package]] 237 | name = "getrandom" 238 | version = "0.2.7" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 241 | dependencies = [ 242 | "cfg-if 1.0.0", 243 | "libc", 244 | "wasi", 245 | ] 246 | 247 | [[package]] 248 | name = "glob" 249 | version = "0.3.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 252 | 253 | [[package]] 254 | name = "hash32" 255 | version = "0.1.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" 258 | dependencies = [ 259 | "byteorder", 260 | ] 261 | 262 | [[package]] 263 | name = "heapless" 264 | version = "0.6.1" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "634bd4d29cbf24424d0a4bfcbf80c6960129dc24424752a7d1d1390607023422" 267 | dependencies = [ 268 | "as-slice", 269 | "generic-array 0.14.6", 270 | "hash32", 271 | "stable_deref_trait", 272 | ] 273 | 274 | [[package]] 275 | name = "hermit-abi" 276 | version = "0.1.19" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 279 | dependencies = [ 280 | "libc", 281 | ] 282 | 283 | [[package]] 284 | name = "humantime" 285 | version = "1.3.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 288 | dependencies = [ 289 | "quick-error", 290 | ] 291 | 292 | [[package]] 293 | name = "itertools" 294 | version = "0.10.3" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 297 | dependencies = [ 298 | "either", 299 | ] 300 | 301 | [[package]] 302 | name = "lazy_static" 303 | version = "1.4.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 306 | 307 | [[package]] 308 | name = "lazycell" 309 | version = "1.3.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 312 | 313 | [[package]] 314 | name = "libc" 315 | version = "0.2.132" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 318 | 319 | [[package]] 320 | name = "libloading" 321 | version = "0.5.2" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" 324 | dependencies = [ 325 | "cc", 326 | "winapi", 327 | ] 328 | 329 | [[package]] 330 | name = "log" 331 | version = "0.4.17" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 334 | dependencies = [ 335 | "cfg-if 1.0.0", 336 | ] 337 | 338 | [[package]] 339 | name = "memchr" 340 | version = "2.5.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 343 | 344 | [[package]] 345 | name = "nom" 346 | version = "5.1.2" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 349 | dependencies = [ 350 | "memchr", 351 | "version_check", 352 | ] 353 | 354 | [[package]] 355 | name = "num-derive" 356 | version = "0.3.3" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 359 | dependencies = [ 360 | "proc-macro2", 361 | "quote", 362 | "syn", 363 | ] 364 | 365 | [[package]] 366 | name = "num-traits" 367 | version = "0.2.15" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 370 | dependencies = [ 371 | "autocfg", 372 | ] 373 | 374 | [[package]] 375 | name = "once_cell" 376 | version = "1.13.1" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" 379 | 380 | [[package]] 381 | name = "pdqselect" 382 | version = "0.1.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "4ec91767ecc0a0bbe558ce8c9da33c068066c57ecc8bb8477ef8c1ad3ef77c27" 385 | 386 | [[package]] 387 | name = "peeking_take_while" 388 | version = "0.1.2" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 391 | 392 | [[package]] 393 | name = "proc-macro2" 394 | version = "1.0.43" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 397 | dependencies = [ 398 | "unicode-ident", 399 | ] 400 | 401 | [[package]] 402 | name = "quick-error" 403 | version = "1.2.3" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 406 | 407 | [[package]] 408 | name = "quote" 409 | version = "1.0.21" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 412 | dependencies = [ 413 | "proc-macro2", 414 | ] 415 | 416 | [[package]] 417 | name = "regex" 418 | version = "1.6.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 421 | dependencies = [ 422 | "aho-corasick", 423 | "memchr", 424 | "regex-syntax", 425 | ] 426 | 427 | [[package]] 428 | name = "regex-syntax" 429 | version = "0.6.27" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 432 | 433 | [[package]] 434 | name = "rsbwapi" 435 | version = "0.2.5" 436 | dependencies = [ 437 | "ahash", 438 | "bwapi_wrapper", 439 | "derive_more", 440 | "itertools", 441 | "memchr", 442 | "num-derive", 443 | "num-traits", 444 | "rstar", 445 | "winapi", 446 | ] 447 | 448 | [[package]] 449 | name = "rstar" 450 | version = "0.8.4" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "3a45c0e8804d37e4d97e55c6f258bc9ad9c5ee7b07437009dd152d764949a27c" 453 | dependencies = [ 454 | "heapless", 455 | "num-traits", 456 | "pdqselect", 457 | "smallvec", 458 | ] 459 | 460 | [[package]] 461 | name = "rustc-hash" 462 | version = "1.1.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 465 | 466 | [[package]] 467 | name = "rustc_version" 468 | version = "0.4.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 471 | dependencies = [ 472 | "semver", 473 | ] 474 | 475 | [[package]] 476 | name = "semver" 477 | version = "1.0.13" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" 480 | 481 | [[package]] 482 | name = "shlex" 483 | version = "0.1.1" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 486 | 487 | [[package]] 488 | name = "smallvec" 489 | version = "1.9.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 492 | 493 | [[package]] 494 | name = "stable_deref_trait" 495 | version = "1.2.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 498 | 499 | [[package]] 500 | name = "strsim" 501 | version = "0.8.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 504 | 505 | [[package]] 506 | name = "syn" 507 | version = "1.0.99" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 510 | dependencies = [ 511 | "proc-macro2", 512 | "quote", 513 | "unicode-ident", 514 | ] 515 | 516 | [[package]] 517 | name = "termcolor" 518 | version = "1.1.3" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 521 | dependencies = [ 522 | "winapi-util", 523 | ] 524 | 525 | [[package]] 526 | name = "textwrap" 527 | version = "0.11.0" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 530 | dependencies = [ 531 | "unicode-width", 532 | ] 533 | 534 | [[package]] 535 | name = "typenum" 536 | version = "1.15.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 539 | 540 | [[package]] 541 | name = "unicode-ident" 542 | version = "1.0.3" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 545 | 546 | [[package]] 547 | name = "unicode-width" 548 | version = "0.1.9" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 551 | 552 | [[package]] 553 | name = "vec_map" 554 | version = "0.8.2" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 557 | 558 | [[package]] 559 | name = "version_check" 560 | version = "0.9.4" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 563 | 564 | [[package]] 565 | name = "wasi" 566 | version = "0.11.0+wasi-snapshot-preview1" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 569 | 570 | [[package]] 571 | name = "which" 572 | version = "3.1.1" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" 575 | dependencies = [ 576 | "libc", 577 | ] 578 | 579 | [[package]] 580 | name = "winapi" 581 | version = "0.3.9" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 584 | dependencies = [ 585 | "winapi-i686-pc-windows-gnu", 586 | "winapi-x86_64-pc-windows-gnu", 587 | ] 588 | 589 | [[package]] 590 | name = "winapi-i686-pc-windows-gnu" 591 | version = "0.4.0" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 594 | 595 | [[package]] 596 | name = "winapi-util" 597 | version = "0.1.5" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 600 | dependencies = [ 601 | "winapi", 602 | ] 603 | 604 | [[package]] 605 | name = "winapi-x86_64-pc-windows-gnu" 606 | version = "0.4.0" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 609 | -------------------------------------------------------------------------------- /example_bot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example_bot" 3 | version = "0.1.0" 4 | authors = ["Bytekeeper "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rsbwapi = { path = ".." } 11 | -------------------------------------------------------------------------------- /example_bot/src/main.rs: -------------------------------------------------------------------------------- 1 | use rsbwapi::*; 2 | 3 | pub struct MyModule; 4 | 5 | impl MyModule { 6 | fn draw_unit_regions(&self, game: &Game) { 7 | /* Draw BW Regions on all units */ 8 | for u in game.get_all_units() { 9 | let region = game.get_region_at(u.get_position()); 10 | if region.is_none() { 11 | game.draw_text_map(u.get_position(), &"NO REGION".to_string()); 12 | } else { 13 | game.draw_text_map( 14 | u.get_position(), 15 | &format!("r#{:?}", region.unwrap().get_id()), 16 | ) 17 | } 18 | } 19 | } 20 | } 21 | 22 | impl AiModule for MyModule { 23 | fn on_start(&mut self, game: &Game) { 24 | for location in game.get_start_locations() { 25 | println!("{:?}", location); 26 | } 27 | } 28 | 29 | fn on_unit_create(&mut self, _game: &Game, unit: Unit) { 30 | println!("Created Unit {}", unit.get_id()) 31 | } 32 | 33 | fn on_unit_destroy(&mut self, _game: &Game, unit: Unit) { 34 | println!("Destroyed Unit {}", unit.get_id()) 35 | } 36 | 37 | fn on_frame(&mut self, game: &Game) { 38 | let self_ = game.self_().unwrap(); 39 | /* Draw player names */ 40 | let names: Vec = game 41 | .get_players() 42 | .iter() 43 | .map(|p| String::from(p.get_name())) 44 | .collect(); 45 | for (i, name) in names.iter().enumerate() { 46 | game.draw_text_screen((10, (i as i32) * 10 + 20), name); 47 | } 48 | game.draw_text_screen((10, 10), &game.enemy().unwrap().get_name()); 49 | let units = game.get_all_units(); 50 | let my_units = self_.get_units(); 51 | 52 | self.draw_unit_regions(game); 53 | let has_pool = my_units 54 | .iter() 55 | .any(|u| u.get_type() == UnitType::Zerg_Spawning_Pool); 56 | if let Some(u) = units 57 | .iter() 58 | .find(|u| u.get_type() == UnitType::Zerg_Hatchery) 59 | { 60 | if game 61 | .can_make(None, UnitType::Zerg_Zergling) 62 | .unwrap_or(false) 63 | { 64 | u.train(UnitType::Zerg_Zergling).ok(); 65 | } else { 66 | u.train(UnitType::Zerg_Drone).ok(); 67 | } 68 | } 69 | let self_ = game.self_().unwrap(); 70 | if self_.supply_used() == self_.supply_total() { 71 | if let Some(larva) = my_units 72 | .iter() 73 | .find(|u| u.get_type() == UnitType::Zerg_Larva) 74 | { 75 | larva.train(UnitType::Zerg_Overlord).ok(); 76 | } 77 | } 78 | let builder = 79 | if self_.minerals() >= UnitType::Zerg_Spawning_Pool.mineral_price() && !has_pool { 80 | let mut found = false; 81 | let builder = my_units 82 | .iter() 83 | .find(|u| u.get_type() == UnitType::Zerg_Drone) 84 | .expect("drone to build sth"); 85 | 'outer: for y in 0..game.map_height() { 86 | for x in 0..game.map_width() { 87 | if game 88 | .can_build_here(builder, (x, y), UnitType::Zerg_Spawning_Pool, true) 89 | .unwrap_or(false) 90 | { 91 | let tl = TilePosition { x, y }.to_position(); 92 | let br = tl + UnitType::Zerg_Spawning_Pool.tile_size().to_position(); 93 | game.draw_box_map(tl, br, Color::Red, false); 94 | builder.build(UnitType::Zerg_Spawning_Pool, (x, y)).ok(); 95 | found = true; 96 | break 'outer; 97 | } 98 | } 99 | } 100 | if found { 101 | Some(builder) 102 | } else { 103 | None 104 | } 105 | } else { 106 | None 107 | }; 108 | 109 | let mineral = units 110 | .iter() 111 | .find(|u| u.get_type().is_mineral_field() && u.is_visible()); 112 | 113 | if let Some(mineral) = mineral { 114 | if let Some(miner) = game.get_closest_unit( 115 | mineral.get_position(), 116 | |u: &Unit| { 117 | u.get_type() == UnitType::Zerg_Drone 118 | && !u.is_gathering_minerals() 119 | && Some(u) != builder 120 | }, 121 | None, 122 | ) { 123 | miner.gather(mineral).ok(); 124 | } 125 | } else { 126 | println!("No minerals found!"); 127 | } 128 | let enemy = units 129 | .iter() 130 | .find(|u| u.get_player() == game.enemy().unwrap()); 131 | if let Some(enemy) = enemy { 132 | units 133 | .iter() 134 | // .filter(|u| u.get_type() == UnitType::Zerg_Drone) 135 | .for_each(|u| { 136 | // println!("Sending {} to attack {:?}", u.id, enemy.get_type()); 137 | if let Err(err) = u.attack(enemy) { 138 | println!( 139 | "{:?} cannot hit {:?}: {:?}", 140 | u.get_type(), 141 | enemy.get_type(), 142 | err 143 | ); 144 | } 145 | }); 146 | } 147 | 148 | // game.cmd().leave_game(); 149 | 150 | for _bullet in game.get_bullets().iter() { 151 | /* println!( 152 | "Bullet {} of player {:?} of unit {:?}", 153 | bullet.get_id(), 154 | bullet.get_player().map(|p| p.get_name().to_string()), 155 | bullet.get_source().map(|u| u.get_id()) 156 | ); 157 | */ 158 | } 159 | } 160 | } 161 | 162 | fn main() { 163 | rsbwapi::start(|_game| MyModule); 164 | } 165 | -------------------------------------------------------------------------------- /resources/test/(2)Benzene.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(2)Benzene.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(2)Destination.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(2)Destination.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(2)Heartbreak Ridge.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(2)Heartbreak Ridge.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(3)Neo Moon Glaive.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(3)Neo Moon Glaive.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(3)Tau Cross.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(3)Tau Cross.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)Andromeda.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)Andromeda.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)Circuit Breaker.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)Circuit Breaker.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)Electric Circuit.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)Electric Circuit.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)Empire of the Sun.scm_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)Empire of the Sun.scm_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)Fighting Spirit.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)Fighting Spirit.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)Icarus.scm_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)Icarus.scm_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)Jade.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)Jade.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)La Mancha1.1.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)La Mancha1.1.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)Python.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)Python.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /resources/test/(4)Roadrunner.scx_frame0_buffer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bytekeeper/rsbwapi/aa2b52a592cf59d083c3e8b542185ab97c7b3d75/resources/test/(4)Roadrunner.scx_frame0_buffer.bin -------------------------------------------------------------------------------- /src/aimodule.rs: -------------------------------------------------------------------------------- 1 | use crate::game::Game; 2 | use crate::player::Player; 3 | use crate::unit::Unit; 4 | use bwapi_wrapper::prelude::Position; 5 | use std::borrow::Borrow; 6 | 7 | /// Callbacks used by rsbwapi. For safety reasons, all references passed in are only valid for the 8 | /// call duration. 9 | pub trait AiModule { 10 | fn on_end(&mut self, _game: &Game, _winner: bool) {} 11 | fn on_nuke_detect(&mut self, _game: &Game, _position: Position) {} 12 | fn on_player_left(&mut self, _game: &Game, _player: Player) {} 13 | fn on_receive_text(&mut self, _game: &Game, _player: Player, _text: impl Borrow) {} 14 | fn on_save_game(&mut self, _game: &Game, _game_name: impl Borrow) {} 15 | fn on_send_text(&mut self, _game: &Game, _text: impl Borrow) {} 16 | fn on_start(&mut self, _game: &Game) {} 17 | fn on_frame(&mut self, state: &Game); 18 | fn on_unit_create(&mut self, _game: &Game, _unit: Unit) {} 19 | fn on_unit_destroy(&mut self, _game: &Game, _unit: Unit) {} 20 | fn on_unit_discover(&mut self, _game: &Game, _unit: Unit) {} 21 | fn on_unit_complete(&mut self, _game: &Game, _unit: Unit) {} 22 | fn on_unit_evade(&mut self, _game: &Game, _unit: Unit) {} 23 | fn on_unit_hide(&mut self, _game: &Game, _unit: Unit) {} 24 | fn on_unit_morph(&mut self, _game: &Game, _unit: Unit) {} 25 | fn on_unit_renegade(&mut self, _game: &Game, _unit: Unit) {} 26 | fn on_unit_show(&mut self, _game: &Game, _unit: Unit) {} 27 | } 28 | -------------------------------------------------------------------------------- /src/bullet.rs: -------------------------------------------------------------------------------- 1 | use crate::game::Game; 2 | use crate::player::Player; 3 | use crate::projected::Projected; 4 | use crate::unit::Unit; 5 | use bwapi_wrapper::prelude::*; 6 | use bwapi_wrapper::*; 7 | use num_traits::FromPrimitive; 8 | 9 | pub struct Bullet { 10 | id: usize, 11 | inner: Projected, 12 | } 13 | 14 | impl Bullet { 15 | pub(crate) fn new(id: usize, game: Game, data: *const BWAPI_BulletData) -> Self { 16 | Self { 17 | id, 18 | inner: unsafe { Projected::new(game, data) }, 19 | } 20 | } 21 | 22 | pub fn exists(&self) -> bool { 23 | self.inner.exists 24 | } 25 | 26 | pub fn get_angle(&self) -> f64 { 27 | self.inner.angle 28 | } 29 | 30 | pub fn get_player(&self) -> Option { 31 | self.inner.game().get_player(self.inner.player as usize) 32 | } 33 | 34 | pub fn get_id(&self) -> usize { 35 | self.id 36 | } 37 | 38 | pub fn get_position(&self) -> Option { 39 | Some(Position { 40 | x: self.inner.positionX, 41 | y: self.inner.positionY, 42 | }) 43 | } 44 | 45 | pub fn get_remove_timer(&self) -> Option { 46 | if self.inner.removeTimer > 0 { 47 | Some(self.inner.removeTimer) 48 | } else { 49 | None 50 | } 51 | } 52 | 53 | pub fn get_source(&self) -> Option { 54 | self.inner.game().get_unit(self.inner.source as usize) 55 | } 56 | 57 | pub fn get_target(&self) -> Option { 58 | self.inner.game().get_unit(self.inner.target as usize) 59 | } 60 | 61 | pub fn get_target_position(&self) -> Option { 62 | Position::new_checked( 63 | self.inner.game(), 64 | self.inner.targetPositionX, 65 | self.inner.targetPositionY, 66 | ) 67 | } 68 | 69 | pub fn get_type(&self) -> BulletType { 70 | BulletType::from_i32(self.inner.type_).unwrap() 71 | } 72 | 73 | pub fn get_velocity(&self) -> Option { 74 | let result = Vector2D::new(self.inner.velocityX, self.inner.velocityY); 75 | if result.x == 0.0 && result.y == 0.0 { 76 | None 77 | } else { 78 | Some(result) 79 | } 80 | } 81 | 82 | pub fn is_visible(&self, player: &Player) -> bool { 83 | self.inner.isVisible[player.id] 84 | } 85 | } 86 | 87 | impl PartialEq for Bullet { 88 | fn eq(&self, other: &Self) -> bool { 89 | self.id == other.id 90 | } 91 | } 92 | 93 | pub type BulletType = BWAPI_BulletTypes_Enum_Enum; 94 | -------------------------------------------------------------------------------- /src/bwem.rs: -------------------------------------------------------------------------------- 1 | use crate::{Rectangle, WalkPosition}; 2 | 3 | macro_rules! markable { 4 | ($m: ident, $n: ident) => { 5 | use std::sync::atomic::{AtomicI32, Ordering}; 6 | static $m: AtomicI32 = AtomicI32::new(0); 7 | 8 | #[derive(Default)] 9 | pub(crate) struct $n { 10 | last_mark: i32, 11 | } 12 | 13 | impl $n { 14 | pub fn new() -> Self { 15 | Self { last_mark: 0 } 16 | } 17 | 18 | pub fn marked(&self) -> bool { 19 | $m.load(Ordering::Relaxed) == self.last_mark 20 | } 21 | 22 | pub fn set_marked(&mut self) { 23 | self.last_mark = $m.load(Ordering::Relaxed); 24 | } 25 | 26 | pub fn setUnmarked(&mut self) { 27 | self.last_mark = -1; 28 | } 29 | 30 | pub fn unmark_all() { 31 | $m.fetch_add(1, Ordering::Relaxed); 32 | } 33 | } 34 | }; 35 | } 36 | pub mod area; 37 | pub mod base; 38 | pub mod cp; 39 | pub mod defs; 40 | pub mod graph; 41 | pub mod map; 42 | pub mod neutral; 43 | pub mod tiles; 44 | 45 | use crate::{Position, ScaledPosition, TilePosition}; 46 | fn outer_mini_tile_border(tl: WalkPosition, size: WalkPosition) -> Vec { 47 | Rectangle::new(tl - 1, tl + size + 1).border() 48 | } 49 | 50 | fn make_bounding_box_include_point( 51 | top_left: &mut ScaledPosition, 52 | bottom_right: &mut ScaledPosition, 53 | a: ScaledPosition, 54 | ) { 55 | if a.x < top_left.x { 56 | top_left.x = a.x 57 | } 58 | if a.x > bottom_right.x { 59 | bottom_right.x = a.x 60 | } 61 | 62 | if a.y < top_left.y { 63 | top_left.y = a.y 64 | } 65 | if a.y < bottom_right.y { 66 | bottom_right.y = a.y 67 | } 68 | } 69 | 70 | fn make_point_fit_to_bounding_box( 71 | a: &mut ScaledPosition, 72 | top_left: ScaledPosition, 73 | bottom_right: ScaledPosition, 74 | ) { 75 | if a.x < top_left.x { 76 | a.x = top_left.x 77 | } else if a.x > bottom_right.x { 78 | a.x = bottom_right.x 79 | } 80 | 81 | if a.y < top_left.y { 82 | a.y = top_left.y 83 | } else if a.y > bottom_right.y { 84 | a.y = bottom_right.y 85 | } 86 | } 87 | 88 | fn dist_to_rectangle(a: Position, top_left: TilePosition, size: TilePosition) -> i32 { 89 | let bottom_right = (top_left + size).to_position() - 1; 90 | let top_left = top_left.to_position(); 91 | 92 | if a.x >= top_left.x { 93 | if a.x <= bottom_right.x { 94 | if a.y > bottom_right.y { 95 | a.y - bottom_right.y // S 96 | } else if a.y < top_left.y { 97 | top_left.y - a.y // N 98 | } else { 99 | 0 // inside 100 | } 101 | } else { 102 | if a.y > bottom_right.y { 103 | rounded_dist(a, bottom_right) // SE 104 | } else if a.y < top_left.y { 105 | rounded_dist(a, Position::new(bottom_right.y, top_left.y)) 106 | // NE 107 | } else { 108 | a.x - bottom_right.x // E 109 | } 110 | } 111 | } else if a.y > bottom_right.y { 112 | rounded_dist(a, Position::new(top_left.x, bottom_right.y)) // SW 113 | } else if a.y < top_left.y { 114 | rounded_dist(a, top_left) // NW 115 | } else { 116 | top_left.x - a.x // W 117 | } 118 | } 119 | 120 | const fn squared_norm(dx: i32, dy: i32) -> i32 { 121 | dx * dx + dy * dy 122 | } 123 | 124 | fn norm(dx: i32, dy: i32) -> f64 { 125 | (squared_norm(dx, dy) as f64).sqrt() 126 | } 127 | 128 | fn dist(a: ScaledPosition, b: ScaledPosition) -> f64 { 129 | let c = b - a; 130 | norm(c.x, c.y) 131 | } 132 | 133 | fn rounded_dist(a: ScaledPosition, b: ScaledPosition) -> i32 { 134 | (0.5 + dist(a, b)) as i32 135 | } 136 | -------------------------------------------------------------------------------- /src/bwem/area.rs: -------------------------------------------------------------------------------- 1 | use super::{base::*, cp::*, defs::*, graph::Graph, map::*, neutral::*, tiles::*}; 2 | use crate::{bwem::*, *}; 3 | use ahash::AHashMap; 4 | use std::cell::RefCell; 5 | use std::collections::{BinaryHeap, HashMap}; 6 | use std::rc::Rc; 7 | 8 | pub type AreaId = i16; 9 | pub type GroupId = i16; 10 | 11 | pub struct Area { 12 | p_graph: *mut Graph, 13 | id: AreaId, 14 | group_id: GroupId, 15 | top: WalkPosition, 16 | top_left: TilePosition, 17 | bottom_right: TilePosition, 18 | max_altitude: Altitude, 19 | mini_tiles: isize, 20 | tiles: isize, 21 | buildable_tiles: isize, 22 | high_ground_tiles: isize, 23 | very_high_ground_tiles: isize, 24 | choke_points_by_area: AHashMap<*const Area, *const Vec>, 25 | accessible_neighbours: Vec<*const Area>, 26 | choke_points: Vec<*const ChokePoint>, 27 | minerals: Vec<*mut Mineral>, 28 | geysers: Vec<*mut Geyser>, 29 | bases: Vec, 30 | } 31 | 32 | impl Area { 33 | /// Unique id > 0 of this Area. Range = 1 .. Map::Areas().size() 34 | /// this == Map::GetArea(Id()) 35 | /// Id() == Map::GetMiniTile(w).AreaId() for each walkable MiniTile w in this Area. 36 | /// Area::ids are guaranteed to remain unchanged. 37 | pub fn id(&self) -> AreaId { 38 | self.id 39 | } 40 | 41 | /// Unique id > 0 of the group of Areas which are accessible from this Area. 42 | /// For each pair (a, b) of Areas: a->GroupId() == b->GroupId() <==> a->AccessibleFrom(b) 43 | /// A groupId uniquely identifies a maximum set of mutually accessible Areas, that is, in the absence of blocking ChokePoints, a continent. 44 | pub fn group_id(&self) -> GroupId { 45 | self.group_id 46 | } 47 | 48 | /// Bounding box of this Area. 49 | pub fn top_left(&self) -> TilePosition { 50 | self.top_left 51 | } 52 | 53 | pub fn bottom_right(&self) -> TilePosition { 54 | self.bottom_right 55 | } 56 | 57 | pub fn bounding_box_size(&self) -> TilePosition { 58 | self.bottom_right - self.top_left + (1, 1) 59 | } 60 | 61 | /// Position of the MiniTile with the highest Altitude() value. 62 | pub fn top(&self) -> WalkPosition { 63 | self.top 64 | } 65 | 66 | /// Returns Map::GetMiniTile(Top()).Altitude(). 67 | pub fn max_altitude(&self) -> Altitude { 68 | self.max_altitude 69 | } 70 | 71 | /// Returns the number of MiniTiles in this Area. 72 | /// This most accurately defines the size of this Area. 73 | pub fn mini_tiles(&self) -> isize { 74 | self.mini_tiles 75 | } 76 | 77 | /// Returns the percentage of low ground Tiles in this Area. 78 | pub fn low_ground_percentage(&self) -> isize { 79 | (self.tiles - self.high_ground_tiles - self.very_high_ground_tiles) * 100 / self.tiles 80 | } 81 | 82 | /// Returns the percentage of high ground Tiles in this Area. 83 | pub fn high_ground_percentage(&self) -> isize { 84 | self.high_ground_tiles * 100 / self.tiles 85 | } 86 | 87 | /// Returns the percentage of very high ground Tiles in this Area. 88 | pub fn very_high_ground_percentage(&self) -> isize { 89 | self.very_high_ground_tiles * 100 / self.tiles 90 | } 91 | 92 | /// Returns the ChokePoints between this Area and the neighbouring ones. 93 | /// Note: if there are no neighbouring Areas, then an empty set is returned. 94 | /// Note there may be more ChokePoints returned than the number of neighbouring Areas, as there may be several ChokePoints between two Areas (Cf. ChokePoints(const Area * pArea)). 95 | pub fn choke_points(&self) -> &[*const ChokePoint] { 96 | &self.choke_points 97 | } 98 | 99 | /// Returns the ChokePoints of this Area grouped by neighbouring Areas 100 | /// Note: if there are no neighbouring Areas, than an empty set is returned. 101 | pub fn choke_points_by_area(&self) -> &AHashMap<*const Area, *const Vec> { 102 | &self.choke_points_by_area 103 | } 104 | 105 | /// Returns the accessible neighbouring Areas. 106 | /// The accessible neighbouring Areas are a subset of the neighbouring Areas (the neighbouring Areas can be iterated using ChokePointsByArea()). 107 | /// Two neighbouring Areas are accessible from each over if at least one the ChokePoints they share is not Blocked (Cf. ChokePoint::Blocked). 108 | pub fn accessible_neighbours(&self) -> &[*const Area] { 109 | &self.accessible_neighbours 110 | } 111 | 112 | /// Returns whether this Area is accessible from pArea, that is, if they share the same GroupId(). 113 | /// Note: accessibility is always symmetrical. 114 | /// Note: even if a and b are neighbouring Areas, 115 | /// we can have: a->AccessibleFrom(b) 116 | /// and not: contains(a->AccessibleNeighbours(), b) 117 | /// See also GroupId() 118 | pub fn accessible_from(&self, area: Rc>) -> bool { 119 | self.group_id() == area.borrow().group_id() 120 | } 121 | 122 | /// Returns the Minerals contained in this Area. 123 | /// Note: only a call to Map::OnMineralDestroyed(BWAPI::Unit u) may change the result (by removing eventually one element). 124 | pub fn minerals(&self) -> &[*mut Mineral] { 125 | &self.minerals 126 | } 127 | 128 | /// Returns the Geysers contained in this Area. 129 | /// Note: the result will remain unchanged. 130 | pub fn geysers(&self) -> &[*mut Geyser] { 131 | &self.geysers 132 | } 133 | 134 | /// Returns the Bases contained in this Area. 135 | /// Note: the result will remain unchanged. 136 | pub fn bases(&self) -> &[Base] { 137 | &self.bases 138 | } 139 | 140 | pub fn get_map(&self) -> *mut Map { 141 | unsafe { self.p_graph.as_ref().unwrap() }.get_map() 142 | } 143 | 144 | pub fn add_choke_points(&mut self, area: *const Area, choke_points: *const Vec) { 145 | debug_assert!(!self.choke_points_by_area.contains_key(&area) && !choke_points.is_null()); 146 | 147 | self.choke_points_by_area.insert(area, choke_points); 148 | for cp in unsafe { choke_points.as_ref().unwrap() }.iter() { 149 | self.choke_points.push(cp); 150 | } 151 | } 152 | 153 | pub fn add_mineral(&mut self, mineral: *mut Mineral) { 154 | debug_assert!(!mineral.is_null() && !self.minerals.contains(&mineral)); 155 | 156 | self.minerals.push(mineral); 157 | } 158 | 159 | pub fn add_geyser(&mut self, geyser: *mut Geyser) { 160 | debug_assert!(!geyser.is_null() && !self.geysers.contains(&geyser)); 161 | 162 | self.geysers.push(geyser); 163 | } 164 | 165 | pub fn add_tile_information(&mut self, t: TilePosition, tile: &Tile) { 166 | self.tiles += 1; 167 | if tile.buildable() { 168 | self.buildable_tiles += 1 169 | } 170 | if tile.ground_height() == 1 { 171 | self.buildable_tiles += 1 172 | } 173 | if tile.ground_height() == 2 { 174 | self.very_high_ground_tiles += 1 175 | } 176 | 177 | self.top_left.x = self.top_left.x.min(t.x); 178 | self.top_left.y = self.top_left.y.min(t.y); 179 | self.bottom_right.x = self.bottom_right.x.max(t.x); 180 | self.bottom_right.y = self.bottom_right.y.max(t.y); 181 | } 182 | 183 | pub fn on_mineral_destroyed(&mut self, mineral: *mut Mineral) { 184 | debug_assert!(!mineral.is_null()); 185 | 186 | if let Some(i) = self.minerals.iter().position(|&m| m == mineral) { 187 | self.minerals.swap_remove(i); 188 | } 189 | 190 | // let's examine the bases even if pMineral was not found in this Area, 191 | // which could arise if Minerals were allowed to be assigned to neighbouring Areas. 192 | for base in self.bases.iter_mut() { 193 | base.on_mineral_destroyed(mineral); 194 | } 195 | } 196 | 197 | pub fn post_collect_information(&self) {} 198 | 199 | pub fn compute_distances( 200 | &mut self, 201 | start_cp: *const ChokePoint, 202 | target_cps: &Vec<*const ChokePoint>, 203 | ) -> Vec { 204 | debug_assert!(!target_cps.contains(&start_cp)); 205 | let map = unsafe { &*self.get_map() }; 206 | 207 | let start = map.breadth_first_search( 208 | unsafe { &*start_cp }.pos_in_area(Node::Middle, self), 209 | |tile: &Tile, _: TilePosition| tile.area_id() == self.id(), 210 | |_: &Tile, _: TilePosition| true, 211 | true, 212 | ); 213 | let targets: Vec<_> = target_cps 214 | .iter() 215 | .map(|&cp| { 216 | map.breadth_first_search( 217 | unsafe { &*cp }.pos_in_area(Node::Middle, self), 218 | |tile: &Tile, _: TilePosition| tile.area_id() == self.id(), 219 | |_: &Tile, _: TilePosition| true, 220 | true, 221 | ) 222 | }) 223 | .collect(); 224 | self.compute_distances_starting_at(start, &targets) 225 | } 226 | 227 | pub fn update_accessible_neighbours(&mut self) { 228 | self.accessible_neighbours = self 229 | .choke_points_by_area() 230 | .iter() 231 | .filter(|(k, &v)| unsafe { &*v }.iter().any(|cp| !cp.blocked())) 232 | .map(|(k, _)| *k) 233 | .collect(); 234 | } 235 | 236 | pub fn set_group_id(&mut self, gid: GroupId) { 237 | debug_assert!(gid >= 1); 238 | self.group_id = gid; 239 | } 240 | 241 | pub fn create_bases(&mut self) { 242 | let dim_cc = UnitType::Terran_Command_Center.tile_size(); 243 | 244 | let map = unsafe { &mut *self.get_map() }; 245 | 246 | // Initialize the RemainingRessources with all the Minerals and Geysers in this Area satisfying some conditions: 247 | 248 | let mut remaining_resources: Vec<*mut dyn Resource> = self 249 | .minerals() 250 | .iter() 251 | .filter(|&m| { 252 | let m = unsafe { &**m }; 253 | m.initial_amount() >= 40 && !m.blocking() 254 | }) 255 | .map(|&m| m as *mut dyn Resource) 256 | .chain( 257 | self.geysers() 258 | .iter() 259 | .filter(|&g| { 260 | let g = unsafe { &**g }; 261 | g.initial_amount() >= 300 && !g.blocking() 262 | }) 263 | .map(|&g| g as *mut dyn Resource), 264 | ) 265 | .collect(); 266 | 267 | while !remaining_resources.is_empty() { 268 | // 1) Calculate the SearchBoundingBox (needless to search too far from the RemainingRessources): 269 | 270 | let mut top_left_resources = TilePosition::new(std::i32::MAX, std::i32::MAX); 271 | let mut bottom_right_resources = TilePosition::new(std::i32::MIN, std::i32::MIN); 272 | for &r in remaining_resources.iter() { 273 | let r = unsafe { &*r }; 274 | make_bounding_box_include_point( 275 | &mut top_left_resources, 276 | &mut bottom_right_resources, 277 | r.top_left(), 278 | ); 279 | make_bounding_box_include_point( 280 | &mut top_left_resources, 281 | &mut bottom_right_resources, 282 | r.bottom_right(), 283 | ); 284 | } 285 | 286 | let mut top_left_search_bounding_box = 287 | top_left_resources - dim_cc - max_tiles_between_command_center_and_resources; 288 | let mut bottom_right_search_bounding_box = 289 | bottom_right_resources + 1 + max_tiles_between_command_center_and_resources; 290 | 291 | make_point_fit_to_bounding_box( 292 | &mut top_left_search_bounding_box, 293 | self.top_left(), 294 | self.bottom_right() - dim_cc + 1, 295 | ); 296 | make_point_fit_to_bounding_box( 297 | &mut bottom_right_search_bounding_box, 298 | self.top_left(), 299 | self.bottom_right() - dim_cc + 1, 300 | ); 301 | 302 | // 2) Mark the Tiles with their distances from each remaining Ressource (Potential Fields >= 0) 303 | for &r in remaining_resources.iter() { 304 | let r = unsafe { &*r }; 305 | for dy in -dim_cc.y - max_tiles_between_command_center_and_resources 306 | ..r.size().y + dim_cc.y + max_tiles_between_command_center_and_resources 307 | { 308 | for dx in -dim_cc.x - max_tiles_between_command_center_and_resources 309 | ..r.size().x + dim_cc.x + max_tiles_between_command_center_and_resources 310 | { 311 | let t = r.top_left() + (dx, dy); 312 | if map.valid(t) { 313 | let tile = map.get_tile_mut(t); 314 | let dist = 315 | (dist_to_rectangle(t.center(), r.top_left(), r.size()) + 16) / 32; 316 | let mut score = 317 | 0.max(max_tiles_between_command_center_and_resources + 3 - dist); 318 | if r.is_geyser().is_some() { 319 | // somewhat compensates for Geyser alone vs the several Minerals 320 | score *= 3 321 | } 322 | if tile.area_id() == self.id() { 323 | // note the additive effect (assume tile.InternalData() is 0 at the begining) 324 | tile.set_internal_data(tile.internal_data() + score); 325 | } 326 | } 327 | } 328 | } 329 | } 330 | 331 | // 3) Invalidate the 7 x 7 Tiles around each remaining Ressource (Starcraft rule) 332 | for &r in remaining_resources.iter() { 333 | let r = unsafe { &*r }; 334 | for dx in -3..r.size().y + 3 { 335 | for dy in -3..r.size().x + 3 { 336 | let t = r.top_left() + (dx, dy); 337 | if map.valid(t) { 338 | map.get_tile_mut(t).set_internal_data(-1); 339 | } 340 | } 341 | } 342 | } 343 | 344 | // 4) Search the best location inside the SearchBoundingBox: 345 | let mut best_location = TilePosition::new(0, 0); 346 | let mut best_score = 0; 347 | let mut blocking_minerals = vec![]; 348 | 349 | for y in top_left_search_bounding_box.y..bottom_right_search_bounding_box.y { 350 | for x in top_left_search_bounding_box.x..bottom_right_search_bounding_box.x { 351 | let score = self.compute_base_location_score(TilePosition::new(x, y)); 352 | if score > best_score { 353 | if self 354 | .validate_base_location(TilePosition::new(x, y), &mut blocking_minerals) 355 | { 356 | best_score = score; 357 | best_location = TilePosition::new(x, y); 358 | } 359 | } 360 | } 361 | } 362 | 363 | // 5) Clear Tile::m_internalData (required due to our use of Potential Fields: see comments in 2)) 364 | for &r in remaining_resources.iter() { 365 | let r = unsafe { &*r }; 366 | let r = unsafe { &*r }; 367 | for dy in -dim_cc.y - max_tiles_between_command_center_and_resources 368 | ..r.size().y + dim_cc.y + max_tiles_between_command_center_and_resources 369 | { 370 | for dx in -dim_cc.x - max_tiles_between_command_center_and_resources 371 | ..r.size().y + dim_cc.x + max_tiles_between_command_center_and_resources 372 | { 373 | let t = r.top_left() + TilePosition::new(dx, dy); 374 | if map.valid(t) { 375 | map.get_tile_mut(t).set_internal_data(0); 376 | } 377 | } 378 | } 379 | } 380 | 381 | if best_score == 0 { 382 | break; 383 | } 384 | 385 | // 6) Create a new Base at bestLocation, assign to it the relevant ressources and remove them from RemainingRessources: 386 | let mut assigned_resources = vec![]; 387 | for (i, &r) in remaining_resources.iter().enumerate() { 388 | let res = unsafe { &*r }; 389 | if dist_to_rectangle(res.pos(), best_location, dim_cc) + 2 390 | <= max_tiles_between_command_center_and_resources * 32 391 | { 392 | assigned_resources.push(r); 393 | } 394 | } 395 | remaining_resources.retain(|r| !assigned_resources.contains(&r)); 396 | 397 | if assigned_resources.is_empty() { 398 | break; 399 | } 400 | 401 | self.bases.push(Base::new( 402 | self, 403 | best_location, 404 | assigned_resources, 405 | blocking_minerals, 406 | )); 407 | } 408 | } 409 | 410 | fn get_graph(&self) -> *mut Graph { 411 | self.p_graph 412 | } 413 | 414 | fn compute_base_location_score(&self, location: TilePosition) -> i32 { 415 | let map = unsafe { &mut *self.get_map() }; 416 | let dim_cc = UnitType::Terran_Command_Center.tile_size(); 417 | 418 | let mut sum_score = 0; 419 | for dy in 0..dim_cc.y { 420 | for dx in 0..dim_cc.x { 421 | let tile = map.get_tile(location + (dx, dy)); 422 | if !tile.buildable() { 423 | return -1; 424 | } 425 | if tile.internal_data() == -1 { 426 | // The special value InternalData() == -1 means there is some ressource at maximum 3 tiles, which Starcraft rules forbid. 427 | return -1; 428 | } 429 | if tile.area_id() != self.id() { 430 | return -1; 431 | } 432 | if let Some(neutral) = unsafe { tile.get_neutral().as_ref() } { 433 | if neutral.is_static_building().is_some() { 434 | return -1; 435 | } 436 | } 437 | sum_score += tile.internal_data(); 438 | } 439 | } 440 | sum_score 441 | } 442 | 443 | // Checks if 'location' is a valid location for the placement of a Base Command Center. 444 | // If the location is valid except for the presence of Mineral patches of less than 9 (see Andromeda.scx), 445 | // the function returns true, and these Minerals are reported in BlockingMinerals 446 | // The function is intended to be called after ComputeBaseLocationScore, as it is more expensive. 447 | // See also the comments inside ComputeBaseLocationScore. 448 | fn validate_base_location( 449 | &self, 450 | location: TilePosition, 451 | blocking_minerals: &mut Vec<*const Mineral>, 452 | ) -> bool { 453 | let map = unsafe { &mut *self.get_map() }; 454 | let dim_cc = UnitType::Terran_Command_Center.tile_size(); 455 | 456 | blocking_minerals.clear(); 457 | 458 | for dy in -3..dim_cc.y + 3 { 459 | for dx in -3..dim_cc.x + 3 { 460 | let t = location + (dx, dy); 461 | if map.valid(t) { 462 | let tile = map.get_tile(t); 463 | if let Some(n) = unsafe { tile.get_neutral().as_ref() } { 464 | if n.is_geyser().is_some() { 465 | return false; 466 | } 467 | if let Some(m) = n.is_mineral() { 468 | let mineral = unsafe { &*m }; 469 | if mineral.initial_amount() <= 8 { 470 | blocking_minerals.push(m); 471 | } else { 472 | return false; 473 | } 474 | } 475 | } 476 | } 477 | } 478 | } 479 | 480 | // checks the distance to the Bases already created: 481 | for base in self.bases() { 482 | if rounded_dist(base.location(), location) < min_tiles_between_bases { 483 | return false; 484 | } 485 | } 486 | true 487 | } 488 | 489 | fn compute_distances_starting_at( 490 | &self, 491 | start: TilePosition, 492 | targets: &[TilePosition], 493 | ) -> Vec { 494 | let map = unsafe { &mut *self.get_map() }; 495 | let mut distances = vec![0; targets.len()]; 496 | 497 | TileMark::unmark_all(); 498 | 499 | #[derive(Eq, PartialEq)] 500 | struct DistPos(i32, TilePosition); 501 | impl Ord for DistPos { 502 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 503 | // Inverted to make it a min-heap 504 | other.0.cmp(&self.0) 505 | } 506 | } 507 | impl PartialOrd for DistPos { 508 | fn partial_cmp(&self, other: &Self) -> Option { 509 | Some(self.cmp(other)) 510 | } 511 | } 512 | 513 | let mut to_visit = BinaryHeap::new(); // a priority queue holding the tiles to visit ordered by their distance to start. 514 | to_visit.push(DistPos(0, start)); 515 | let mut remaining_targets = targets.len(); 516 | while let Some(DistPos(current_dist, current)) = to_visit.pop() { 517 | let current_tile = map.get_tile_mut(current); 518 | 519 | // Small change to original algorithm: We might have duplicated entries, see below 520 | if current_tile.mark.marked() { 521 | continue; 522 | } 523 | debug_assert!(current_tile.internal_data() == current_dist); 524 | current_tile.set_internal_data(0); 525 | // resets Tile::m_internalData for future usage 526 | current_tile.mark.set_marked(); 527 | 528 | for i in 0..targets.len() { 529 | if current == targets[i] { 530 | distances[i] = (0.5 + current_dist as f32 + 32.0 / 10000.0) as i32; 531 | remaining_targets -= 1; 532 | } 533 | } 534 | if remaining_targets == 0 { 535 | break; 536 | } 537 | 538 | for delta in [ 539 | TilePosition::new(-1, -1), 540 | TilePosition::new(0, -1), 541 | TilePosition::new(1, -1), 542 | TilePosition::new(-1, 0), 543 | TilePosition::new(1, 0), 544 | TilePosition::new(-1, 1), 545 | TilePosition::new(0, 1), 546 | TilePosition::new(1, 1), 547 | ] { 548 | let diagonal_move = delta.x != 0 && delta.y != 0; 549 | let new_next_dist = current_dist + if diagonal_move { 14142 } else { 10000 }; 550 | 551 | let next = current + delta; 552 | if map.valid(next) { 553 | let next_tile = map.get_tile_mut(next); 554 | if !next_tile.mark.marked() { 555 | if next_tile.internal_data() != 0 { 556 | // next already in ToVisit 557 | if new_next_dist < next_tile.internal_data() { 558 | // nextNewDist < nextOldDist 559 | // Change from original algorithm: We're not using a multimap, but a 560 | // binary heap. Updating an entry is even slower here, so we just add 561 | // it. See above on how it is skipped 562 | next_tile.set_internal_data(new_next_dist); 563 | } 564 | } else if next_tile.area_id() == self.id() || next_tile.area_id() == -1 { 565 | next_tile.set_internal_data(new_next_dist); 566 | to_visit.push(DistPos(new_next_dist, next)); 567 | } 568 | } 569 | } 570 | } 571 | } 572 | 573 | debug_assert_eq!(remaining_targets, 0); 574 | 575 | // Reset Tile::m_internalData for future usage 576 | for DistPos(_, tile_pos) in to_visit { 577 | map.get_tile_mut(tile_pos).set_internal_data(0); 578 | } 579 | distances 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /src/bwem/base.rs: -------------------------------------------------------------------------------- 1 | use super::{area::*, map::*, neutral::*}; 2 | use crate::*; 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | 6 | pub struct Base { 7 | map: *const Map, 8 | area: *const Area, 9 | location: TilePosition, 10 | center: Position, 11 | minerals: Vec<*const Mineral>, 12 | geysers: Vec<*const Geyser>, 13 | blocking_minerals: Vec<*const Mineral>, 14 | starting: bool, 15 | } 16 | 17 | impl Base { 18 | pub fn new( 19 | area: *const Area, 20 | location: TilePosition, 21 | assigned_resources: Vec<*mut dyn Resource>, 22 | blocking_minerals: Vec<*const Mineral>, 23 | ) -> Self { 24 | let mut minerals = vec![]; 25 | let mut geysers = vec![]; 26 | for &r in assigned_resources.iter() { 27 | let r = unsafe { &*r }; 28 | if let Some(mineral) = r.is_mineral() { 29 | minerals.push(mineral as *const Mineral); 30 | } 31 | if let Some(geyser) = r.is_geyser() { 32 | geysers.push(geyser as *const Geyser); 33 | } 34 | } 35 | Self { 36 | map: unsafe { &*area }.get_map(), 37 | area, 38 | location, 39 | center: location.to_position() 40 | + UnitType::Terran_Command_Center.tile_size().to_position() / 2, 41 | blocking_minerals, 42 | starting: false, 43 | geysers, 44 | minerals, 45 | } 46 | } 47 | 48 | pub fn location(&self) -> TilePosition { 49 | self.location 50 | } 51 | 52 | pub fn on_mineral_destroyed(&mut self, mineral: *mut Mineral) { 53 | unimplemented!(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/bwem/cp.rs: -------------------------------------------------------------------------------- 1 | use super::{area::*, graph::*, map::*, neutral::*}; 2 | use crate::*; 3 | use std::cell::RefCell; 4 | use std::collections::VecDeque; 5 | use std::rc::Rc; 6 | 7 | pub type Index = isize; 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////// 10 | /// // 11 | /// class ChokePoint 12 | /// // 13 | /////////////////////////////////////////////////////////////////////////////////////////////// 14 | /// 15 | /// ChokePoints are frontiers that BWEM automatically computes from Brood War's maps 16 | /// A ChokePoint represents (part of) the frontier between exactly 2 Areas. It has a form of line. 17 | /// A ChokePoint doesn't contain any MiniTile: All the MiniTiles whose positions are returned by its Geometry() 18 | /// are just guaranteed to be part of one of the 2 Areas. 19 | /// Among the MiniTiles of its Geometry, 3 particular ones called nodes can also be accessed using Pos(middle), Pos(end1) and Pos(end2). 20 | /// ChokePoints play an important role in BWEM: 21 | /// - they define accessibility between Areas. 22 | /// - the Paths provided by Map::GetPath are made of ChokePoints. 23 | /// Like Areas and Bases, the number and the addresses of ChokePoint instances remain unchanged. 24 | /// 25 | /// Pseudo ChokePoints: 26 | /// Some Neutrals can be detected as blocking Neutrals (Cf. Neutral::Blocking). 27 | /// Because only ChokePoints can serve as frontiers between Areas, BWEM automatically creates a ChokePoint 28 | /// for each blocking Neutral (only one in the case of stacked blocking Neutral). 29 | /// Such ChokePoints are called pseudo ChokePoints and they behave differently in several ways. 30 | /// 31 | /// ChokePoints inherit utils::Markable, which provides marking ability 32 | /// ChokePoints inherit utils::UserData, which provides free-to-use data. 33 | pub struct ChokePoint { 34 | p_graph: Rc>, 35 | pseudo: bool, 36 | index: Index, 37 | areas: (Rc>, Rc>), 38 | nodes: [WalkPosition; 4], 39 | nodes_in_area: [(WalkPosition, WalkPosition); 4], 40 | geometry: VecDeque, 41 | blocked: bool, 42 | blocking_neutral: Option>, 43 | p_path_back_trace: Option>>, 44 | } 45 | 46 | /// ChokePoint::middle denotes the "middle" MiniTile of Geometry(), while 47 | /// ChokePoint::end1 and ChokePoint::end2 denote its "ends". 48 | /// It is guaranteed that, among all the MiniTiles of Geometry(), ChokePoint::middle has the highest altitude value (Cf. MiniTile::Altitude()). 49 | pub enum Node { 50 | End1, 51 | Middle, 52 | End2, 53 | NodeCount, 54 | } 55 | 56 | /// Type of all the Paths used in BWEM (Cf. Map::GetPath). 57 | /// See also the typedef CPPath. 58 | pub type Path = Vec>>; 59 | 60 | impl ChokePoint { 61 | /// Tells whether this ChokePoint is a pseudo ChokePoint, i.e., it was created on top of a blocking Neutral. 62 | pub fn is_pseudo(&self) -> bool { 63 | self.pseudo 64 | } 65 | 66 | /// Returns the two Areas of this ChokePoint. 67 | pub fn get_areas(&self) -> (Rc>, Rc>) { 68 | self.areas.clone() 69 | } 70 | 71 | /// Returns the center of this ChokePoint. 72 | pub fn center(&self) -> WalkPosition { 73 | self.pos(Node::Middle) 74 | } 75 | 76 | /// Returns the position of one of the 3 nodes of this ChokePoint (Cf. node definition). 77 | /// Note: the returned value is contained in Geometry() 78 | pub fn pos(&self, n: Node) -> WalkPosition { 79 | match n { 80 | Node::End1 => self.nodes[0], 81 | Node::Middle => self.nodes[1], 82 | Node::End2 => self.nodes[2], 83 | _ => panic!(), 84 | } 85 | } 86 | 87 | /// Pretty much the same as Pos(n), except that the returned MiniTile position is guaranteed to be part of pArea. 88 | /// That is: Map::GetArea(PosInArea(n, pArea)) == pArea. 89 | pub fn pos_in_area(&self, n: Node, area: &mut Area) -> TilePosition { 90 | unimplemented!(); 91 | } 92 | 93 | /// Returns the set of positions that defines the shape of this ChokePoint. 94 | /// Note: none of these MiniTiles actually belongs to this ChokePoint (a ChokePoint doesn't contain any MiniTile). 95 | /// They are however guaranteed to be part of one of the 2 Areas. 96 | /// Note: the returned set contains Pos(middle), Pos(end1) and Pos(end2). 97 | /// If IsPseudo(), returns {p} where p is the position of a walkable MiniTile near from BlockingNeutral()->Pos(). 98 | pub fn geometry(&self) -> &VecDeque { 99 | &self.geometry 100 | } 101 | 102 | /// If !IsPseudo(), returns false. 103 | /// Otherwise, returns whether this ChokePoint is considered blocked. 104 | /// Normally, a pseudo ChokePoint either remains blocked, or switches to not blocked when BlockingNeutral() 105 | /// is destroyed and there is no remaining Neutral stacked with it. 106 | /// However, in the case where Map::AutomaticPathUpdate() == false, Blocked() will always return true 107 | /// whatever BlockingNeutral() returns. 108 | /// Cf. Area::AccessibleNeighbours(). 109 | pub fn blocked(&self) -> bool { 110 | self.blocked 111 | } 112 | 113 | /// If !IsPseudo(), returns nullptr. 114 | /// Otherwise, returns a pointer to the blocking Neutral on top of which this pseudo ChokePoint was created, 115 | /// unless this blocking Neutral has been destroyed. 116 | /// In this case, returns a pointer to the next blocking Neutral that was stacked at the same location, 117 | /// or nullptr if no such Neutral exists. 118 | pub fn blocking_neutral(&self) -> Option> { 119 | self.blocking_neutral.clone() 120 | } 121 | 122 | /// If AccessibleFrom(cp) == false, returns -1. 123 | /// Otherwise, returns the ground distance in pixels between Center() and cp->Center(). 124 | /// Note: if this == cp, returns 0. 125 | /// Time complexity: O(1) 126 | /// Note: Corresponds to the length in pixels of GetPathTo(cp). So it suffers from the same lack of accuracy. 127 | /// In particular, the value returned tends to be slightly higher than expected when GetPathTo(cp).size() is high. 128 | pub fn distance_from(&self, cp: Rc>) -> usize { 129 | unimplemented!() 130 | } 131 | 132 | /// Returns whether this ChokePoint is accessible from cp (through a walkable path). 133 | /// Note: the relation is symmetric: this->AccessibleFrom(cp) == cp->AccessibleFrom(this) 134 | /// Note: if this == cp, returns true. 135 | /// Time complexity: O(1) 136 | pub fn accessible_from(&self, cp: Rc>) -> bool { 137 | self.distance_from(cp) >= 0 138 | } 139 | 140 | /// Returns a list of ChokePoints, which is intended to be the shortest walking path from this ChokePoint to cp. 141 | /// The path always starts with this ChokePoint and ends with cp, unless AccessibleFrom(cp) == false. 142 | /// In this case, an empty list is returned. 143 | /// Note: if this == cp, returns [cp]. 144 | /// Time complexity: O(1) 145 | /// To get the length of the path returned in pixels, use DistanceFrom(cp). 146 | /// Note: all the possible Paths are precomputed during Map::Initialize(). 147 | /// The best one is then stored for each pair of ChokePoints. 148 | /// However, only the center of the ChokePoints is considered. 149 | /// As a consequence, the returned path may not be the shortest one. 150 | pub fn get_path_to(&self, cp: Rc>) -> &Path { 151 | unimplemented!() 152 | } 153 | 154 | pub fn get_map(&self) -> Rc> { 155 | unimplemented!() 156 | } 157 | 158 | // ChokePoint & operator=(const ChokePoint &) = delete; 159 | 160 | //////////////////////////////////////////////////////////////////////////// 161 | // Details: The functions below are used by the BWEM's internals 162 | 163 | pub fn new( 164 | graph: Rc>, 165 | idx: Index, 166 | area1: Rc>, 167 | area2: Rc>, 168 | geometry: VecDeque, 169 | neutral: Option>, 170 | ) { 171 | unimplemented!(); 172 | } 173 | 174 | pub fn on_blocking_neutral_destroyed(&mut self, neutral: Rc) { 175 | unimplemented!(); 176 | } 177 | 178 | pub fn index(&self) -> Index { 179 | self.index 180 | } 181 | 182 | pub fn path_back_trace(&self) -> Option>> { 183 | self.p_path_back_trace.clone() 184 | } 185 | 186 | pub fn set_path_back_trace(&mut self, p: Rc>) { 187 | self.p_path_back_trace = Some(p); 188 | } 189 | 190 | fn get_graph(&self) -> Rc> { 191 | self.p_graph.clone() 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/bwem/defs.rs: -------------------------------------------------------------------------------- 1 | pub type Altitude = i16; 2 | pub const max_tiles_between_command_center_and_resources: i32 = 10; 3 | pub const min_tiles_between_bases: i32 = 10; 4 | pub const lake_max_mini_tiles: i32 = 300; 5 | pub const lake_max_width_in_mini_tiles: i32 = 8 * 4; 6 | 7 | // At least area_min_miniTiles connected MiniTiles are necessary for an Area to be created. 8 | pub const area_min_mini_tiles: i32 = 64; 9 | -------------------------------------------------------------------------------- /src/bwem/graph.rs: -------------------------------------------------------------------------------- 1 | use super::{area::*, cp::*, map::*}; 2 | use crate::*; 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | 6 | const empty_path: Path = vec![]; 7 | 8 | pub struct Graph { 9 | p_map: *mut Map, 10 | areas: Vec, 11 | choke_point_list: Vec>>, 12 | choke_points_matrix: Vec>>, 13 | choke_point_distance_matrix: Vec>, 14 | paths_between_choke_points: Vec>, 15 | } 16 | 17 | impl Graph { 18 | pub fn get_map(&self) -> *mut Map { 19 | self.p_map 20 | } 21 | 22 | pub fn create_areas(&mut self, areas_list: Vec<(WalkPosition, i32)>) { 23 | unimplemented!() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/bwem/neutral.rs: -------------------------------------------------------------------------------- 1 | use super::{area::*, map::*}; 2 | use crate::*; 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | 6 | struct NeutralData { 7 | unit_id: UnitId, 8 | unit_type: UnitType, 9 | pos: Position, 10 | top_left: TilePosition, 11 | bottom_right: TilePosition, 12 | size: TilePosition, 13 | blocked_areas: Vec, 14 | } 15 | 16 | pub trait AsNeutral { 17 | // If this Neutral is a Mineral, returns a typed pointer to this Mineral. 18 | // Otherwise, returns nullptr. 19 | fn is_mineral(&self) -> Option<&Mineral> { 20 | None 21 | } 22 | 23 | /// If this Neutral is a Geyser, returns a typed pointer to this Geyser. 24 | /// Otherwise, returns nullptr. 25 | fn is_geyser(&self) -> Option<&Geyser> { 26 | None 27 | } 28 | 29 | /// If this Neutral is a StaticBuilding, returns a typed pointer to this StaticBuilding. 30 | /// Otherwise, returns nullptr. 31 | fn is_static_building(&self) -> Option<&StaticBuilding> { 32 | None 33 | } 34 | } 35 | 36 | pub trait Neutral: AsNeutral { 37 | /// Returns the BWAPI::Unit this Neutral is wrapping around. 38 | fn unit_id(&self) -> UnitId; 39 | 40 | /// Returns the BWAPI::UnitType of the BWAPI::Unit this Neutral is wrapping around. 41 | fn unit_type(&self) -> UnitType; 42 | 43 | /// Returns the center of this Neutral, in pixels (same as Unit()->getInitialPosition()). 44 | fn pos(&self) -> Position; 45 | 46 | /// Returns the top left Tile position of this Neutral (same as Unit()->getInitialTilePosition()). 47 | fn top_left(&self) -> TilePosition; 48 | 49 | /// Returns the bottom right Tile position of this Neutral 50 | fn bottom_right(&self) -> TilePosition; 51 | 52 | /// Returns the size of this Neutral, in Tiles (same as Type()->tileSize()) 53 | fn size(&self) -> TilePosition; 54 | 55 | /// Tells whether this Neutral is blocking some ChokePoint. 56 | /// This applies to Minerals and StaticBuildings only. 57 | /// For each blocking Neutral, a pseudo ChokePoint (which is Blocked()) is created on top of it, 58 | /// with the exception of stacked blocking Neutrals for which only one pseudo ChokePoint is created. 59 | /// Cf. definition of pseudo ChokePoints in class ChokePoint comment. 60 | /// Cf. ChokePoint::BlockingNeutral and ChokePoint::Blocked. 61 | fn blocking(&self) -> bool; 62 | 63 | fn set_blocking(&mut self, blocked_areas: &[WalkPosition]); 64 | 65 | /// If Blocking() == true, returns the set of Areas blocked by this Neutral. 66 | fn blocked_areas(&self) -> Vec>>; 67 | 68 | /// Returns the next Neutral stacked over this Neutral, if ever. 69 | /// To iterate through the whole stack, one can use the following: 70 | /// for (const Neutral * n = Map::GetTile(TopLeft()).GetNeutral() ; n ; n = n->NextStacked()) 71 | fn next_stacked(&self) -> *mut dyn Neutral; 72 | 73 | fn put_on_tiles(&self); 74 | 75 | /// Returns the last Neutral stacked over this Neutral, if ever. 76 | fn last_stacked(&self) -> Rc; 77 | } 78 | 79 | macro_rules! neutral_delegate { 80 | ($l:ident) => { 81 | impl Neutral for $l { 82 | /// Returns the BWAPI::Unit this Neutral is wrapping around. 83 | fn unit_id(&self) -> UnitId { 84 | self.neutral.unit_id 85 | } 86 | 87 | /// Returns the BWAPI::UnitType of the BWAPI::Unit this Neutral is wrapping around. 88 | fn unit_type(&self) -> UnitType { 89 | self.neutral.unit_type 90 | } 91 | 92 | /// Returns the center of this Neutral, in pixels (same as Unit()->getInitialPosition()). 93 | fn pos(&self) -> Position { 94 | self.neutral.pos 95 | } 96 | 97 | /// Returns the top left Tile position of this Neutral (same as Unit()->getInitialTilePosition()). 98 | fn top_left(&self) -> TilePosition { 99 | self.neutral.top_left 100 | } 101 | 102 | /// Returns the bottom right Tile position of this Neutral 103 | fn bottom_right(&self) -> TilePosition { 104 | self.neutral.bottom_right 105 | } 106 | 107 | /// Returns the size of this Neutral, in Tiles (same as Type()->tileSize()) 108 | fn size(&self) -> TilePosition { 109 | self.neutral.size 110 | } 111 | 112 | /// Tells whether this Neutral is blocking some ChokePoint. 113 | /// This applies to Minerals and StaticBuildings only. 114 | /// For each blocking Neutral, a pseudo ChokePoint (which is Blocked()) is created on top of it, 115 | /// with the exception of stacked blocking Neutrals for which only one pseudo ChokePoint is created. 116 | /// Cf. definition of pseudo ChokePoints in class ChokePoint comment. 117 | /// Cf. ChokePoint::BlockingNeutral and ChokePoint::Blocked. 118 | fn blocking(&self) -> bool { 119 | unimplemented!() 120 | } 121 | 122 | fn set_blocking(&mut self, blocked_areas: &[WalkPosition]) { 123 | self.neutral.blocked_areas = blocked_areas.to_vec(); 124 | } 125 | 126 | /// If Blocking() == true, returns the set of Areas blocked by this Neutral. 127 | fn blocked_areas(&self) -> Vec>> { 128 | unimplemented!() 129 | } 130 | 131 | /// Returns the next Neutral stacked over this Neutral, if ever. 132 | /// To iterate through the whole stack, one can use the following: 133 | /// for (const Neutral * n = Map::GetTile(TopLeft()).GetNeutral() ; n ; n = n->NextStacked()) 134 | fn next_stacked(&self) -> *mut dyn Neutral { 135 | unimplemented!() 136 | } 137 | 138 | fn put_on_tiles(&self) { 139 | unimplemented!() 140 | } 141 | 142 | /// Returns the last Neutral stacked over this Neutral, if ever. 143 | fn last_stacked(&self) -> Rc { 144 | unimplemented!() 145 | } 146 | } 147 | }; 148 | } 149 | 150 | /////////////////////////////////////////////////////////////////////////////////////////////// 151 | /// // 152 | /// class Ressource 153 | /// // 154 | /////////////////////////////////////////////////////////////////////////////////////////////// 155 | /// 156 | /// A Ressource is either a Mineral or a Geyser 157 | pub struct ResourceData { 158 | initial_amount: isize, 159 | neutral: NeutralData, 160 | } 161 | 162 | pub trait Resource: Neutral { 163 | fn initial_amount(&self) -> isize; 164 | } 165 | 166 | impl AsNeutral for ResourceData {} 167 | 168 | neutral_delegate!(ResourceData); 169 | 170 | impl ResourceData { 171 | /// Returns the initial amount of ressources for this Ressource (same as Unit()->getInitialResources). 172 | fn initial_amount(&self) -> isize { 173 | self.initial_amount 174 | } 175 | 176 | /// Returns the current amount of ressources for this Ressource (same as Unit()->getResources). 177 | fn amount(&self) -> usize { 178 | unimplemented!() 179 | } 180 | } 181 | 182 | /////////////////////////////////////////////////////////////////////////////////////////////// 183 | /// // 184 | /// class Mineral 185 | /// // 186 | /////////////////////////////////////////////////////////////////////////////////////////////// 187 | /// 188 | /// Minerals Correspond to the units in BWAPI::getStaticNeutralUnits() for which getType().isMineralField(), 189 | 190 | pub struct Mineral { 191 | initial_amount: isize, 192 | neutral: NeutralData, 193 | } 194 | 195 | impl AsNeutral for Mineral { 196 | // If this Neutral is a Mineral, returns a typed pointer to this Mineral. 197 | // Otherwise, returns nullptr. 198 | fn is_mineral(&self) -> Option<&Mineral> { 199 | Some(self) 200 | } 201 | } 202 | 203 | impl Resource for Mineral { 204 | fn initial_amount(&self) -> isize { 205 | self.initial_amount 206 | } 207 | } 208 | 209 | impl Mineral { 210 | pub fn new(unit: &Unit, map: &Map) -> Self { 211 | unimplemented!() 212 | } 213 | } 214 | 215 | neutral_delegate!(Mineral); 216 | 217 | /////////////////////////////////////////////////////////////////////////////////////////////// 218 | /// // 219 | /// class Geyser 220 | /// // 221 | /////////////////////////////////////////////////////////////////////////////////////////////// 222 | /// 223 | /// Geysers Correspond to the units in BWAPI::getStaticNeutralUnits() for which getType() == Resource_Vespene_Geyser 224 | pub struct Geyser { 225 | initial_amount: isize, 226 | neutral: NeutralData, 227 | } 228 | 229 | impl AsNeutral for Geyser { 230 | /// If this Neutral is a Geyser, returns a typed pointer to this Geyser. 231 | /// Otherwise, returns nullptr. 232 | fn is_geyser(&self) -> Option<&Geyser> { 233 | Some(self) 234 | } 235 | } 236 | 237 | impl Resource for Geyser { 238 | fn initial_amount(&self) -> isize { 239 | self.initial_amount 240 | } 241 | } 242 | 243 | impl Geyser { 244 | pub fn new(unit: &Unit, map: &Map) -> Self { 245 | unimplemented!() 246 | } 247 | } 248 | 249 | neutral_delegate!(Geyser); 250 | 251 | /////////////////////////////////////////////////////////////////////////////////////////////// 252 | /// // 253 | /// class StaticBuilding 254 | /// // 255 | /////////////////////////////////////////////////////////////////////////////////////////////// 256 | /// 257 | /// StaticBuildings Correspond to the units in BWAPI::getStaticNeutralUnits() for which getType().isSpecialBuilding 258 | /// StaticBuilding also wrappers some special units like Special_Pit_Door. 259 | pub struct StaticBuilding { 260 | neutral: NeutralData, 261 | } 262 | 263 | impl AsNeutral for StaticBuilding { 264 | /// If this Neutral is a StaticBuilding, returns a typed pointer to this StaticBuilding. 265 | /// Otherwise, returns nullptr. 266 | fn is_static_building(&self) -> Option<&StaticBuilding> { 267 | Some(self) 268 | } 269 | } 270 | 271 | impl StaticBuilding { 272 | pub fn new(unit: &Unit, map: &Map) -> Self { 273 | unimplemented!() 274 | } 275 | } 276 | 277 | neutral_delegate!(StaticBuilding); 278 | -------------------------------------------------------------------------------- /src/bwem/tiles.rs: -------------------------------------------------------------------------------- 1 | use super::{area::*, defs::*, neutral::*}; 2 | use std::cell::{Cell, RefCell}; 3 | use std::rc::Rc; 4 | 5 | /// // 6 | /// class MiniTile 7 | /// // 8 | /// 9 | /// Corresponds to BWAPI/Starcraft's concept of minitile (8x8 pixels). 10 | /// MiniTiles are accessed using WalkPositions (Cf. Map::GetMiniTile). 11 | /// A Map holds Map::WalkSize().x * Map::WalkSize().y MiniTiles as its "MiniTile map". 12 | /// A MiniTile contains essentialy 3 informations: 13 | /// - its Walkability 14 | /// - its altitude (distance from the nearest non walkable MiniTile, except those which are part of small enough zones (lakes)) 15 | /// - the id of the Area it is part of, if ever. 16 | /// The whole process of analysis of a Map relies on the walkability information 17 | /// from which are derived successively : altitudes, Areas, ChokePoints. 18 | #[derive(Default)] 19 | pub struct MiniTile { 20 | altitude: Cell, 21 | area_id: Cell, 22 | } 23 | 24 | const blocking_cp: AreaId = AreaId::MIN; 25 | 26 | impl MiniTile { 27 | /// Corresponds approximatively to BWAPI::isWalkable 28 | /// The differences are: 29 | /// - For each BWAPI's unwalkable MiniTile, we also mark its 8 neighbours as not walkable. 30 | /// According to some tests, this prevents from wrongly pretending one small unit can go by some thin path. 31 | /// - The relation buildable ==> walkable is enforced, by marking as walkable any MiniTile part of a buildable Tile (Cf. Tile::Buildable) 32 | /// Among the MiniTiles having Altitude() > 0, the walkable ones are considered Terrain-MiniTiles, and the other ones Lake-MiniTiles. 33 | pub fn walkable(&self) -> bool { 34 | self.area_id.get() != 0 35 | } 36 | 37 | /// Distance in pixels between the center of this MiniTile and the center of the nearest Sea-MiniTile 38 | /// Sea-MiniTiles all have their Altitude() equal to 0. 39 | /// MiniTiles having Altitude() > 0 are not Sea-MiniTiles. They can be either Terrain-MiniTiles or Lake-MiniTiles. 40 | pub fn altitude(&self) -> Altitude { 41 | self.altitude.get() 42 | } 43 | 44 | /// Sea-MiniTiles are unwalkable MiniTiles that have their Altitude() equal to 0. 45 | pub fn sea(&self) -> bool { 46 | self.altitude.get() == 0 47 | } 48 | 49 | /// Lake-MiniTiles are unwalkable MiniTiles that have their Altitude() > 0. 50 | /// They form small zones (inside Terrain-zones) that can be eaysily walked around (e.g. Starcraft's doodads) 51 | /// The intent is to preserve the continuity of altitudes inside Areas. 52 | pub fn lake(&self) -> bool { 53 | self.altitude.get() != 0 && !self.walkable() 54 | } 55 | 56 | /// Terrain MiniTiles are just walkable MiniTiles 57 | pub fn terrain(&self) -> bool { 58 | self.walkable() 59 | } 60 | 61 | /// For Sea and Lake MiniTiles, returns 0 62 | /// For Terrain MiniTiles, returns a non zero id: 63 | /// - if (id > 0), id uniquely identifies the Area A that contains this MiniTile. 64 | /// Moreover we have: A.Id() == id and Map::GetArea(id) == A 65 | /// For more information about positive Area::ids, see Area::Id() 66 | /// - if (id < 0), then this MiniTile is part of a Terrain-zone that was considered too small to create an Area for it. 67 | /// Note: negative Area::ids start from -2 68 | /// Note: because of the lakes, Map::GetNearestArea should be prefered over Map::GetArea. 69 | pub fn area_id(&self) -> AreaId { 70 | self.area_id.get() 71 | } 72 | 73 | // Details: The functions below are used by the BWEM's internals 74 | 75 | pub fn set_walkable(&self, walkable: bool) { 76 | if walkable { 77 | self.area_id.set(-1); 78 | self.altitude.set(-1); 79 | } else { 80 | self.area_id.set(0); 81 | self.altitude.set(1); 82 | } 83 | } 84 | pub fn sea_or_lake(&self) -> bool { 85 | self.altitude.get() == 1 86 | } 87 | 88 | pub fn set_sea(&self) { 89 | debug_assert!(!self.walkable() && self.sea_or_lake()); 90 | self.altitude.set(0); 91 | } 92 | 93 | pub fn set_lake(&self) { 94 | debug_assert!(!self.walkable() && self.sea()); 95 | self.altitude.set(-1); 96 | } 97 | 98 | pub fn altitude_missing(&self) -> bool { 99 | self.altitude.get() == -1 100 | } 101 | 102 | pub fn set_altitude(&self, a: Altitude) { 103 | debug_assert!(self.altitude_missing() && a > 0); 104 | self.altitude.set(a); 105 | } 106 | 107 | pub fn area_id_missing(&self) -> bool { 108 | self.area_id.get() == -1 109 | } 110 | 111 | pub fn set_area_id(&self, id: AreaId) { 112 | debug_assert!(self.area_id_missing() && id >= 1); 113 | self.area_id.set(id); 114 | } 115 | 116 | pub fn replace_area_id(&self, id: AreaId) { 117 | debug_assert!(self.area_id.get() > 0 && (id >= 1 || id <= -2) && id != self.area_id.get()); 118 | self.area_id.set(id); 119 | } 120 | 121 | pub fn set_blocked(&self) { 122 | debug_assert!(self.area_id_missing()); 123 | self.area_id.set(blocking_cp); 124 | } 125 | 126 | pub fn blocked(&self) -> bool { 127 | self.area_id.get() == blocking_cp 128 | } 129 | 130 | pub fn replace_blocked_area_id(&self, id: AreaId) { 131 | debug_assert!(self.area_id.get() == blocking_cp && id >= 1); 132 | self.area_id.set(id); 133 | } 134 | } 135 | 136 | /// // 137 | /// class Tile 138 | /// // 139 | /// 140 | /// Corresponds to BWAPI/Starcraft's concept of tile (32x32 pixels). 141 | /// Tiles are accessed using TilePositions (Cf. Map::GetTile). 142 | /// A Map holds Map::Size().x * Map::Size().y Tiles as its "Tile map". 143 | /// 144 | /// It should be noted that a Tile exactly overlaps 4 x 4 MiniTiles. 145 | /// As there are 16 times as many MiniTiles as Tiles, we allow a Tiles to contain more data than MiniTiles. 146 | /// As a consequence, Tiles should be preferred over MiniTiles, for efficiency. 147 | /// The use of Tiles is further facilitated by some functions like Tile::AreaId or Tile::MinAltitude 148 | /// which somewhat aggregate the MiniTile's corresponding information 149 | /// 150 | /// Tiles inherit utils::Markable, which provides marking ability 151 | /// Tiles inherit utils::UserData, which provides free-to-use data. 152 | pub struct Tile { 153 | neutral: *mut dyn Neutral, 154 | min_altitude: Altitude, 155 | area_id: AreaId, 156 | internal_data: i32, 157 | bits: Bits, 158 | pub(crate) mark: TileMark, 159 | } 160 | 161 | markable!(tile_mark, TileMark); 162 | 163 | impl Default for Tile { 164 | fn default() -> Self { 165 | Self { 166 | neutral: std::ptr::null_mut::(), 167 | min_altitude: 0, 168 | area_id: 0, 169 | internal_data: 0, 170 | bits: Default::default(), 171 | mark: Default::default(), 172 | } 173 | } 174 | } 175 | 176 | /// TODO: These are actual bits in the original implementation! 177 | #[derive(Default)] 178 | pub struct Bits { 179 | buildable: bool, 180 | ground_height: u8, 181 | doodad: bool, 182 | } 183 | 184 | impl Tile { 185 | /// Corresponds to BWAPI::isBuildable 186 | /// Note: BWEM enforces the relation buildable ==> walkable (Cf. MiniTile::Walkable) 187 | pub fn buildable(&self) -> bool { 188 | self.bits.buildable 189 | } 190 | 191 | /// Tile::AreaId() somewhat aggregates the MiniTile::AreaId() values of the 4 x 4 sub-MiniTiles. 192 | /// Let S be the set of MiniTile::AreaId() values for each walkable MiniTile in this Tile. 193 | /// If empty(S), returns 0. Note: in this case, no contained MiniTile is walkable, so all of them have their AreaId() == 0. 194 | /// If S = {a}, returns a (whether positive or negative). 195 | /// If size(S) > 1 returns -1 (note that -1 is never returned by MiniTile::AreaId()). 196 | pub fn area_id(&self) -> AreaId { 197 | self.area_id 198 | } 199 | 200 | /// Tile::MinAltitude() somewhat aggregates the MiniTile::Altitude() values of the 4 x 4 sub-MiniTiles. 201 | /// Returns the minimum value. 202 | pub fn min_altitude(&self) -> Altitude { 203 | self.min_altitude 204 | } 205 | 206 | /// Tells if at least one of the sub-MiniTiles is Walkable. 207 | pub fn walkable(&self) -> bool { 208 | self.area_id != 0 209 | } 210 | 211 | /// Tells if at least one of the sub-MiniTiles is a Terrain-MiniTile. 212 | pub fn terrain(&self) -> bool { 213 | self.walkable() 214 | } 215 | 216 | /// 0: lower ground 1: high ground 2: very high ground 217 | /// Corresponds to BWAPI::getGroundHeight / 2 218 | pub fn ground_height(&self) -> isize { 219 | self.bits.ground_height as isize 220 | } 221 | 222 | /// Tells if this Tile is part of a doodad. 223 | /// Corresponds to BWAPI::getGroundHeight % 2 224 | pub fn doodad(&self) -> bool { 225 | self.bits.doodad 226 | } 227 | 228 | /// If any Neutral occupies this Tile, returns it (note that all the Tiles it occupies will then return it). 229 | /// Otherwise, returns nullptr. 230 | /// Neutrals are Minerals, Geysers and StaticBuildings (Cf. Neutral). 231 | /// In some maps (e.g. Benzene.scx), several Neutrals are stacked at the same location. 232 | /// In this case, only the "bottom" one is returned, while the other ones can be accessed using Neutral::NextStacked(). 233 | /// Because Neutrals never move on the Map, the returned value is guaranteed to remain the same, unless some Neutral 234 | /// is destroyed and BWEM is informed of that by a call of Map::OnMineralDestroyed(BWAPI::Unit u) for exemple. In such a case, 235 | /// BWEM automatically updates the data by deleting the Neutral instance and clearing any reference to it such as the one 236 | /// returned by Tile::GetNeutral(). In case of stacked Neutrals, the next one is then returned. 237 | pub fn get_neutral(&self) -> *mut dyn Neutral { 238 | self.neutral 239 | } 240 | 241 | /// Returns the number of Neutrals that occupy this Tile (Cf. GetNeutral). 242 | pub fn stacked_neutrals(&self) -> isize { 243 | let mut stack_size = 0; 244 | let mut stacked = self.get_neutral(); 245 | while !stacked.is_null() { 246 | stack_size += 1; 247 | stacked = unsafe { &*stacked }.next_stacked(); 248 | } 249 | stack_size 250 | } 251 | 252 | /// Details: The functions below are used by the BWEM's internals 253 | 254 | pub fn set_buildable(&mut self) { 255 | self.bits.buildable = true; 256 | } 257 | 258 | pub fn set_ground_height(&mut self, h: i32) { 259 | debug_assert!((0 <= h) && (h <= 2)); 260 | self.bits.ground_height = h as u8; 261 | } 262 | 263 | pub fn set_doodad(&mut self) { 264 | self.bits.doodad = true; 265 | } 266 | 267 | pub fn add_neutral(&mut self, neutral: *mut dyn Neutral) { 268 | self.neutral = neutral; 269 | } 270 | 271 | pub fn set_area_id(&mut self, id: AreaId) { 272 | debug_assert!((id == -1) || self.area_id == 0 && id != 0); 273 | self.area_id = id; 274 | } 275 | 276 | pub fn reset_area_id(&mut self) { 277 | self.area_id = 0; 278 | } 279 | 280 | pub fn set_min_altitude(&mut self, a: Altitude) { 281 | debug_assert!(a >= 0); 282 | self.min_altitude = a; 283 | } 284 | 285 | pub fn remove_neutral(&mut self, neutral: *mut dyn Neutral) { 286 | debug_assert!( 287 | !self.neutral.is_null() 288 | && unsafe { &*self.neutral }.unit_id() == unsafe { &*neutral }.unit_id() 289 | ); 290 | self.neutral = std::ptr::null_mut::(); 291 | } 292 | 293 | pub fn internal_data(&self) -> i32 { 294 | self.internal_data 295 | } 296 | 297 | pub fn set_internal_data(&mut self, data: i32) { 298 | self.internal_data = data; 299 | } 300 | } 301 | 302 | pub trait TileOfPosition

{ 303 | type Tile; 304 | } 305 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::game::Game; 2 | use bwapi_wrapper::*; 3 | 4 | use std::ffi::CStr; 5 | use std::fs::File; 6 | use std::fs::OpenOptions; 7 | use std::io::Read; 8 | use std::io::Write; 9 | 10 | use std::thread; 11 | use std::time::Duration; 12 | 13 | use crate::aimodule::AiModule; 14 | 15 | use super::shm; 16 | 17 | pub struct Client { 18 | pipe: File, 19 | game: Game, 20 | } 21 | 22 | pub trait ToStr { 23 | fn to_str(&self) -> &str; 24 | } 25 | 26 | impl ToStr for [i8] { 27 | fn to_str(&self) -> &str { 28 | let as_u8 = unsafe { &*(self as *const [i8] as *const [u8]) }; 29 | let nul_pos = memchr::memchr(0, as_u8); 30 | if let Some(nul_pos) = nul_pos { 31 | unsafe { CStr::from_bytes_with_nul_unchecked(&as_u8[0..nul_pos + 1]) } 32 | .to_str() 33 | .unwrap() 34 | } else { 35 | "" 36 | } 37 | } 38 | } 39 | 40 | impl Default for Client { 41 | fn default() -> Self { 42 | 'outer: loop { 43 | if let Some(game_table) = 44 | shm::map_memory::("Local\\bwapi_shared_memory_game_list") 45 | { 46 | let game_table = game_table.get(); 47 | for game_instance in game_table.gameInstances.iter() { 48 | if game_instance.serverProcessID != 0 && !game_instance.isConnected { 49 | let pid = game_instance.serverProcessID; 50 | let mut file: File = OpenOptions::new() 51 | .read(true) 52 | .write(true) 53 | .open(format!("\\\\.\\pipe\\bwapi_pipe_{}", pid)) 54 | .expect("Game table was found, but could not open bwapi_pipe!"); 55 | let mut buf: [u8; 1] = [0]; 56 | println!("Connecting to {}", pid); 57 | loop { 58 | file.read_exact(&mut buf) 59 | .expect("Could not read from bwapi_pipe!"); 60 | if buf[0] == 2 { 61 | break; 62 | } 63 | } 64 | println!("Connected to {}", pid); 65 | let game_data = &format!("Local\\bwapi_shared_memory_{}", pid); 66 | let game_data: shm::Shm = shm::map_memory(game_data) 67 | .expect( 68 | "Game table was found, but could not establish shared memory link.", 69 | ); 70 | println!("Client version: {}", game_data.get().client_version); 71 | break 'outer Client { 72 | pipe: file, 73 | game: Game::new(game_data), 74 | }; 75 | } 76 | } 77 | } else { 78 | println!("Game table mapping not found."); 79 | thread::sleep(Duration::from_millis(1000)); 80 | } 81 | } 82 | } 83 | } 84 | impl Client { 85 | pub fn update(&mut self, module: &mut impl AiModule) { 86 | let mut buf: [u8; 1] = [1]; 87 | self.pipe 88 | .write_all(&buf) 89 | .expect("Pipe closed while writing"); 90 | loop { 91 | self.pipe 92 | .read_exact(&mut buf) 93 | .expect("Pipe closed while reading"); 94 | if buf[0] == 2 { 95 | break; 96 | } 97 | } 98 | self.game.handle_events(module); 99 | } 100 | 101 | pub fn get_game(&self) -> &Game { 102 | &self.game 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/command.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{Color, TextSize}; 2 | use crate::{Game, Player}; 3 | use bwapi_wrapper::prelude::*; 4 | use bwapi_wrapper::*; 5 | use core::cell::RefMut; 6 | use std::ffi::CString; 7 | use std::path::Path; 8 | 9 | struct CommandApplier<'a> { 10 | data: &'a mut BWAPI_GameData, 11 | } 12 | 13 | impl<'a> CommandApplier<'a> { 14 | fn add_commands( 15 | limit: i32, 16 | count: &mut i32, 17 | src: &[T], 18 | dst: &mut [T], 19 | ) -> Result<(), ()> { 20 | let copy_amount = ((limit - *count) as usize).min(src.len()); 21 | if *count + copy_amount as i32 >= limit { 22 | return Err(()); 23 | } 24 | let command_count = *count as usize; 25 | dst[command_count..command_count + copy_amount].copy_from_slice(src); 26 | *count += copy_amount as i32; 27 | Ok(()) 28 | } 29 | 30 | fn apply_commands(&mut self, commands: Commands) { 31 | CommandApplier::add_commands( 32 | BWAPI_GameData_MAX_COMMANDS, 33 | &mut self.data.commandCount, 34 | &commands.game_commands, 35 | &mut self.data.commands, 36 | ) 37 | .map_err(|_| "Too many game commmands") 38 | .unwrap(); 39 | 40 | CommandApplier::add_commands( 41 | BWAPI_GameData_MAX_UNIT_COMMANDS, 42 | &mut self.data.unitCommandCount, 43 | &commands.unit_commands, 44 | &mut self.data.unitCommands, 45 | ) 46 | .map_err(|_| "Too many unit commmands") 47 | .unwrap(); 48 | 49 | for cmd in commands.commands.iter() { 50 | use Command::*; 51 | match cmd { 52 | DrawText { 53 | ctype, 54 | x, 55 | y, 56 | string, 57 | } => self.draw_text(*ctype, *x, *y, string), 58 | SendText { message, to_allies } => self.send_text(message, *to_allies), 59 | SetMap(map_file_name) => self.set_map(map_file_name), 60 | } 61 | } 62 | 63 | for shape in commands.shapes.iter() { 64 | self.add_shape(*shape); 65 | } 66 | } 67 | 68 | pub fn set_map(&mut self, map_file_name: &str) { 69 | let string_index = self.add_string(map_file_name); 70 | self.add_command(BWAPIC_Command { 71 | type_: BWAPIC_CommandType_Enum::SetMap, 72 | value1: string_index as i32, 73 | value2: 0, 74 | }) 75 | } 76 | 77 | fn send_text(&mut self, message: &str, to_allies: bool) { 78 | let string_index = self.add_string(message); 79 | self.add_command(BWAPIC_Command { 80 | type_: BWAPIC_CommandType_Enum::SendText, 81 | value1: string_index as i32, 82 | value2: to_allies as i32, 83 | }) 84 | } 85 | 86 | fn draw_text(&mut self, ctype: CoordinateType, x1: i32, y1: i32, string: &str) { 87 | let id = self.add_string(string); 88 | let shape = BWAPIC_Shape { 89 | type_: BWAPIC_ShapeType_Enum::Text, 90 | ctype, 91 | x1, 92 | x2: 0, 93 | y1, 94 | y2: 0, 95 | extra1: id as i32, 96 | extra2: TextSize::Default as i32, 97 | color: Color::Black as i32, 98 | isSolid: false, 99 | }; 100 | 101 | self.add_shape(shape); 102 | } 103 | 104 | fn add_string(&mut self, string: &str) -> usize { 105 | assert!(self.data.stringCount < BWAPI_GameData_MAX_STRINGS); 106 | let string_count = self.data.stringCount as usize; 107 | let string = CString::new(string).unwrap(); 108 | let bytes = string.as_bytes_with_nul(); 109 | let len = bytes.len(); 110 | let dst = unsafe { 111 | &mut *(&mut self.data.strings[string_count][..len] as *mut [i8] as *mut [u8]) 112 | }; 113 | dst.copy_from_slice(bytes); 114 | self.data.stringCount += 1; 115 | string_count 116 | } 117 | 118 | fn add_shape(&mut self, shape: BWAPIC_Shape) { 119 | assert!( 120 | self.data.shapeCount < BWAPI_GameData_MAX_SHAPES, 121 | "Too many shapes" 122 | ); 123 | let shape_count = self.data.shapeCount as usize; 124 | self.data.shapes[shape_count] = shape; 125 | self.data.shapeCount += 1; 126 | } 127 | 128 | fn add_command(&mut self, cmd: BWAPIC_Command) { 129 | assert!( 130 | self.data.commandCount < BWAPI_GameData_MAX_COMMANDS, 131 | "Too many commands" 132 | ); 133 | let command_count = self.data.commandCount as usize; 134 | self.data.commands[command_count] = cmd; 135 | self.data.commandCount += 1; 136 | } 137 | } 138 | 139 | #[derive(Default)] 140 | pub(crate) struct Commands { 141 | commands: Vec, 142 | game_commands: Vec, 143 | unit_commands: Vec, 144 | shapes: Vec, 145 | } 146 | 147 | pub(crate) enum Command { 148 | DrawText { 149 | ctype: CoordinateType, 150 | x: i32, 151 | y: i32, 152 | string: String, 153 | }, 154 | SendText { 155 | message: String, 156 | to_allies: bool, 157 | }, 158 | SetMap(String), 159 | } 160 | 161 | impl Commands { 162 | pub fn new() -> Self { 163 | Self::default() 164 | } 165 | 166 | pub(crate) fn commit(self, to: &mut BWAPI_GameData) { 167 | CommandApplier { data: to }.apply_commands(self); 168 | } 169 | } 170 | 171 | impl Game { 172 | fn cmd(&self) -> RefMut { 173 | self.inner.cmd.borrow_mut() 174 | } 175 | 176 | pub fn send_text_ex(&self, to_allies: bool, message: &str) { 177 | self.cmd().commands.push(Command::SendText { 178 | to_allies, 179 | message: message.to_owned(), 180 | }); 181 | } 182 | 183 | pub fn send_text(&self, message: &str) { 184 | self.send_text_ex(false, message); 185 | } 186 | 187 | pub fn set_map(&self, map_file_name: &str) -> Result<(), Error> { 188 | if map_file_name.len() >= 260 || map_file_name.is_empty() { 189 | return Err(Error::Invalid_Parameter); 190 | } 191 | 192 | if !Path::new(map_file_name).exists() { 193 | return Err(Error::File_Not_Found); 194 | } 195 | 196 | self.cmd() 197 | .commands 198 | .push(Command::SetMap(map_file_name.to_owned())); 199 | Ok(()) 200 | } 201 | 202 | pub fn draw_text_screen>(&self, position: P, string: &str) { 203 | self.draw_text(CoordinateType::Screen, position, string); 204 | } 205 | 206 | pub fn draw_text_map>(&self, position: P, string: &str) { 207 | self.draw_text(CoordinateType::Map, position, string); 208 | } 209 | 210 | pub fn draw_text_mouse>(&self, position: P, string: &str) { 211 | self.draw_text(CoordinateType::Mouse, position, string); 212 | } 213 | 214 | pub fn draw_text>(&self, ctype: CoordinateType, position: P, string: &str) { 215 | let p = position.into(); 216 | self.cmd().commands.push(Command::DrawText { 217 | ctype, 218 | x: p.x, 219 | y: p.y, 220 | string: string.to_owned(), 221 | }); 222 | } 223 | 224 | pub fn draw_line_screen>(&self, a: P, b: P, color: Color) { 225 | self.draw_line(CoordinateType::Screen, a, b, color) 226 | } 227 | 228 | pub fn draw_line_map>(&self, a: P, b: P, color: Color) { 229 | self.draw_line(CoordinateType::Map, a, b, color) 230 | } 231 | 232 | pub fn draw_line_mouse>(&self, a: P, b: P, color: Color) { 233 | self.draw_line(CoordinateType::Mouse, a, b, color) 234 | } 235 | 236 | pub fn draw_dot_screen>(&self, p: P, color: Color) { 237 | self.draw_dot(CoordinateType::Screen, p, color) 238 | } 239 | 240 | pub fn draw_dot_map>(&self, p: P, color: Color) { 241 | self.draw_dot(CoordinateType::Map, p, color) 242 | } 243 | 244 | pub fn draw_dot_mouse>(&self, p: P, color: Color) { 245 | self.draw_dot(CoordinateType::Mouse, p, color) 246 | } 247 | 248 | pub fn draw_dot>(&self, ctype: CoordinateType, p: P, color: Color) { 249 | let Position { x, y } = p.into(); 250 | self.cmd().shapes.push(BWAPIC_Shape { 251 | type_: BWAPIC_ShapeType_Enum::Dot, 252 | ctype, 253 | x1: x, 254 | y1: y, 255 | x2: 0, 256 | y2: 0, 257 | extra1: 0, 258 | extra2: 0, 259 | color: color as i32, 260 | isSolid: false, 261 | }) 262 | } 263 | 264 | pub fn draw_triangle_map>( 265 | &self, 266 | a: P, 267 | b: P, 268 | c: P, 269 | color: Color, 270 | solid: bool, 271 | ) { 272 | self.draw_triangle(CoordinateType::Map, a, b, c, color, solid) 273 | } 274 | 275 | pub fn draw_triangle_mouse>( 276 | &self, 277 | a: P, 278 | b: P, 279 | c: P, 280 | color: Color, 281 | solid: bool, 282 | ) { 283 | self.draw_triangle(CoordinateType::Mouse, a, b, c, color, solid) 284 | } 285 | 286 | pub fn draw_triangle_screen>( 287 | &self, 288 | a: P, 289 | b: P, 290 | c: P, 291 | color: Color, 292 | solid: bool, 293 | ) { 294 | self.draw_triangle(CoordinateType::Screen, a, b, c, color, solid) 295 | } 296 | 297 | pub fn draw_triangle>( 298 | &self, 299 | ctype: CoordinateType, 300 | a: P, 301 | b: P, 302 | c: P, 303 | color: Color, 304 | solid: bool, 305 | ) { 306 | let a = a.into(); 307 | let b = b.into(); 308 | let c = c.into(); 309 | 310 | self.cmd().shapes.push(BWAPIC_Shape { 311 | type_: BWAPIC_ShapeType_Enum::Triangle, 312 | ctype, 313 | x1: a.x, 314 | y1: a.y, 315 | x2: b.x, 316 | y2: b.y, 317 | extra1: c.x, 318 | extra2: c.y, 319 | color: color as i32, 320 | isSolid: solid, 321 | }) 322 | } 323 | 324 | pub fn draw_circle_map>(&self, p: P, radius: i32, color: Color, solid: bool) { 325 | self.draw_circle(CoordinateType::Map, p, radius, color, solid) 326 | } 327 | 328 | pub fn draw_circle_screen>( 329 | &self, 330 | p: P, 331 | radius: i32, 332 | color: Color, 333 | solid: bool, 334 | ) { 335 | self.draw_circle(CoordinateType::Screen, p, radius, color, solid) 336 | } 337 | 338 | pub fn draw_circle_mouse>( 339 | &self, 340 | p: P, 341 | radius: i32, 342 | color: Color, 343 | solid: bool, 344 | ) { 345 | self.draw_circle(CoordinateType::Mouse, p, radius, color, solid) 346 | } 347 | 348 | pub fn draw_circle>( 349 | &self, 350 | ctype: CoordinateType, 351 | p: P, 352 | radius: i32, 353 | color: Color, 354 | solid: bool, 355 | ) { 356 | let Position { x, y } = p.into(); 357 | self.cmd().shapes.push(BWAPIC_Shape { 358 | type_: BWAPIC_ShapeType_Enum::Circle, 359 | ctype, 360 | x1: x, 361 | y1: y, 362 | x2: 0, 363 | y2: 0, 364 | extra1: radius, 365 | extra2: 0, 366 | color: color as i32, 367 | isSolid: solid, 368 | }); 369 | } 370 | 371 | pub fn draw_ellipse_screen>( 372 | &self, 373 | p: P, 374 | xrad: i32, 375 | yrad: i32, 376 | color: Color, 377 | solid: bool, 378 | ) { 379 | self.draw_ellipse(CoordinateType::Screen, p, xrad, yrad, color, solid) 380 | } 381 | 382 | pub fn draw_ellipse_map>( 383 | &self, 384 | p: P, 385 | xrad: i32, 386 | yrad: i32, 387 | color: Color, 388 | solid: bool, 389 | ) { 390 | self.draw_ellipse(CoordinateType::Map, p, xrad, yrad, color, solid) 391 | } 392 | 393 | pub fn draw_ellipse_mouse>( 394 | &self, 395 | p: P, 396 | xrad: i32, 397 | yrad: i32, 398 | color: Color, 399 | solid: bool, 400 | ) { 401 | self.draw_ellipse(CoordinateType::Mouse, p, xrad, yrad, color, solid) 402 | } 403 | 404 | pub fn draw_ellipse>( 405 | &self, 406 | ctype: CoordinateType, 407 | p: P, 408 | xrad: i32, 409 | yrad: i32, 410 | color: Color, 411 | solid: bool, 412 | ) { 413 | let Position { x, y } = p.into(); 414 | self.cmd().shapes.push(BWAPIC_Shape { 415 | type_: BWAPIC_ShapeType_Enum::Ellipse, 416 | ctype, 417 | x1: x, 418 | y1: y, 419 | x2: 0, 420 | y2: 0, 421 | extra1: xrad, 422 | extra2: yrad, 423 | color: color as i32, 424 | isSolid: solid, 425 | }); 426 | } 427 | 428 | pub fn draw_line>(&self, ctype: CoordinateType, a: P, b: P, color: Color) { 429 | let a = a.into(); 430 | let b = b.into(); 431 | self.cmd().shapes.push(BWAPIC_Shape { 432 | type_: BWAPIC_ShapeType_Enum::Line, 433 | ctype, 434 | x1: a.x, 435 | y1: a.y, 436 | x2: b.x, 437 | y2: b.y, 438 | extra1: 0, 439 | extra2: 0, 440 | color: color as i32, 441 | isSolid: false, 442 | }); 443 | } 444 | 445 | pub fn draw_box_map>( 446 | &self, 447 | top_left: P, 448 | bottom_right: P, 449 | color: Color, 450 | solid: bool, 451 | ) { 452 | self.draw_box(CoordinateType::Map, top_left, bottom_right, color, solid) 453 | } 454 | 455 | pub fn draw_box_mouse>( 456 | &self, 457 | top_left: P, 458 | bottom_right: P, 459 | color: Color, 460 | solid: bool, 461 | ) { 462 | self.draw_box(CoordinateType::Mouse, top_left, bottom_right, color, solid) 463 | } 464 | 465 | pub fn draw_box_screen>( 466 | &self, 467 | top_left: P, 468 | bottom_right: P, 469 | color: Color, 470 | solid: bool, 471 | ) { 472 | self.draw_box(CoordinateType::Screen, top_left, bottom_right, color, solid) 473 | } 474 | 475 | #[allow(clippy::too_many_arguments)] 476 | pub fn draw_box>( 477 | &self, 478 | ctype: CoordinateType, 479 | top_left: P, 480 | bottom_right: P, 481 | color: Color, 482 | solid: bool, 483 | ) { 484 | let Position { x: left, y: top } = top_left.into(); 485 | let Position { 486 | x: right, 487 | y: bottom, 488 | } = bottom_right.into(); 489 | self.cmd().shapes.push(BWAPIC_Shape { 490 | type_: BWAPIC_ShapeType_Enum::Box, 491 | ctype, 492 | x1: left, 493 | x2: right, 494 | y1: top, 495 | y2: bottom, 496 | extra1: 0, 497 | extra2: 0, 498 | color: color as i32, 499 | isSolid: solid, 500 | }) 501 | } 502 | 503 | pub fn issue_command(&self, cmd: UnitCommand) { 504 | self.cmd().unit_commands.push(cmd) 505 | } 506 | 507 | pub fn set_alliance(&mut self, other: &Player, allied: bool, allied_victory: bool) { 508 | if self.is_replay() || other == &self.self_().expect("Self to exist") { 509 | return; 510 | } 511 | 512 | self.cmd().game_commands.push(BWAPIC_Command { 513 | type_: BWAPIC_CommandType_Enum::SetAllies, 514 | value1: other.id as i32, 515 | value2: if allied { 516 | if allied_victory { 517 | 2 518 | } else { 519 | 1 520 | } 521 | } else { 522 | 0 523 | }, 524 | }); 525 | } 526 | 527 | pub fn set_reveal_all(&mut self, reveal: bool) -> Result<(), Error> { 528 | if !self.is_replay() { 529 | return Err(Error::Invalid_Parameter); 530 | } 531 | 532 | self.cmd().game_commands.push(BWAPIC_Command { 533 | type_: BWAPIC_CommandType_Enum::SetAllies, 534 | value1: reveal as i32, 535 | value2: 0, 536 | }); 537 | 538 | Ok(()) 539 | } 540 | 541 | pub fn set_vision(&mut self, player: &Player, enabled: bool) -> Result<(), Error> { 542 | if !self.is_replay() && self.self_().ok_or(Error::Invalid_Parameter)? == *player { 543 | return Err(Error::Invalid_Parameter); 544 | } 545 | 546 | self.cmd().game_commands.push(BWAPIC_Command { 547 | type_: BWAPIC_CommandType_Enum::SetAllies, 548 | value1: player.id as i32, 549 | value2: enabled as i32, 550 | }); 551 | 552 | Ok(()) 553 | } 554 | 555 | pub fn leave_game(&self) { 556 | self.cmd().game_commands.push(BWAPIC_Command { 557 | type_: BWAPIC_CommandType_Enum::LeaveGame, 558 | value1: 0, 559 | value2: 0, 560 | }); 561 | } 562 | 563 | pub fn pause_game(&self) { 564 | self.cmd().game_commands.push(BWAPIC_Command { 565 | type_: BWAPIC_CommandType_Enum::PauseGame, 566 | value1: 0, 567 | value2: 0, 568 | }); 569 | } 570 | 571 | pub fn ping_minimap>(&mut self, p: P) { 572 | let p = p.into(); 573 | self.cmd().game_commands.push(BWAPIC_Command { 574 | type_: BWAPIC_CommandType_Enum::PingMinimap, 575 | value1: p.x, 576 | value2: p.y, 577 | }); 578 | } 579 | 580 | pub fn restart_game(&mut self) { 581 | self.cmd().game_commands.push(BWAPIC_Command { 582 | type_: BWAPIC_CommandType_Enum::RestartGame, 583 | value1: 0, 584 | value2: 0, 585 | }); 586 | } 587 | 588 | pub fn enable_flag(&mut self, flag: i32) { 589 | self.cmd().game_commands.push(BWAPIC_Command { 590 | type_: BWAPIC_CommandType_Enum::EnableFlag, 591 | value1: flag, 592 | value2: 0, 593 | }); 594 | } 595 | 596 | pub fn set_frame_skip(&mut self, frame_skip: i32) { 597 | if frame_skip > 0 { 598 | self.cmd().game_commands.push(BWAPIC_Command { 599 | type_: BWAPIC_CommandType_Enum::SetFrameSkip, 600 | value1: frame_skip, 601 | value2: 0, 602 | }); 603 | } 604 | } 605 | 606 | pub fn set_gui(&mut self, enabled: bool) { 607 | self.cmd().game_commands.push(BWAPIC_Command { 608 | type_: BWAPIC_CommandType_Enum::SetGui, 609 | value1: enabled as i32, 610 | value2: 0, 611 | }); 612 | } 613 | 614 | pub fn set_lat_com(&mut self, enabled: bool) { 615 | self.cmd().game_commands.push(BWAPIC_Command { 616 | type_: BWAPIC_CommandType_Enum::SetLatCom, 617 | value1: enabled as i32, 618 | value2: 0, 619 | }); 620 | } 621 | 622 | pub fn set_local_speed(&mut self, speed: i32) { 623 | self.cmd().game_commands.push(BWAPIC_Command { 624 | type_: BWAPIC_CommandType_Enum::SetLocalSpeed, 625 | value1: speed, 626 | value2: 0, 627 | }); 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /src/force.rs: -------------------------------------------------------------------------------- 1 | use crate::types::c_str_to_str; 2 | use crate::Player; 3 | use bwapi_wrapper::*; 4 | 5 | pub struct Force { 6 | pub id: usize, 7 | pub name: String, 8 | pub players: Vec, 9 | } 10 | 11 | impl Force { 12 | pub fn new(id: usize, data: &BWAPI_ForceData, players: Vec) -> Self { 13 | Self { 14 | id, 15 | name: c_str_to_str(&data.name), 16 | players, 17 | } 18 | } 19 | 20 | pub fn get_players(&self) -> &[Player] { 21 | &self.players 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "metrics"))] 2 | macro_rules! measure { 3 | ($metric:expr, $e:expr) => { 4 | $e 5 | }; 6 | } 7 | 8 | // mod bwem; 9 | mod projected; 10 | mod shm; 11 | 12 | pub use crate::types::*; 13 | pub use bwapi_wrapper::prelude::*; 14 | pub mod aimodule; 15 | pub mod bullet; 16 | pub mod can_do; 17 | pub mod client; 18 | pub mod command; 19 | pub mod force; 20 | pub mod game; 21 | pub mod player; 22 | pub mod predicate; 23 | pub mod region; 24 | pub mod sma; 25 | pub mod types; 26 | pub mod unit; 27 | 28 | pub use aimodule::AiModule; 29 | pub use bullet::{Bullet, BulletType}; 30 | pub use force::Force; 31 | pub use game::Game; 32 | pub use player::{Player, PlayerId}; 33 | pub use unit::{Unit, UnitId}; 34 | 35 | pub fn start(build_module: impl FnOnce(&Game) -> M) { 36 | let mut client = client::Client::default(); 37 | 38 | println!("Waiting for frame to start"); 39 | let mut module = Box::new(build_module(client.get_game())); 40 | 41 | while !client.get_game().is_in_game() { 42 | client.update(&mut *module); 43 | } 44 | 45 | while client.get_game().is_in_game() { 46 | client.update(&mut *module); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/player.rs: -------------------------------------------------------------------------------- 1 | use crate::force::Force; 2 | use crate::game::Game; 3 | use crate::types::c_str_to_str; 4 | use crate::types::Color; 5 | use crate::unit::Unit; 6 | use bwapi_wrapper::prelude::*; 7 | use bwapi_wrapper::*; 8 | use num_traits::FromPrimitive; 9 | use std::fmt; 10 | 11 | pub type PlayerId = usize; 12 | 13 | #[derive(Clone)] 14 | pub struct Player { 15 | pub id: PlayerId, 16 | game: Game, 17 | data: &'static BWAPI_PlayerData, 18 | } 19 | 20 | impl fmt::Debug for Player { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | f.debug_struct("Player").field("id", &self.id).finish() 23 | } 24 | } 25 | impl Player { 26 | pub(crate) fn new(id: PlayerId, game: Game, data: &'static BWAPI_PlayerData) -> Self { 27 | Player { id, game, data } 28 | } 29 | 30 | pub fn all_unit_count(&self, unit: UnitType) -> i32 { 31 | self.data.allUnitCount[unit as usize] 32 | } 33 | 34 | pub fn armor(&self, unit: UnitType) -> i32 { 35 | let mut armor = unit.armor(); 36 | armor += self.get_upgrade_level(unit.armor_upgrade()); 37 | if unit == UnitType::Zerg_Ultralisk 38 | && self.get_upgrade_level(UpgradeType::Chitinous_Plating) > 0 39 | || unit == UnitType::Hero_Torrasque 40 | { 41 | armor += 2; 42 | } 43 | armor 44 | } 45 | 46 | pub fn completed_unit_count(&self, unit: UnitType) -> i32 { 47 | self.data.completedUnitCount[unit as usize] 48 | } 49 | 50 | pub fn damage(&self, wpn: WeaponType) -> i32 { 51 | let mut dmg = wpn.damage_amount(); 52 | dmg += self.get_upgrade_level(wpn.upgrade_type()) * wpn.damage_bonus(); 53 | dmg * wpn.damage_factor() 54 | } 55 | 56 | pub fn dead_unit_count(&self, unit: UnitType) -> i32 { 57 | self.data.deadUnitCount[unit as usize] 58 | } 59 | 60 | pub fn gas(&self) -> i32 { 61 | self.data.gas 62 | } 63 | 64 | pub fn gathered_gas(&self) -> i32 { 65 | self.data.gatheredGas 66 | } 67 | 68 | pub fn gathered_minerals(&self) -> i32 { 69 | self.data.gatheredMinerals 70 | } 71 | 72 | pub fn get_building_score(&self) -> i32 { 73 | self.data.totalBuildingScore 74 | } 75 | 76 | pub fn get_color(&self) -> Color { 77 | Color::from_i32(self.data.color).unwrap() 78 | } 79 | 80 | pub fn get_custom_score(&self) -> i32 { 81 | self.data.customScore 82 | } 83 | 84 | pub(crate) fn force_id(&self) -> i32 { 85 | self.data.force 86 | } 87 | 88 | pub fn get_force(&self) -> Force { 89 | self.game.get_force(self.force_id()) 90 | } 91 | 92 | pub fn get_id(&self) -> PlayerId { 93 | self.id as PlayerId 94 | } 95 | 96 | pub fn get_kill_score(&self) -> i32 { 97 | self.data.totalKillScore 98 | } 99 | 100 | pub fn get_max_upgrade_level(&self, upgrade: UpgradeType) -> i32 { 101 | self.data.maxUpgradeLevel[upgrade as usize] 102 | } 103 | 104 | pub fn get_name(&self) -> String { 105 | c_str_to_str(&self.data.name) 106 | } 107 | 108 | pub fn get_race(&self) -> Race { 109 | Race::new(self.data.race) 110 | } 111 | 112 | pub fn get_units(&self) -> Vec { 113 | self.game 114 | .get_all_units() 115 | .iter() 116 | .filter(|u| u.get_player() == *self) 117 | .cloned() 118 | .collect() 119 | } 120 | 121 | pub fn get_upgrade_level(&self, upgrade_type: UpgradeType) -> i32 { 122 | self.data.upgradeLevel[upgrade_type as usize] 123 | } 124 | 125 | pub fn has_researched(&self, tech: TechType) -> bool { 126 | self.data.hasResearched[tech as usize] 127 | } 128 | 129 | pub fn has_unit_type_requirement(&self, unit: UnitType, amount: i32) -> bool { 130 | if unit == UnitType::None { 131 | return true; 132 | } 133 | 134 | (match unit { 135 | UnitType::Zerg_Hatchery => { 136 | self.completed_unit_count(UnitType::Zerg_Hatchery) 137 | + self.all_unit_count(UnitType::Zerg_Lair) 138 | + self.all_unit_count(UnitType::Zerg_Hive) 139 | } 140 | UnitType::Zerg_Lair => { 141 | self.completed_unit_count(UnitType::Zerg_Lair) 142 | + self.all_unit_count(UnitType::Zerg_Hive) 143 | } 144 | UnitType::Zerg_Spire => { 145 | self.completed_unit_count(UnitType::Zerg_Spire) 146 | + self.all_unit_count(UnitType::Zerg_Greater_Spire) 147 | } 148 | _ => self.completed_unit_count(unit), 149 | }) >= amount 150 | } 151 | 152 | pub fn incomplete_unit_count(&self, unit: UnitType) -> i32 { 153 | self.all_unit_count(unit) - self.completed_unit_count(unit) 154 | } 155 | 156 | pub fn is_ally(&self, other: &Player) -> bool { 157 | self.data.isAlly[other.id] 158 | } 159 | 160 | pub fn is_defeated(&self) -> bool { 161 | self.data.isDefeated 162 | } 163 | 164 | pub fn is_enemy(&self, other: &Player) -> bool { 165 | self.data.isEnemy[other.id] 166 | } 167 | 168 | pub fn is_neutral(&self) -> bool { 169 | self.data.isNeutral 170 | } 171 | 172 | pub fn is_observer(&self) -> bool { 173 | !self.data.isParticipating 174 | } 175 | 176 | pub fn is_research_available(&self, tech: TechType) -> bool { 177 | self.data.isResearchAvailable[tech as usize] 178 | } 179 | 180 | pub fn is_researching(&self, tech: TechType) -> bool { 181 | self.data.isResearching[tech as usize] 182 | } 183 | 184 | pub fn is_unit_available(&self, unit: UnitType) -> bool { 185 | self.data.isUnitAvailable[unit as usize] 186 | } 187 | 188 | pub fn is_upgrading(&self, upgrade: UpgradeType) -> bool { 189 | self.data.isUpgrading[upgrade as usize] 190 | } 191 | 192 | pub fn is_victorious(&self) -> bool { 193 | self.data.isVictorious 194 | } 195 | 196 | pub fn killed_unit_count(&self, unit: UnitType) -> i32 { 197 | self.data.killedUnitCount[unit as usize] 198 | } 199 | 200 | pub fn left_game(&self) -> bool { 201 | self.data.leftGame 202 | } 203 | 204 | pub fn max_energy(&self, unit: UnitType) -> i32 { 205 | let mut energy = unit.max_energy(); 206 | if (unit == UnitType::Protoss_Arbiter 207 | && self.get_upgrade_level(UpgradeType::Khaydarin_Core) > 0) 208 | || (unit == UnitType::Protoss_Corsair 209 | && self.get_upgrade_level(UpgradeType::Argus_Jewel) > 0) 210 | || (unit == UnitType::Protoss_Dark_Archon 211 | && self.get_upgrade_level(UpgradeType::Argus_Talisman) > 0) 212 | || (unit == UnitType::Protoss_High_Templar 213 | && self.get_upgrade_level(UpgradeType::Khaydarin_Amulet) > 0) 214 | || (unit == UnitType::Terran_Ghost 215 | && self.get_upgrade_level(UpgradeType::Moebius_Reactor) > 0) 216 | || (unit == UnitType::Terran_Battlecruiser 217 | && self.get_upgrade_level(UpgradeType::Colossus_Reactor) > 0) 218 | || (unit == UnitType::Terran_Science_Vessel 219 | && self.get_upgrade_level(UpgradeType::Titan_Reactor) > 0) 220 | || (unit == UnitType::Terran_Wraith 221 | && self.get_upgrade_level(UpgradeType::Apollo_Reactor) > 0) 222 | || (unit == UnitType::Terran_Medic 223 | && self.get_upgrade_level(UpgradeType::Caduceus_Reactor) > 0) 224 | || (unit == UnitType::Zerg_Defiler 225 | && self.get_upgrade_level(UpgradeType::Metasynaptic_Node) > 0) 226 | || (unit == UnitType::Zerg_Queen 227 | && self.get_upgrade_level(UpgradeType::Gamete_Meiosis) > 0) 228 | { 229 | energy += 50 230 | } 231 | energy 232 | } 233 | 234 | pub fn minerals(&self) -> i32 { 235 | self.data.minerals 236 | } 237 | 238 | pub fn refunded_gas(&self) -> i32 { 239 | self.data.refundedGas 240 | } 241 | 242 | pub fn refunded_minerals(&self) -> i32 { 243 | self.data.refundedMinerals 244 | } 245 | 246 | pub fn repaired_gas(&self) -> i32 { 247 | self.data.repairedGas 248 | } 249 | 250 | pub fn repaired_minerals(&self) -> i32 { 251 | self.data.repairedMinerals 252 | } 253 | 254 | pub fn sight_range(&self, unit: UnitType) -> i32 { 255 | let mut range = unit.sight_range(); 256 | if (unit == UnitType::Terran_Ghost 257 | && self.get_upgrade_level(UpgradeType::Ocular_Implants) > 0) 258 | || (unit == UnitType::Zerg_Overlord 259 | && self.get_upgrade_level(UpgradeType::Antennae) > 0) 260 | || (unit == UnitType::Protoss_Observer 261 | && self.get_upgrade_level(UpgradeType::Sensor_Array) > 0) 262 | || (unit == UnitType::Protoss_Scout 263 | && self.get_upgrade_level(UpgradeType::Apial_Sensors) > 0) 264 | { 265 | range = 11 * 32 266 | } 267 | range 268 | } 269 | 270 | pub fn spent_gas(&self) -> i32 { 271 | self.gathered_gas() + self.refunded_gas() - self.gas() - self.repaired_gas() 272 | } 273 | 274 | pub fn spent_minerals(&self) -> i32 { 275 | self.gathered_minerals() + self.refunded_minerals() 276 | - self.minerals() 277 | - self.repaired_minerals() 278 | } 279 | 280 | pub fn supply_total(&self) -> i32 { 281 | self.supply_total_for(self.get_race()) 282 | } 283 | 284 | pub fn supply_total_for(&self, race: Race) -> i32 { 285 | self.data.supplyTotal[race as usize] 286 | } 287 | 288 | pub fn supply_used(&self) -> i32 { 289 | self.supply_used_by(self.get_race()) 290 | } 291 | 292 | pub fn supply_used_by(&self, race: Race) -> i32 { 293 | self.data.supplyUsed[race as usize] 294 | } 295 | 296 | pub fn top_speed(&self, unit: UnitType) -> f64 { 297 | let mut speed = unit.top_speed(); 298 | if (unit == UnitType::Terran_Vulture 299 | && self.get_upgrade_level(UpgradeType::Ion_Thrusters) > 0) 300 | || (unit == UnitType::Zerg_Overlord 301 | && self.get_upgrade_level(UpgradeType::Pneumatized_Carapace) > 0) 302 | || (unit == UnitType::Zerg_Zergling 303 | && self.get_upgrade_level(UpgradeType::Metabolic_Boost) > 0) 304 | || (unit == UnitType::Zerg_Hydralisk 305 | && self.get_upgrade_level(UpgradeType::Muscular_Augments) > 0) 306 | || (unit == UnitType::Protoss_Zealot 307 | && self.get_upgrade_level(UpgradeType::Leg_Enhancements) > 0) 308 | || (unit == UnitType::Protoss_Shuttle 309 | && self.get_upgrade_level(UpgradeType::Gravitic_Drive) > 0) 310 | || (unit == UnitType::Protoss_Observer 311 | && self.get_upgrade_level(UpgradeType::Gravitic_Boosters) > 0) 312 | || (unit == UnitType::Protoss_Scout 313 | && self.get_upgrade_level(UpgradeType::Gravitic_Thrusters) > 0) 314 | || (unit == UnitType::Zerg_Ultralisk 315 | && self.get_upgrade_level(UpgradeType::Anabolic_Synthesis) > 0) 316 | { 317 | if unit == UnitType::Protoss_Scout { 318 | speed += 427.0 / 256.0; 319 | } else { 320 | speed *= 1.5; 321 | } 322 | if speed < 853.0 / 256.0 { 323 | speed = 853.0 / 256.0; 324 | } 325 | } 326 | speed 327 | } 328 | 329 | pub fn visible_unit_count(&self, unit: UnitType) -> i32 { 330 | self.data.visibleUnitCount[unit as usize] 331 | } 332 | 333 | pub fn weapon_damage_cooldown(&self, unit: UnitType) -> i32 { 334 | let mut cooldown = unit.ground_weapon().damage_cooldown(); 335 | if unit == UnitType::Zerg_Zergling 336 | && self.get_upgrade_level(UpgradeType::Adrenal_Glands) > 0 337 | { 338 | // Divide cooldown by 2 339 | cooldown /= 2; 340 | // Prevent cooldown from going out of bounds 341 | cooldown = cooldown.clamp(5, 250); 342 | } 343 | cooldown 344 | } 345 | 346 | pub fn weapon_max_range(&self, weapon: WeaponType) -> i32 { 347 | weapon.max_range() + self.weapon_range_extension(weapon) 348 | } 349 | 350 | pub fn weapon_range_extension(&self, weapon: WeaponType) -> i32 { 351 | if (weapon == WeaponType::Gauss_Rifle 352 | && self.get_upgrade_level(UpgradeType::U_238_Shells) > 0) 353 | || (weapon == WeaponType::Needle_Spines 354 | && self.get_upgrade_level(UpgradeType::Grooved_Spines) > 0) 355 | { 356 | 32 357 | } else if weapon == WeaponType::Phase_Disruptor 358 | && self.get_upgrade_level(UpgradeType::Singularity_Charge) > 0 359 | { 360 | 2 * 32 361 | } else if weapon == WeaponType::Hellfire_Missile_Pack 362 | && self.get_upgrade_level(UpgradeType::Charon_Boosters) > 0 363 | { 364 | 3 * 32 365 | } else { 366 | 0 367 | } 368 | } 369 | } 370 | 371 | impl PartialEq for Player { 372 | fn eq(&self, other: &Self) -> bool { 373 | self.id == other.id 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/predicate.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | use core::ops::{BitAnd, BitOr, BitXor, Not}; 3 | 4 | pub trait Predicate { 5 | type Item; 6 | 7 | fn test(&self, item: &Self::Item) -> bool; 8 | } 9 | 10 | #[derive(Clone, Copy, Debug)] 11 | pub struct FnPredicate(F, PhantomData); 12 | 13 | impl bool> Predicate for FnPredicate { 14 | type Item = T; 15 | 16 | fn test(&self, item: &T) -> bool { 17 | self.0(item) 18 | } 19 | } 20 | 21 | #[derive(Clone, Copy, Debug)] 22 | pub struct AndPredicate(A, B); 23 | 24 | impl> Predicate for AndPredicate { 25 | type Item = A::Item; 26 | 27 | fn test(&self, item: &Self::Item) -> bool { 28 | let Self(a, b) = self; 29 | a.test(item) && b.test(item) 30 | } 31 | } 32 | 33 | #[derive(Clone, Copy, Debug)] 34 | pub struct OrPredicate(A, B); 35 | 36 | impl> Predicate for OrPredicate { 37 | type Item = A::Item; 38 | 39 | fn test(&self, item: &Self::Item) -> bool { 40 | let Self(a, b) = self; 41 | a.test(item) || b.test(item) 42 | } 43 | } 44 | 45 | #[derive(Clone, Copy, Debug)] 46 | pub struct XorPredicate(A, B); 47 | 48 | impl> Predicate for XorPredicate { 49 | type Item = A::Item; 50 | 51 | fn test(&self, item: &Self::Item) -> bool { 52 | let Self(a, b) = self; 53 | a.test(item) ^ b.test(item) 54 | } 55 | } 56 | 57 | #[derive(Clone, Copy, Debug)] 58 | pub struct NotPredicate(A); 59 | 60 | impl Predicate for NotPredicate { 61 | type Item = A::Item; 62 | 63 | fn test(&self, item: &Self::Item) -> bool { 64 | !self.0.test(item) 65 | } 66 | } 67 | 68 | pub trait IntoPredicate { 69 | type Pred: Predicate; 70 | 71 | fn into_predicate(self) -> Self::Pred; 72 | } 73 | 74 | impl bool> IntoPredicate for F { 75 | type Pred = FnPredicate; 76 | 77 | fn into_predicate(self) -> Self::Pred { 78 | FnPredicate(self, PhantomData) 79 | } 80 | } 81 | 82 | macro_rules! impl_pred { 83 | ($(($($type_args:tt)*) $type:ty),* $(,)?) => {$( 84 | impl<$($type_args)*> IntoPredicate<::Item> for $type where $type : Predicate { 85 | type Pred = Self; 86 | 87 | fn into_predicate(self) -> Self::Pred { 88 | self 89 | } 90 | } 91 | 92 | impl::Item>, $($type_args)*> BitAnd

for $type where $type : Predicate { 93 | 94 | type Output = AndPredicate; 95 | 96 | fn bitand(self, other: P) -> Self::Output { 97 | AndPredicate(self, other.into_predicate()) 98 | } 99 | } 100 | impl::Item>, $($type_args)*> BitOr

for $type where $type : Predicate { 101 | 102 | type Output = OrPredicate; 103 | 104 | fn bitor(self, other: P) -> Self::Output { 105 | OrPredicate(self, other.into_predicate()) 106 | } 107 | } 108 | impl::Item>, $($type_args)*> BitXor

for $type where $type : Predicate { 109 | 110 | type Output = XorPredicate; 111 | 112 | fn bitxor(self, other: P) -> Self::Output { 113 | XorPredicate(self, other.into_predicate()) 114 | } 115 | } 116 | 117 | impl<$($type_args)*> Not for $type where $type : Predicate { 118 | type Output = NotPredicate; 119 | fn not(self) -> Self::Output { 120 | NotPredicate(self) 121 | } 122 | } 123 | 124 | )*}} 125 | 126 | impl_pred! { 127 | (A, B) AndPredicate, 128 | (A, B) XorPredicate, 129 | (A, B) OrPredicate, 130 | (A) NotPredicate, 131 | (F, T) FnPredicate 132 | } 133 | -------------------------------------------------------------------------------- /src/projected.rs: -------------------------------------------------------------------------------- 1 | use crate::Game; 2 | use std::ops::Deref; 3 | 4 | #[derive(Clone)] 5 | pub(crate) struct Projected { 6 | owner: G, 7 | data: *const T, 8 | } 9 | 10 | impl Deref for Projected { 11 | type Target = T; 12 | 13 | fn deref(&self) -> &Self::Target { 14 | // Unsafe! The reference is inside shared memory "held" by Game. 15 | // And RSBWAPI will only mutate in one function (as will BWAPI externally). 16 | unsafe { &*self.data } 17 | } 18 | } 19 | 20 | impl Projected { 21 | pub(crate) unsafe fn new(owner: G, data: *const T) -> Self { 22 | Self { owner, data } 23 | } 24 | 25 | pub(crate) fn owner(&self) -> &G { 26 | &self.owner 27 | } 28 | } 29 | 30 | impl Projected { 31 | pub(crate) fn game(&self) -> &Game { 32 | self.owner() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/region.rs: -------------------------------------------------------------------------------- 1 | use crate::projected::Projected; 2 | use crate::{Game, Position}; 3 | use bwapi_wrapper::*; 4 | 5 | #[derive(Clone)] 6 | pub struct Region { 7 | inner: Projected, 8 | } 9 | 10 | impl Region { 11 | pub(crate) fn new(id: u16, game: Game) -> Self { 12 | let region = &game.inner.data.regions[id as usize] as *const BWAPI_RegionData; 13 | Self { 14 | inner: unsafe { Projected::new(game, region) }, 15 | } 16 | } 17 | 18 | pub fn get_region_group_id(&self) -> i32 { 19 | self.inner.islandID 20 | } 21 | 22 | pub fn get_center(&self) -> Position { 23 | Position { 24 | x: self.inner.center_x, 25 | y: self.inner.center_y, 26 | } 27 | } 28 | 29 | pub fn is_higher_ground(&self) -> bool { 30 | self.inner.isHigherGround 31 | } 32 | 33 | pub fn get_defense_priority(&self) -> i32 { 34 | self.inner.priority 35 | } 36 | 37 | pub fn is_accessible(&self) -> bool { 38 | self.inner.isAccessible 39 | } 40 | 41 | pub fn get_id(&self) -> i32 { 42 | self.inner.id 43 | } 44 | 45 | pub fn get_bounds_left(&self) -> i32 { 46 | self.inner.leftMost 47 | } 48 | 49 | pub fn get_bounds_top(&self) -> i32 { 50 | self.inner.topMost 51 | } 52 | 53 | pub fn get_bounds_right(&self) -> i32 { 54 | self.inner.rightMost 55 | } 56 | 57 | pub fn get_bounds_bottom(&self) -> i32 { 58 | self.inner.bottomMost 59 | } 60 | 61 | pub fn get_neighbors(&self) -> Vec { 62 | (0..self.inner.neighborCount as usize) 63 | .map(|idx| { 64 | self.inner 65 | .game() 66 | .get_region(idx as u16) 67 | .expect("neighbor region to exist") 68 | }) 69 | .collect() 70 | } 71 | 72 | pub fn get_closest_accessible_region(&self) -> Option { 73 | self.get_neighbors() 74 | .iter() 75 | .filter(|r| r.is_accessible()) 76 | .min_by_key(|r| self.get_center().get_approx_distance(r.get_center())) 77 | .cloned() 78 | } 79 | 80 | pub fn get_closest_inaccessible_region(&self) -> Option { 81 | self.get_neighbors() 82 | .iter() 83 | .filter(|r| !r.is_accessible()) 84 | .min_by_key(|r| self.get_center().get_approx_distance(r.get_center())) 85 | .cloned() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/shm.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Deref, DerefMut}; 2 | use core::ptr::NonNull; 3 | 4 | #[cfg(windows)] 5 | use std::ffi::CString; 6 | #[cfg(windows)] 7 | use winapi::shared::minwindef::FALSE; 8 | #[cfg(windows)] 9 | use winapi::um::handleapi::CloseHandle; 10 | #[cfg(windows)] 11 | use winapi::um::memoryapi::{MapViewOfFile, FILE_MAP_READ, FILE_MAP_WRITE}; 12 | #[cfg(windows)] 13 | use winapi::um::winbase::OpenFileMappingA; 14 | #[cfg(windows)] 15 | use winapi::um::winnt::HANDLE; 16 | 17 | #[cfg(windows)] 18 | pub(crate) struct Shm(HANDLE, NonNull); 19 | 20 | #[cfg(not(windows))] 21 | pub(crate) struct Shm((), NonNull); 22 | 23 | impl Deref for Shm { 24 | type Target = T; 25 | 26 | fn deref(&self) -> &Self::Target { 27 | unsafe { self.1.as_ref() } 28 | } 29 | } 30 | 31 | impl DerefMut for Shm { 32 | fn deref_mut(&mut self) -> &mut Self::Target { 33 | unsafe { self.1.as_mut() } 34 | } 35 | } 36 | 37 | impl Shm { 38 | pub(crate) fn as_ptr(&self) -> *const T { 39 | self.1.as_ptr() 40 | } 41 | 42 | pub(crate) fn get(&self) -> &T { 43 | // Not safe at all 44 | unsafe { self.1.as_ref() } 45 | } 46 | 47 | #[cfg(test)] 48 | pub fn from_mut_slice(data: &mut [u8]) -> Shm { 49 | Self((), NonNull::new(data.as_mut_ptr()).unwrap().cast()) 50 | } 51 | } 52 | 53 | #[cfg(windows)] 54 | impl Drop for Shm { 55 | fn drop(&mut self) { 56 | unsafe { 57 | CloseHandle(self.0); 58 | } 59 | } 60 | } 61 | 62 | #[cfg(windows)] 63 | pub(crate) fn map_memory(name: &str) -> Option> { 64 | let memory_size = std::mem::size_of::(); 65 | let lp_name = CString::new(name).unwrap(); 66 | unsafe { 67 | let handle = OpenFileMappingA(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, lp_name.as_ptr()); 68 | if handle.is_null() { 69 | // BWAPI Server is most likely not running yet 70 | return None; 71 | } 72 | let mapped = 73 | MapViewOfFile(handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, memory_size) as *mut T; 74 | Some(Shm(handle, NonNull::new_unchecked(mapped))) 75 | } 76 | } 77 | 78 | #[cfg(not(windows))] 79 | pub(crate) fn map_memory(_name: &str) -> Option> { 80 | None 81 | } 82 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use bwapi_wrapper::prelude::{Error, ScaledPosition, TilePosition, WalkPosition}; 2 | use derive_more::{Add, AddAssign, Sub, SubAssign}; 3 | use num_derive::FromPrimitive; 4 | use std::ffi::CStr; 5 | use std::os::raw::c_char; 6 | 7 | /// Many functions produce error code. BWAPI usually sets a error variable and returns a success flag. 8 | /// Rsbwapi instead returns a result with the error code. 9 | pub type BwResult = Result; 10 | 11 | #[derive(Debug, Copy, Clone, FromPrimitive)] 12 | pub enum Color { 13 | ///

The default color for Player 1. 14 | Red = 111, 15 | 16 | /// The default color for Player 2. 17 | Blue = 165, 18 | 19 | /// The default color for Player 3. 20 | Teal = 159, 21 | 22 | /// The default color for Player 4. 23 | Purple = 164, 24 | 25 | /// The default color for Player 5. 26 | Orange = 179, 27 | 28 | /// The default color for Player 6. 29 | Brown = 19, 30 | 31 | /// A bright white. Note that this is lighter than Player 7's white. 32 | White = 255, 33 | 34 | /// The default color for Player 8. 35 | Yellow = 135, 36 | 37 | /// The alternate color for Player 7 on Ice tilesets. 38 | Green = 117, 39 | 40 | /// The default color for Neutral (Player 12). 41 | Cyan = 128, 42 | 43 | /// The color black 44 | Black = 0, 45 | 46 | /// The color grey 47 | Grey = 74, 48 | } 49 | 50 | #[derive(Debug)] 51 | pub enum TextSize { 52 | /// The smallest text size in the game. 53 | Small, 54 | 55 | /// The standard text size, used for most things in the game such as chat messages. 56 | Default, 57 | 58 | /// A larger text size. This size is used for the in-game countdown timer seen in @CTF and @UMS game types. 59 | Large, 60 | 61 | /// The largest text size in the game. 62 | Huge, 63 | } 64 | 65 | pub(crate) fn c_str_to_str(i: &[c_char]) -> String { 66 | unsafe { 67 | let i = &*(i as *const [c_char] as *const [u8]); 68 | CStr::from_bytes_with_nul_unchecked(&i[..=i.iter().position(|&c| c == 0).unwrap()]) 69 | .to_string_lossy() 70 | .to_string() 71 | } 72 | } 73 | 74 | #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Add, Sub, AddAssign, SubAssign)] 75 | pub struct Rectangle

{ 76 | pub tl: P, 77 | pub br: P, 78 | } 79 | 80 | impl Rectangle> { 81 | pub fn new>, P2: Into>>( 82 | corner_a: P1, 83 | corner_b: P2, 84 | ) -> Self { 85 | let (mut a, mut b) = (corner_a.into(), corner_b.into()); 86 | if a.x > b.x { 87 | core::mem::swap(&mut a.x, &mut b.x); 88 | } 89 | if a.y > b.y { 90 | core::mem::swap(&mut a.y, &mut b.y); 91 | } 92 | Rectangle { tl: a, br: b } 93 | } 94 | 95 | pub fn border(&self) -> Vec> { 96 | (self.tl.x..=self.br.x) 97 | .map(|x| ScaledPosition::::new(x, self.tl.y)) 98 | .chain((self.tl.y + 1..self.br.y).map(|y| ScaledPosition::::new(self.br.x, y))) 99 | .chain( 100 | (self.tl.x..=self.br.x) 101 | .rev() 102 | .map(|x| ScaledPosition::::new(x, self.br.y)), 103 | ) 104 | .chain( 105 | (self.tl.y + 1..self.br.y) 106 | .rev() 107 | .map(|y| ScaledPosition::::new(self.tl.x, y)), 108 | ) 109 | .collect() 110 | } 111 | 112 | pub fn extrude(self, amount: i32) -> Self { 113 | Self::new(self.tl - (amount, amount), self.br + (amount, amount)) 114 | } 115 | 116 | pub fn shrink(self, amount: i32) -> Self { 117 | self.extrude(-amount) 118 | } 119 | 120 | pub fn envelops(&self, other: Rectangle>) -> bool { 121 | self.tl.x * N <= other.tl.x * M 122 | && self.tl.y * N <= other.tl.y * M 123 | && self.br.x * N >= other.br.x * M 124 | && self.br.y * N >= other.br.y * M 125 | } 126 | 127 | pub fn intersects(&self, other: Rectangle>) -> bool { 128 | self.tl.x * N <= other.br.x * M 129 | && self.tl.y * N <= other.br.y * M 130 | && self.br.x * N >= other.tl.x * M 131 | && self.br.y * N >= other.tl.y * M 132 | } 133 | 134 | pub fn contains(&self, pos: ScaledPosition) -> bool { 135 | self.tl.x * N <= pos.x * M 136 | && self.tl.y * N <= pos.y * M 137 | && self.br.x * N >= pos.x * M 138 | && self.br.y * N >= pos.y * M 139 | } 140 | 141 | pub fn resize_to_contain(mut self, pos: ScaledPosition) -> Self { 142 | self.tl.x = self.tl.x.min(pos.x); 143 | self.br.x = self.br.x.min(pos.x); 144 | self.tl.y = self.tl.y.min(pos.y); 145 | self.br.y = self.br.y.min(pos.y); 146 | self 147 | } 148 | 149 | pub fn width(&self) -> i32 { 150 | self.br.x - self.tl.x + 1 151 | } 152 | 153 | pub fn height(&self) -> i32 { 154 | self.br.y - self.tl.y + 1 155 | } 156 | } 157 | 158 | impl Rectangle { 159 | pub fn to_walk_rect(self) -> Rectangle { 160 | Rectangle { 161 | tl: self.tl.to_walk_position(), 162 | br: self.br.to_walk_position(), 163 | } 164 | } 165 | } 166 | 167 | impl From<(i32, i32, i32, i32)> for Rectangle> { 168 | fn from(coords: (i32, i32, i32, i32)) -> Self { 169 | Self::new((coords.0, coords.1), (coords.2, coords.3)) 170 | } 171 | } 172 | 173 | impl std::iter::IntoIterator for Rectangle> { 174 | type Item = ScaledPosition; 175 | type IntoIter = std::vec::IntoIter; 176 | 177 | fn into_iter(self) -> Self::IntoIter { 178 | (self.tl.y..=self.br.y) 179 | .flat_map(|y| (self.tl.x..=self.br.x).map(move |x| ScaledPosition::::new(x, y))) 180 | .collect::>() 181 | .into_iter() 182 | } 183 | } 184 | 185 | #[cfg(test)] 186 | mod test { 187 | use super::*; 188 | use crate::*; 189 | 190 | #[test] 191 | fn should_return_all_positions_of_rectangle() { 192 | let rec: Rectangle = Rectangle::new((0, 0), (2, 2)); 193 | let mut iter = rec.into_iter(); 194 | assert_eq!(Some(TilePosition { x: 0, y: 0 }), iter.next()); 195 | assert_eq!(Some(TilePosition { x: 1, y: 0 }), iter.next()); 196 | assert_eq!(Some(TilePosition { x: 2, y: 0 }), iter.next()); 197 | assert_eq!(Some(TilePosition { x: 0, y: 1 }), iter.next()); 198 | assert_eq!(Some(TilePosition { x: 1, y: 1 }), iter.next()); 199 | assert_eq!(Some(TilePosition { x: 2, y: 1 }), iter.next()); 200 | assert_eq!(Some(TilePosition { x: 0, y: 2 }), iter.next()); 201 | assert_eq!(Some(TilePosition { x: 1, y: 2 }), iter.next()); 202 | assert_eq!(Some(TilePosition { x: 2, y: 2 }), iter.next()); 203 | } 204 | 205 | #[test] 206 | fn should_return_border() { 207 | let rec: Rectangle = Rectangle::new((3, 4), (5, 6)); 208 | assert_eq!( 209 | rec.border(), 210 | [ 211 | ScaledPosition { x: 3, y: 4 }, 212 | ScaledPosition { x: 4, y: 4 }, 213 | ScaledPosition { x: 5, y: 4 }, 214 | ScaledPosition { x: 5, y: 5 }, 215 | ScaledPosition { x: 5, y: 6 }, 216 | ScaledPosition { x: 4, y: 6 }, 217 | ScaledPosition { x: 3, y: 6 }, 218 | ScaledPosition { x: 3, y: 5 }, 219 | ] 220 | ); 221 | } 222 | } 223 | --------------------------------------------------------------------------------