├── ceres-parsers ├── src │ ├── lib.rs │ ├── test-cases │ │ ├── big.lua │ │ ├── vararg.lua │ │ ├── locals.lua │ │ ├── verybig.lua │ │ ├── goto.lua │ │ ├── utf8.lua │ │ ├── closure.lua │ │ └── code.lua │ ├── lua.rs │ └── lua.pest └── Cargo.toml ├── ceres-core ├── src │ ├── resource │ │ ├── buildscript_default.lua │ │ ├── map_footer.lua │ │ └── map_header.lua │ ├── lua │ │ ├── compiler.rs │ │ ├── mod.rs │ │ ├── launcher.rs │ │ ├── macros.rs │ │ └── util.rs │ ├── evloop.rs │ ├── lib.rs │ └── error.rs └── Cargo.toml ├── .gitattributes ├── .gitmodules ├── ceres-data ├── data │ ├── units_en │ │ ├── unitglobalstrings.txt │ │ ├── neutralupgradestrings.txt │ │ ├── campaignupgradestrings.txt │ │ ├── commonabilitystrings.txt │ │ ├── undeadupgradestrings.txt │ │ ├── humanupgradestrings.txt │ │ ├── orcupgradestrings.txt │ │ └── nightelfupgradestrings.txt │ ├── units │ │ ├── neutralupgradefunc.txt │ │ ├── campaignupgradefunc.txt │ │ ├── commandfunc.txt │ │ ├── miscdata.txt │ │ ├── undeadupgradefunc.txt │ │ ├── orcupgradefunc.txt │ │ ├── nightelfupgradefunc.txt │ │ ├── humanupgradefunc.txt │ │ ├── commonabilityfunc.txt │ │ ├── miscgame.txt │ │ └── abilitybuffmetadata.slk │ └── ui │ │ └── aieditordata.txt ├── Cargo.toml ├── src │ └── lib.rs └── build.rs ├── Cargo.toml ├── .gitignore ├── ci ├── script.sh ├── before_deploy.sh └── install.sh ├── rustfmt.toml ├── ceres-binaries ├── Cargo.toml └── src │ └── bin │ ├── ceres.rs │ └── util.rs ├── ceres-formats ├── Cargo.toml └── src │ ├── error.rs │ ├── parser │ ├── crlf.rs │ ├── profile.rs │ └── slk.rs │ ├── lib.rs │ └── object.rs ├── LICENSE ├── .travis.yml ├── README.md └── CHANGELOG.md /ceres-parsers/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod lua; 2 | -------------------------------------------------------------------------------- /ceres-core/src/resource/buildscript_default.lua: -------------------------------------------------------------------------------- 1 | ceres.defaultHandler() -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ceres-parsers/src/test-cases/* linguist-vendored 2 | docs/* linguist-documentation -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rlua-st"] 2 | path = rlua-st 3 | url = https://github.com/ElusiveMori/rlua-st.git 4 | -------------------------------------------------------------------------------- /ceres-core/src/resource/map_footer.lua: -------------------------------------------------------------------------------- 1 | --[[ ceres map post-script start ]] 2 | ceres.init() 3 | --[[ ceres map post-script end ]] -------------------------------------------------------------------------------- /ceres-data/data/units_en/unitglobalstrings.txt: -------------------------------------------------------------------------------- 1 | [Categories] 2 | GiantClass=Giant 3 | UndeadClass=Undead 4 | MechanicalClass=Mechanical 5 | TaurenClass=Tauren -------------------------------------------------------------------------------- /ceres-parsers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ceres-parsers" 3 | version = "0.1.0-INTERNAL" 4 | authors = ["mori"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | pest = "2.1.2" 9 | pest_derive = "2.1.0" 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "ceres-binaries", 5 | "ceres-parsers", 6 | "ceres-core", 7 | "ceres-formats", 8 | "ceres-data" 9 | ] 10 | 11 | [profile.dev] 12 | opt-level = 0 13 | 14 | [patch.crates-io] 15 | rlua = { path = "./rlua-st" } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | *.log 4 | !.gitattributes 5 | !/docs 6 | !/ceres-binaries 7 | !/ceres-core 8 | !/ceres-mpq 9 | !/ceres-parsers 10 | !/ceres-formats 11 | !/ceres-data 12 | !/ci 13 | !/storm-sys 14 | !Cargo.lock 15 | !Cargo.toml 16 | !rustfmt.toml 17 | !README.md 18 | !CHANGELOG.md 19 | *.slk# -------------------------------------------------------------------------------- /ceres-data/data/units/neutralupgradefunc.txt: -------------------------------------------------------------------------------- 1 | // Non-race-specific upgrades 2 | // glyph of fortification 3 | [Rgfo] 4 | Art=ReplaceableTextures\CommandButtons\BTNGlyph.blp,ReplaceableTextures\CommandButtons\BTNGlyph.blp,ReplaceableTextures\CommandButtons\BTNGlyph.blp 5 | 6 | // glyph of ultravision 7 | [Rguv] 8 | Art=ReplaceableTextures\CommandButtons\BTNGlyph.blp -------------------------------------------------------------------------------- /ceres-data/data/units_en/neutralupgradestrings.txt: -------------------------------------------------------------------------------- 1 | // Non-race-specific upgrades 2 | // glyph of fortification 3 | [Rgfo] 4 | Name=Glyph of Fortification,Glyph of Fortification,Glyph of Fortification 5 | EditorSuffix= (Upgrade 1), (Upgrade 2), (Upgrade 3) 6 | 7 | // glyph of ultravision 8 | [Rguv] 9 | Name=Glyph of Ultravision 10 | EditorSuffix= (Upgrade) 11 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | # This script takes care of testing your crate 2 | 3 | set -ex 4 | 5 | main() { 6 | cross build --target $TARGET 7 | 8 | if [ ! -z $DISABLE_TESTS ]; then 9 | return 10 | fi 11 | 12 | cross test --target $TARGET 13 | } 14 | 15 | # we don't run the "test phase" when doing deploys 16 | if [ -z $TRAVIS_TAG ]; then 17 | main 18 | fi 19 | -------------------------------------------------------------------------------- /ceres-data/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ceres-data" 3 | version = "0.1.0-INTERNAL" 4 | authors = ["SamuelMoriarty "] 5 | edition = "2018" 6 | build = "build.rs" 7 | 8 | [dependencies] 9 | ceres-formats = {path = "../ceres-formats"} 10 | bincode = "1.2.1" 11 | lazy_static = "1.4.0" 12 | 13 | [build-dependencies] 14 | ceres-formats = {path = "../ceres-formats"} 15 | bincode = "1.2.1" 16 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | 3 | unstable_features = true 4 | 5 | # indent_style = "visual" 6 | indent_style = "block" 7 | 8 | merge_imports = false 9 | reorder_imports = false 10 | reorder_modules = false 11 | reorder_impl_items = true 12 | 13 | struct_field_align_threshold = 20 14 | enum_discrim_align_threshold = 20 15 | 16 | use_field_init_shorthand = true 17 | overflow_delimited_expr = false -------------------------------------------------------------------------------- /ceres-binaries/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ceres-binaries" 3 | version = "0.1.0-INTERNAL" 4 | authors = ["mori"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ceres-parsers = {path = "../ceres-parsers"} 9 | ceres-formats = {path = "../ceres-formats"} 10 | ceres-core = {path = "../ceres-core"} 11 | 12 | serde = "1.0.104" 13 | pest = "2.1.2" 14 | rlua = "0.16.3" 15 | ron = "0.5.1" 16 | bincode = "1.2.1" 17 | anyhow = "1.0.26" 18 | dotenv = "0.15.0" 19 | 20 | [dependencies.clap] 21 | version = "2.33.0" 22 | default-features = false 23 | -------------------------------------------------------------------------------- /ceres-formats/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ceres-formats" 3 | version = "0.1.0-INTERNAL" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | 8 | # util 9 | lazy_static = "1.4.0" 10 | atoi = "0.3.2" 11 | indexmap = "1.3.2" 12 | slotmap = { version = "0.4.0", features = ["serde"] } 13 | num-traits = "0.2.11" 14 | bitflags = "1.2.1" 15 | 16 | # parsing/casting 17 | byteorder = "1.3.4" 18 | byte-slice-cast = "0.3.5" 19 | serde = { version = "1.0.104", features = ["derive", "rc"] } 20 | 21 | # lua 22 | rlua = "0.16.3" 23 | 24 | # error handling 25 | thiserror = "1.0.11" 26 | anyhow = "1.0.26" 27 | -------------------------------------------------------------------------------- /ceres-data/src/lib.rs: -------------------------------------------------------------------------------- 1 | use ceres_formats::metadata::MetadataStore; 2 | use ceres_formats::objectstore::ObjectStoreStock; 3 | use lazy_static::lazy_static; 4 | 5 | const BUNDLED_DATA_BIN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/data.bin")); 6 | 7 | lazy_static! { 8 | static ref BUNDLED_DATA: (ObjectStoreStock, MetadataStore) = { 9 | let data: (ObjectStoreStock, MetadataStore) = 10 | bincode::deserialize(BUNDLED_DATA_BIN).unwrap(); 11 | 12 | data 13 | }; 14 | } 15 | 16 | pub fn metadata() -> &'static MetadataStore { 17 | &BUNDLED_DATA.1 18 | } 19 | 20 | pub fn data() -> &'static ObjectStoreStock { 21 | &BUNDLED_DATA.0 22 | } 23 | -------------------------------------------------------------------------------- /ceres-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ceres-core" 3 | version = "0.1.0-INTERNAL" 4 | authors = ["mori"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | pest = "2.1.2" 9 | rlua = "0.16.3" 10 | serde = { version = "1.0.104", features = ["derive"] } 11 | serde_json = "1.0.48" 12 | toml = "0.5.6" 13 | indexmap = "1.3.2" 14 | walkdir = "2.3.1" 15 | itertools = "0.8.2" 16 | path-absolutize = "1.1.7" 17 | atoi = "0.3.2" 18 | notify = "4.0.15" 19 | 20 | # error handling 21 | thiserror = "1.0.11" 22 | anyhow = "1.0.26" 23 | 24 | # internal 25 | ceres-mpq = "0.1.8" 26 | ceres-formats = {path = "../ceres-formats"} 27 | ceres-parsers = {path = "../ceres-parsers"} 28 | ceres-data = {path = "../ceres-data"} 29 | -------------------------------------------------------------------------------- /ceres-formats/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error as IoError; 2 | 3 | use thiserror::Error; 4 | 5 | use crate::ObjectId; 6 | 7 | #[derive(Debug, Error)] 8 | pub enum ObjParseError { 9 | #[error("Parse error")] 10 | Io { 11 | #[from] 12 | source: IoError, 13 | }, 14 | #[error("C String is unterminated")] 15 | UnterminatedString, 16 | #[error("Unknown field {id}")] 17 | UnknownField { id: ObjectId }, 18 | } 19 | 20 | impl ObjParseError { 21 | pub fn unknown_field(id: ObjectId) -> ObjParseError { 22 | ObjParseError::UnknownField { id } 23 | } 24 | 25 | pub fn unterminated_string() -> ObjParseError { 26 | ObjParseError::UnterminatedString 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ceres-data/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | 5 | use ceres_formats::metadata; 6 | use ceres_formats::objectstore; 7 | 8 | fn main() { 9 | let out_dir: PathBuf = env::var("OUT_DIR").unwrap().into(); 10 | let crate_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into(); 11 | 12 | let meta = metadata::read_metadata_dir(crate_dir.join("data")); 13 | let data = objectstore::read_data_dir("data", &meta); 14 | let data = objectstore::ObjectStoreStock::new(&data); 15 | 16 | let mut file = fs::OpenOptions::new() 17 | .create(true) 18 | .write(true) 19 | .open(out_dir.join("data.bin")) 20 | .unwrap(); 21 | 22 | bincode::serialize_into(&mut file, &(&data, &meta)).unwrap(); 23 | } 24 | -------------------------------------------------------------------------------- /ceres-data/data/units/campaignupgradefunc.txt: -------------------------------------------------------------------------------- 1 | //naga siren training 2 | [Rnsw] 3 | Art=ReplaceableTextures\CommandButtons\BTNSirenAdept.blp,ReplaceableTextures\CommandButtons\BTNSirenMaster.blp 4 | Buttonpos=0,2 5 | 6 | [Rnen] 7 | Art=ReplaceableTextures\CommandButtons\BTNEnsnare.blp 8 | Buttonpos=0,2 9 | 10 | [Rnsi] 11 | Art=ReplaceableTextures\CommandButtons\BTNDryadDispelMagic.blp 12 | Buttonpos=1,2 13 | 14 | [Rnat] 15 | Art=ReplaceableTextures\CommandButtons\BTNNagaWeaponUp1.blp,ReplaceableTextures\CommandButtons\BTNNagaWeaponUp2.blp,ReplaceableTextures\CommandButtons\BTNNagaWeaponUp3.blp 16 | Buttonpos=0,2 17 | 18 | 19 | [Rnam] 20 | Art=ReplaceableTextures\CommandButtons\BTNNagaArmorUp1.blp,ReplaceableTextures\CommandButtons\BTNNagaArmorUp2.blp,ReplaceableTextures\CommandButtons\BTNNagaArmorUp3.blp 21 | Buttonpos=1,2 22 | 23 | [Rnsb] 24 | Art=ReplaceableTextures\CommandButtons\BTNNagaBurrow.blp 25 | Buttonpos=2,2 26 | 27 | -------------------------------------------------------------------------------- /ci/before_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script takes care of building your crate and packaging it for release 3 | 4 | set -ex 5 | 6 | main() { 7 | local src=$(pwd) \ 8 | stage= 9 | 10 | case $TRAVIS_OS_NAME in 11 | linux) 12 | stage=$(mktemp -d) 13 | ;; 14 | osx) 15 | stage=$(mktemp -d -t tmp) 16 | ;; 17 | esac 18 | 19 | test -f Cargo.lock || cargo generate-lockfile 20 | 21 | cross rustc --manifest-path ceres-binaries/Cargo.toml --bin ceres --release --target $TARGET -- -C lto 22 | 23 | if [ -e "target/$TARGET/release/ceres.exe" ]; then 24 | cp target/$TARGET/release/ceres.exe $stage/ 25 | fi 26 | 27 | if [ -e "target/$TARGET/release/ceres" ]; then 28 | cp target/$TARGET/release/ceres $stage/ 29 | fi 30 | 31 | cd $stage 32 | tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz * 33 | cd $src 34 | 35 | rm -rf $stage 36 | } 37 | 38 | main 39 | -------------------------------------------------------------------------------- /ceres-formats/src/parser/crlf.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Lines<'src> { 3 | source: &'src [u8], 4 | current_pos: usize, 5 | } 6 | 7 | impl<'src> Lines<'src> { 8 | pub fn new(source: &'src [u8]) -> Lines<'src> { 9 | Lines { 10 | source, 11 | current_pos: 0, 12 | } 13 | } 14 | } 15 | 16 | impl<'src> Iterator for Lines<'src> { 17 | type Item = &'src [u8]; 18 | 19 | fn next(&mut self) -> Option { 20 | let mut start = self.current_pos; 21 | while start < self.source.len() 22 | && (self.source[start] == b'\r' || self.source[start] == b'\n') 23 | { 24 | start += 1; 25 | } 26 | 27 | let mut end = start; 28 | while end < self.source.len() && !(self.source[end] == b'\r' || self.source[end] == b'\n') { 29 | end += 1; 30 | } 31 | 32 | self.current_pos = end; 33 | 34 | if end > start { 35 | Some(&self.source[start..end]) 36 | } else { 37 | None 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ElusiveMori 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ceres-core/src/lua/compiler.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use rlua::prelude::*; 4 | 5 | use crate::compiler; 6 | use crate::lua::macros; 7 | use crate::lua::util::wrap_result; 8 | 9 | pub fn get_compile_script_luafn(ctx: LuaContext) -> LuaFunction { 10 | ctx.create_function(|ctx, args: LuaTable| { 11 | let result = compile_script(ctx, args); 12 | 13 | Ok(wrap_result(ctx, result)) 14 | }) 15 | .unwrap() 16 | } 17 | 18 | fn compile_script(ctx: LuaContext, args: LuaTable) -> Result { 19 | let src_directories: Vec = args.get("srcDirectories")?; 20 | let map_script: LuaString = args.get("mapScript")?; 21 | 22 | let src_directories: Vec = src_directories 23 | .iter() 24 | .map(|s| s.to_str().unwrap().into()) 25 | .collect(); 26 | 27 | let mut module_provider = compiler::ProjectModuleProvider::new(&src_directories); 28 | module_provider.scan(); 29 | let macro_provider = macros::get_threadlocal_macro_provider(); 30 | let mut compiler = compiler::ScriptCompiler::new(ctx, module_provider, macro_provider); 31 | 32 | compiler.set_map_script(map_script.to_str()?.into()); 33 | compiler.add_module("main", false)?; 34 | compiler.add_module("config", true)?; 35 | compiler.add_module("init", true)?; 36 | 37 | Ok(compiler.emit_script()) 38 | } 39 | -------------------------------------------------------------------------------- /ci/install.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | main() { 4 | local target= 5 | if [ $TRAVIS_OS_NAME = linux ]; then 6 | target=x86_64-unknown-linux-musl 7 | sort=sort 8 | else 9 | target=x86_64-apple-darwin 10 | sort=gsort # for `sort --sort-version`, from brew's coreutils. 11 | fi 12 | 13 | # Builds for iOS are done on OSX, but require the specific target to be 14 | # installed. 15 | case $TARGET in 16 | aarch64-apple-ios) 17 | rustup target install aarch64-apple-ios 18 | ;; 19 | armv7-apple-ios) 20 | rustup target install armv7-apple-ios 21 | ;; 22 | armv7s-apple-ios) 23 | rustup target install armv7s-apple-ios 24 | ;; 25 | i386-apple-ios) 26 | rustup target install i386-apple-ios 27 | ;; 28 | x86_64-apple-ios) 29 | rustup target install x86_64-apple-ios 30 | ;; 31 | esac 32 | 33 | # This fetches latest stable release 34 | local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \ 35 | | cut -d/ -f3 \ 36 | | grep -E '^v[0.1.0-9.]+$' \ 37 | | $sort --version-sort \ 38 | | tail -n1) 39 | curl -LSfs https://japaric.github.io/trust/install.sh | \ 40 | sh -s -- \ 41 | --force \ 42 | --git japaric/cross \ 43 | --tag $tag \ 44 | --target $target 45 | } 46 | 47 | main 48 | -------------------------------------------------------------------------------- /ceres-data/data/units/commandfunc.txt: -------------------------------------------------------------------------------- 1 | [CmdMove] 2 | Art=CommandMove 3 | Buttonpos=0,0 4 | 5 | [CmdAttack] 6 | Art=CommandAttack 7 | Buttonpos=3,0 8 | 9 | [CmdAttackGround] 10 | Art=CommandAttackGround 11 | Buttonpos=3,1 12 | 13 | [CmdBuild] 14 | Art=CommandBasicStruct 15 | Buttonpos=0,2 16 | 17 | [CmdBuildHuman] 18 | Art=CommandBasicStructHuman 19 | Buttonpos=0,2 20 | 21 | [CmdBuildOrc] 22 | Art=CommandBasicStructOrc 23 | Buttonpos=0,2 24 | 25 | [CmdBuildNightElf] 26 | Art=CommandBasicStructNightElf 27 | Buttonpos=0,2 28 | 29 | [CmdBuildUndead] 30 | Art=CommandBasicStructUndead 31 | Buttonpos=0,2 32 | 33 | [CmdCancel] 34 | Art=CommandCancel 35 | ButtonPos=3,2 36 | 37 | [CmdCancelBuild] 38 | Art=CommandCancel 39 | ButtonPos=3,2 40 | 41 | [CmdCancelTrain] 42 | Art=CommandCancel 43 | ButtonPos=3,2 44 | 45 | [CmdCancelRevive] 46 | Art=CommandCancel 47 | ButtonPos=3,2 48 | 49 | [CmdHoldPos] 50 | Art=CommandHoldPosition 51 | Buttonpos=2,0 52 | 53 | [CmdPatrol] 54 | Art=CommandPatrol 55 | Buttonpos=0,1 56 | 57 | [CmdPurchase] 58 | Art=CommandPurchase 59 | Buttonpos=0,0 60 | 61 | [CmdRally] 62 | Art=CommandRally 63 | Buttonpos=3,1 64 | PlacementModel=UI\Feedback\RallyPoint\RallyPoint.mdl 65 | 66 | [CmdSelectSkill] 67 | Art=CommandNewSkill 68 | Buttonpos=3,1 69 | 70 | [CmdStop] 71 | Art=CommandStop 72 | Buttonpos=1,0 73 | 74 | //[CmdUnivAgi] 75 | //Art= 76 | //Buttonpos=2,0 77 | 78 | //[CmdUnivInt] 79 | //Art= 80 | //Buttonpos=1,0 81 | 82 | //[CmdUnivStr] 83 | //Art= 84 | //Buttonpos=0,0 -------------------------------------------------------------------------------- /ceres-data/data/units_en/campaignupgradestrings.txt: -------------------------------------------------------------------------------- 1 | [Rnsw] 2 | Name=Naga Siren Adept Training,Naga Siren Master Training 3 | Tip=Research |cffffcc00N|raga Siren Adept Training,Research |cffffcc00N|raga Siren Master Training 4 | Ubertip="Increases Naga Sirens' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Frost Armor.","Increases Naga Sirens' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Cyclone." 5 | Hotkey=N,N 6 | 7 | [Rnen] 8 | Name=Ensnare 9 | Tip=Research E|cffffcc00n|rsnare 10 | Ubertip="Enables Naga Myrmidons to use the Ensnare ability. Ensnare causes a target enemy unit to be bound to the ground so that it cannot move. Air units that are ensnared can be attacked as though they were land units." 11 | Hotkey=N 12 | 13 | [Rnsi] 14 | Name=Abolish Magic 15 | Tip=Research A|cffffcc00b|rolish Magic 16 | Ubertip="Gives the Couatl the ability to dispel positive buffs from enemy units, and negative buffs from friendly units. |nDamages summoned units." 17 | Hotkey=B 18 | 19 | [Rnam] 20 | Name=Coral Scales,Chitinous Scales,Razorspine Scales 21 | Tip=Upgrade to Coral S|cffffcc00c|rales,Upgrade to Chitinous S|cffffcc00c|rales,Upgrade to Razorspine S|cffffcc00c|rales 22 | Ubertip="Increases the armor of Naga attack units.","Further increases the armor of Naga attack units.","Further increases the the armor of Naga attack units." 23 | Hotkey=C,C,C 24 | 25 | [Rnat] 26 | Name=Coral Blades,Chitinous Blades,Razorspine Blades 27 | Tip=Upgrade to Coral |cffffcc00B|rlades,Upgrade to Chitinous |cffffcc00B|rlades,Upgrade to Razorspine |cffffcc00B|rlades 28 | Ubertip="Increases the attack damage of Naga attack units.","Further increases the attack damage of Naga attack units.","Further increases the attack damage of Naga attack units." 29 | Hotkey=B,B,B 30 | 31 | [Rnsb] 32 | Name=Submerge 33 | Tip=Research S|cffffcc00u|rbmerge 34 | Ubertip="Gives Naga Myrmidons and Snap Dragons the ability to submerge under water, hiding them from view." 35 | Hotkey=U -------------------------------------------------------------------------------- /ceres-core/src/evloop.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use std::sync::mpsc::*; 4 | 5 | use anyhow::Context as _; 6 | use rlua::Lua; 7 | use rlua::prelude::{LuaContext, LuaError}; 8 | 9 | use crate::handle_lua_result; 10 | 11 | pub enum Message { 12 | ChildTerminated, 13 | LuaRun(Box Result<(), LuaError>>), 14 | } 15 | 16 | struct Context { 17 | rx: Option>, 18 | tx: Option>, 19 | } 20 | 21 | impl Default for Context { 22 | fn default() -> Context { 23 | let (tx, rx) = channel(); 24 | Context { 25 | tx: Some(tx), 26 | rx: Some(rx), 27 | } 28 | } 29 | } 30 | 31 | thread_local! { 32 | static CONTEXT: RefCell = RefCell::new(Context::default()) 33 | } 34 | 35 | pub fn get_event_loop_tx() -> Sender { 36 | CONTEXT.with(|ctx| { 37 | let ctx = ctx.borrow(); 38 | ctx.tx.as_ref().unwrap().clone() 39 | }) 40 | } 41 | 42 | pub fn wait_on_evloop(lua: Rc) { 43 | CONTEXT.with(|ctx| { 44 | let mut borrowed_ctx = ctx.borrow_mut(); 45 | let rx = borrowed_ctx 46 | .rx 47 | .take() 48 | .expect("evloop recv must be available"); 49 | // no more tx for you! 50 | let tx = borrowed_ctx.tx.take(); 51 | drop(tx); 52 | drop(borrowed_ctx); 53 | 54 | while let Ok(message) = rx.recv() { 55 | match message { 56 | Message::ChildTerminated => break, 57 | Message::LuaRun(callback) => { 58 | let should_continue = lua.context(|ctx| { 59 | let result = callback(ctx); 60 | 61 | if result.is_err() { 62 | println!("[ERROR] An error occured inside the event loop. The event loop will terminate."); 63 | handle_lua_result(result.context("evloop callback failed")); 64 | return false; 65 | } 66 | 67 | true 68 | }); 69 | 70 | if !should_continue { 71 | break; 72 | } 73 | } 74 | } 75 | } 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /ceres-core/src/lua/mod.rs: -------------------------------------------------------------------------------- 1 | use rlua::prelude::*; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::CeresRunMode; 5 | 6 | pub mod util; 7 | pub mod compiler; 8 | pub mod macros; 9 | pub mod fs; 10 | pub mod mpq; 11 | pub mod launcher; 12 | pub mod object; 13 | 14 | #[derive(Serialize, Deserialize)] 15 | struct ProjectLayout { 16 | #[serde(rename = "mapsDirectory")] 17 | maps_directory: String, 18 | #[serde(rename = "srcDirectory")] 19 | src_directory: String, 20 | #[serde(rename = "libDirectory")] 21 | lib_directory: String, 22 | #[serde(rename = "targetDirectory")] 23 | target_directory: String, 24 | } 25 | 26 | pub fn setup_ceres_environ(ctx: LuaContext, run_mode: CeresRunMode, script_args: Vec) { 27 | const CERES_BUILDSCRIPT_LIB: &str = include_str!("../resource/buildscript_lib.lua"); 28 | 29 | let globals = ctx.globals(); 30 | 31 | let ceres_table = ctx.create_table().unwrap(); 32 | 33 | ceres_table 34 | .set("registerMacro", macros::get_register_luafn(ctx)) 35 | .unwrap(); 36 | ceres_table 37 | .set("compileScript", compiler::get_compile_script_luafn(ctx)) 38 | .unwrap(); 39 | 40 | ceres_table 41 | .set( 42 | "runMode", 43 | ctx.create_function(move |ctx, _: ()| match run_mode { 44 | CeresRunMode::RunMap => Ok(ctx.create_string("run")), 45 | CeresRunMode::Build => Ok(ctx.create_string("build")), 46 | CeresRunMode::LiveReload => Ok(ctx.create_string("reload")), 47 | }) 48 | .unwrap(), 49 | ) 50 | .unwrap(); 51 | 52 | ceres_table 53 | .set( 54 | "getScriptArgs", 55 | ctx.create_function(move |_, _: ()| Ok(script_args.clone())) 56 | .unwrap(), 57 | ) 58 | .unwrap(); 59 | 60 | ceres_table 61 | .set("runWarcraft", launcher::get_runmap_luafn(ctx)) 62 | .unwrap(); 63 | 64 | let fs_table = fs::get_fs_module(ctx); 65 | let mpq_table = mpq::get_mpq_module(ctx); 66 | let object_table = object::get_object_module(ctx); 67 | 68 | globals.set("fs", fs_table).unwrap(); 69 | globals.set("mpq", mpq_table).unwrap(); 70 | globals.set("objdata", object_table).unwrap(); 71 | globals.set("ceres", ceres_table).unwrap(); 72 | 73 | ctx.load(CERES_BUILDSCRIPT_LIB) 74 | .set_name("buildscript_lib.lua") 75 | .unwrap() 76 | .exec() 77 | .unwrap(); 78 | } 79 | -------------------------------------------------------------------------------- /ceres-parsers/src/test-cases/big.lua: -------------------------------------------------------------------------------- 1 | -- $Id: big.lua,v 1.32 2016/11/07 13:11:28 roberto Exp $ 2 | -- See Copyright Notice in file all.lua 3 | 4 | if _soft then 5 | return 'a' 6 | end 7 | 8 | print "testing large tables" 9 | 10 | local debug = require"debug" 11 | 12 | local lim = 2^18 + 1000 13 | local prog = { "local y = {0" } 14 | for i = 1, lim do prog[#prog + 1] = i end 15 | prog[#prog + 1] = "}\n" 16 | prog[#prog + 1] = "X = y\n" 17 | prog[#prog + 1] = ("assert(X[%d] == %d)"):format(lim - 1, lim - 2) 18 | prog[#prog + 1] = "return 0" 19 | prog = table.concat(prog, ";") 20 | 21 | local env = {string = string, assert = assert} 22 | local f = assert(load(prog, nil, nil, env)) 23 | 24 | f() 25 | assert(env.X[lim] == lim - 1 and env.X[lim + 1] == lim) 26 | for k in pairs(env) do env[k] = nil end 27 | 28 | -- yields during accesses larger than K (in RK) 29 | setmetatable(env, { 30 | __index = function (t, n) coroutine.yield('g'); return _G[n] end, 31 | __newindex = function (t, n, v) coroutine.yield('s'); _G[n] = v end, 32 | }) 33 | 34 | X = nil 35 | co = coroutine.wrap(f) 36 | assert(co() == 's') 37 | assert(co() == 'g') 38 | assert(co() == 'g') 39 | assert(co() == 0) 40 | 41 | assert(X[lim] == lim - 1 and X[lim + 1] == lim) 42 | 43 | -- errors in accesses larger than K (in RK) 44 | getmetatable(env).__index = function () end 45 | getmetatable(env).__newindex = function () end 46 | local e, m = pcall(f) 47 | assert(not e and m:find("global 'X'")) 48 | 49 | -- errors in metamethods 50 | getmetatable(env).__newindex = function () error("hi") end 51 | local e, m = xpcall(f, debug.traceback) 52 | assert(not e and m:find("'__newindex'")) 53 | 54 | f, X = nil 55 | 56 | coroutine.yield'b' 57 | 58 | if 2^32 == 0 then -- (small integers) { 59 | 60 | print "testing string length overflow" 61 | 62 | local repstrings = 192 -- number of strings to be concatenated 63 | local ssize = math.ceil(2.0^32 / repstrings) + 1 -- size of each string 64 | 65 | assert(repstrings * ssize > 2.0^32) -- it should be larger than maximum size 66 | 67 | local longs = string.rep("\0", ssize) -- create one long string 68 | 69 | -- create function to concatentate 'repstrings' copies of its argument 70 | local rep = assert(load( 71 | "local a = ...; return " .. string.rep("a", repstrings, ".."))) 72 | 73 | local a, b = pcall(rep, longs) -- call that function 74 | 75 | -- it should fail without creating string (result would be too large) 76 | assert(not a and string.find(b, "overflow")) 77 | 78 | end -- } 79 | 80 | print'OK' 81 | 82 | return 'a' 83 | -------------------------------------------------------------------------------- /ceres-formats/src/parser/profile.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::iter::Peekable; 3 | use std::str::from_utf8; 4 | 5 | use crate::parser::crlf::Lines; 6 | 7 | #[derive(Debug)] 8 | pub struct Entry<'src> { 9 | pub id: &'src str, 10 | pub values: HashMap<&'src str, &'src str>, 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct Entries<'src> { 15 | lines: Peekable>, 16 | } 17 | 18 | impl<'src> Entries<'src> { 19 | pub fn new(source: &'src [u8]) -> Entries<'src> { 20 | Entries { 21 | lines: Lines::new(source).peekable(), 22 | } 23 | } 24 | } 25 | 26 | fn parse_entry_start(mut input: &[u8]) -> Option<&str> { 27 | if input[0] == b'[' { 28 | input = &input[1..]; 29 | if input.is_empty() { 30 | return None; 31 | } 32 | 33 | let (end, _) = input.iter().enumerate().find(|(_, c)| **c == b']')?; 34 | 35 | return Some(from_utf8(&input[..end]).ok()?); 36 | } 37 | 38 | None 39 | } 40 | 41 | fn parse_entry_value(input: &[u8]) -> Option<(&str, &str)> { 42 | if input.starts_with(b"//") { 43 | return None; 44 | } 45 | 46 | let equals = input 47 | .iter() 48 | .enumerate() 49 | .find(|(_, c)| **c == b'=') 50 | .map(|(i, _)| i); 51 | 52 | if let Some(equals) = equals { 53 | let key = from_utf8(&input[..equals]).ok()?; 54 | let value = from_utf8(&input[equals + 1..]).ok()?; 55 | 56 | if key.is_empty() || value.is_empty() { 57 | return None; 58 | } 59 | 60 | Some((key, value)) 61 | } else { 62 | None 63 | } 64 | } 65 | 66 | impl<'src> Iterator for Entries<'src> { 67 | type Item = Entry<'src>; 68 | 69 | fn next(&mut self) -> Option { 70 | self.lines.find_map(|l| parse_entry_start(l)).map(|id| { 71 | let mut values = HashMap::default(); 72 | 73 | loop { 74 | if self.lines.peek().is_none() { 75 | break; 76 | } 77 | 78 | if self 79 | .lines 80 | .peek() 81 | .and_then(|l| parse_entry_start(l)) 82 | .is_some() 83 | { 84 | break; 85 | } 86 | 87 | if let Some((key, value)) = self.lines.next().and_then(|l| parse_entry_value(l)) { 88 | values.insert(key, value); 89 | } 90 | } 91 | 92 | Entry { id, values } 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /ceres-data/data/units/miscdata.txt: -------------------------------------------------------------------------------- 1 | [Misc] 2 | 3 | // Range around selected ground area to search for a target 4 | CloseEnoughRange=100 5 | 6 | // Radius of building blight dispel 7 | BuildingUnblightRadius=350 8 | 9 | // Radius of creep notification when a new building gets placed 10 | BuildingPlacementNotifyRadius=600 11 | 12 | // Radius of creep notification when a neutral building is in use 13 | NeutralUseNotifyRadius=900 14 | 15 | // the angle in degrees structures face 16 | BuildingAngle=270 17 | RootAngle=250 18 | 19 | // half angle in radians that a unit must meet to be facing his target 20 | AttackHalfAngle=0.5 21 | FoggedAttackRevealRadius=200.0 22 | DyingRevealRadius=500.0 23 | 24 | // death and decay impact gameplay, so duration is specified 25 | BoneDecayTime=88 26 | StructureDecayTime=30 27 | DecayTime=2 28 | DissipateTime=3 29 | CancelTime=6 30 | BulletDeathTime=5 31 | EffectDeathTime=5 32 | FogFlashTime=3 33 | CreepCampPathingCellDistance=26 34 | 35 | // follow ranges 36 | FollowRange=300 37 | StructureFollowRange=100 38 | FollowItemRange=1000 39 | 40 | // distance target can move between starting cast and spell effect 41 | SpellCastRangeBuffer=300 42 | 43 | // largest possible collision radius for any widget 44 | MaxCollisionRadius=200 45 | 46 | // rally point vertical offset when set on non-units 47 | RallyZOffset=200 48 | 49 | // duration of art animations that get scaled 50 | ScaledAnimTime=60 51 | 52 | // max random reaction delay (seconds) 53 | ReactionDelay=0.25 54 | 55 | // missile chance to miss if target is moving or on high ground 56 | ChanceToMiss=0.25 57 | 58 | // game-seconds per game-day 59 | DayLength=480 60 | Dawn=6 61 | Dusk=18 62 | 63 | // earth has a 24 hour day, how many does Azeroth have 64 | DayHours=24 65 | 66 | // maximum amount of gold a gold mine can have 67 | GoldMineMaxGold=1000000 68 | 69 | // this is the amount where a gold mine is considered low. 70 | LowGoldAmount=1500 71 | 72 | // the length that a mine is considered owned after the last peon leaves it. 73 | GoldMineOwnDuration=2.0 74 | 75 | // this is the speed units change visibility (smaller = "cloak" slower) 76 | InvisSpeed=0.4 77 | 78 | // All selection circles will have this added to their z coord 79 | SelectionCircleBaseZ=16 80 | 81 | // item shadow data 82 | ItemShadowFile=Shadow 83 | ItemShadowSize=120,120 84 | ItemShadowOffset=50,50 85 | 86 | // seconds between attack notifications 87 | AttackNotifyDelay=30.0 88 | AttackNotifyRange=1250 89 | 90 | // alliance resource trading increments 91 | TradingIncSmall=100 92 | TradingIncLarge=200 93 | 94 | MissDamageReduction=0.5 -------------------------------------------------------------------------------- /ceres-parsers/src/lua.rs: -------------------------------------------------------------------------------- 1 | use pest_derive::*; 2 | 3 | pub use pest::Parser; 4 | 5 | #[derive(Parser)] 6 | #[grammar = "lua.pest"] 7 | pub struct LuaParser; 8 | 9 | #[cfg(test)] 10 | mod test { 11 | use super::LuaParser; 12 | use super::Rule; 13 | use pest::Parser; 14 | 15 | #[test] 16 | fn lua_test_suite() { 17 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/all.lua")).unwrap(); 18 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/api.lua")).unwrap(); 19 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/attrib.lua")).unwrap(); 20 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/big.lua")).unwrap(); 21 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/bitwise.lua")).unwrap(); 22 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/calls.lua")).unwrap(); 23 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/closure.lua")).unwrap(); 24 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/code.lua")).unwrap(); 25 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/constructs.lua")).unwrap(); 26 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/coroutine.lua")).unwrap(); 27 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/db.lua")).unwrap(); 28 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/errors.lua")).unwrap(); 29 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/events.lua")).unwrap(); 30 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/files.lua")).unwrap(); 31 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/gc.lua")).unwrap(); 32 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/goto.lua")).unwrap(); 33 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/literals.lua")).unwrap(); 34 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/locals.lua")).unwrap(); 35 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/main.lua")).unwrap(); 36 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/math.lua")).unwrap(); 37 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/nextvar.lua")).unwrap(); 38 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/pm.lua")).unwrap(); 39 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/sort.lua")).unwrap(); 40 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/strings.lua")).unwrap(); 41 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/tpack.lua")).unwrap(); 42 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/utf8.lua")).unwrap(); 43 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/vararg.lua")).unwrap(); 44 | LuaParser::parse(Rule::Chunk, include_str!("test-cases/verybig.lua")).unwrap(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Based on the "trust" template v0.1.2 2 | # https://github.com/japaric/trust/tree/v0.1.2 3 | 4 | dist: trusty 5 | language: rust 6 | services: docker 7 | sudo: required 8 | 9 | # TODO Rust builds on stable by default, this can be 10 | # overridden on a case by case basis down below. 11 | 12 | env: 13 | global: 14 | # TODO Update this to match the name of your project. 15 | - CRATE_NAME=ceres-w3 16 | 17 | matrix: 18 | # TODO These are all the build jobs. Adjust as necessary. Comment out what you 19 | # don't need 20 | include: 21 | # Linux 22 | - env: TARGET=x86_64-unknown-linux-gnu 23 | rust: nightly 24 | 25 | # OSX 26 | - env: TARGET=x86_64-apple-darwin 27 | rust: nightly 28 | os: osx 29 | 30 | # Windows 31 | - env: TARGET=x86_64-pc-windows-gnu 32 | rust: nightly 33 | 34 | before_install: 35 | - set -e 36 | - rustup self update 37 | 38 | install: 39 | - sh ci/install.sh 40 | - source ~/.cargo/env || true 41 | 42 | script: 43 | - bash ci/script.sh 44 | 45 | after_script: set +e 46 | 47 | before_deploy: 48 | - sh ci/before_deploy.sh 49 | 50 | deploy: 51 | # TODO update `api_key.secure` 52 | # - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new 53 | # - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789 54 | # - Paste the output down here 55 | api_key: 56 | secure: "COSmw83UXOgFo1Sk2ZyhKd8vyDgghf5citVktTkAvjdxp3CC7MZzVQUYAKM72SUWuEe9ojQoBzMmeTEIjq2aCLUXF1jCclZXhgTY8sfSOB7PSDoYKWks7Oy5D7Hf6TiTkenNnLcuyGamk8WGufRCRxq2WZo6FnmTmWS1FFVI7xk2oZjVg7rAg0unXSgXxW+QLQj4mf+lNJVNHs+Ajxm5cZoc5cqD86SyFN6sXC86gUmM07ciloFQWw+EgH5GJ2Cnl0MOL4OR+CPzBO0TCQpHjynclyhXzj5vr+jI4PahLQn1cQwbaLem/drdZOD2Xh2TR2cpfJA3b2s/qHJLeZZfonzysK7X4bYn46N83FNceagTyzRHttJbR6QYm9APCKTsjBrrGkbHy6mGX8bbPrObOs8oXDwwEdiTTWg+Eolf+tEOPWWlebeUgBohrKwIRxb456HuEIyd+ismk2GawvQf2HKmlQbd+CQpl229MxnQ2hvfvUVHb5UXHNc4TKRl6yDqrG4dHuY3cx4f/Mva7VqsFIZObg2lKsqKgC6Rsd2Sq5DgDvFrSBPstXnlrG2k5jJ0qUiRaSLo1Zj5m1lN6oLUBGzDLuDfjfUlW09M+n7kdQp05MYYaXecnew+s5LT0BiTSsoYD5QWiQUNjX8EwLAo3xOnWVi9JNlBty9AdS35IIY=" 57 | file_glob: true 58 | file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.* 59 | on: 60 | # TODO Here you can pick which targets will generate binary releases 61 | # In this example, there are some targets that are tested using the stable 62 | # and nightly channels. This condition makes sure there is only one release 63 | # for such targets and that's generated using the stable channel 64 | condition: $TRAVIS_RUST_VERSION = nightly 65 | tags: true 66 | provider: releases 67 | skip_cleanup: true 68 | draft: true 69 | 70 | cache: cargo 71 | before_cache: 72 | # Travis can't cache files that are not readable by "others" 73 | - chmod -R a+r $HOME/.cargo 74 | 75 | branches: 76 | only: 77 | # release tags 78 | - /^v\d+\.\d+\.\d+.*$/ 79 | - master 80 | - dev 81 | 82 | notifications: 83 | email: 84 | on_success: never -------------------------------------------------------------------------------- /ceres-data/data/units/undeadupgradefunc.txt: -------------------------------------------------------------------------------- 1 | [Rusp] 2 | Art=ReplaceableTextures\CommandButtons\BTNDestroyer.blp 3 | ButtonPos=2,2 4 | Requires=unp2,utom 5 | 6 | [Rume] 7 | Art=ReplaceableTextures\CommandButtons\BTNUnholyStrength.blp,ReplaceableTextures\CommandButtons\BTNImprovedUnholyStrength.blp,ReplaceableTextures\CommandButtons\BTNAdvancedUnholyStrength.blp 8 | Buttonpos=0,0 9 | Requirescount=3 10 | Requires= 11 | Requires1=unp1 12 | Requires2=unp2 13 | 14 | [Rura] 15 | Art=ReplaceableTextures\CommandButtons\BTNCreatureAttack.blp,ReplaceableTextures\CommandButtons\BTNImprovedCreatureAttack.blp,ReplaceableTextures\CommandButtons\BTNAdvancedCreatureAttack.blp 16 | Buttonpos=1,0 17 | Requirescount=3 18 | Requires= 19 | Requires1=unp1 20 | Requires2=unp2 21 | 22 | [Ruar] 23 | Art=ReplaceableTextures\CommandButtons\BTNUnholyArmor.blp,ReplaceableTextures\CommandButtons\BTNImprovedUnholyArmor.blp,ReplaceableTextures\CommandButtons\BTNAdvancedUnholyArmor.blp 24 | Buttonpos=0,1 25 | Requirescount=3 26 | Requires= 27 | Requires1=unp1 28 | Requires2=unp2 29 | 30 | [Rucr] 31 | Art=ReplaceableTextures\CommandButtons\BTNCreatureCarapace.blp,ReplaceableTextures\CommandButtons\BTNImprovedCreatureCarapace.blp,ReplaceableTextures\CommandButtons\BTNAdvancedCreatureCarapace.blp 32 | Buttonpos=1,1 33 | Requirescount=3 34 | Requires= 35 | Requires1=unp1 36 | Requires2=unp2 37 | 38 | [Ruac] 39 | Art=ReplaceableTextures\CommandButtons\BTNCannibalize.blp 40 | Buttonpos=0,2 41 | 42 | [Rugf] 43 | Art=ReplaceableTextures\CommandButtons\BTNGhoulFrenzy.blp 44 | Buttonpos=0,1 45 | Requires=ugrv,unp2 46 | 47 | [Ruwb] 48 | Art=ReplaceableTextures\CommandButtons\BTNWeb.blp 49 | Buttonpos=1,2 50 | Requires=ugrv,unp1 51 | 52 | [Rusf] 53 | Art=ReplaceableTextures\CommandButtons\BTNStoneForm.blp 54 | Buttonpos=2,1 55 | Requires=ugrv,unp2 56 | 57 | [Rune] 58 | Art=ReplaceableTextures\CommandButtons\BTNNecromancerAdept.blp,ReplaceableTextures\CommandButtons\BTNNecromancerMaster.blp 59 | Buttonpos=0,2 60 | Requirescount=2 61 | Requires= 62 | Requires1=unp2 63 | 64 | [Ruba] 65 | Art=ReplaceableTextures\CommandButtons\BTNBansheeAdept.blp,ReplaceableTextures\CommandButtons\BTNBansheeMaster.blp 66 | Buttonpos=1,2 67 | Requirescount=2 68 | Requires= 69 | Requires1=unp2 70 | 71 | [Rufb] 72 | Art=ReplaceableTextures\CommandButtons\BTNFreezingBreath.blp 73 | Buttonpos=0,2 74 | 75 | [Rusl] 76 | Requires=unp2 77 | Art=ReplaceableTextures\CommandButtons\BTNSkeletalLongevity.blp 78 | Buttonpos=2,1 79 | 80 | [Rupc] 81 | Art=ReplaceableTextures\CommandButtons\BTNPlagueCloud.blp 82 | Buttonpos=1,2 83 | Requires=unp2 84 | 85 | [Rusm] 86 | Requires=Rune 87 | Art=ReplaceableTextures\CommandButtons\BTNSkeletonMage.blp 88 | Buttonpos=0,1 89 | 90 | [Rubu] 91 | Art=ReplaceableTextures\CommandButtons\BTNCryptFiendBurrow.blp 92 | Buttonpos=1,1 93 | Requires=unp1,ugrv 94 | 95 | [Ruex] 96 | Art=ReplaceableTextures\CommandButtons\BTNExhumeCorpses.blp 97 | Buttonpos=0,2 98 | Requires=unp1 99 | 100 | [Rupm] 101 | Art=ReplaceableTextures\CommandButtons\BTNPackBeast.blp 102 | Buttonpos=3,0 103 | Requires=utom -------------------------------------------------------------------------------- /ceres-core/src/lua/launcher.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | use std::process::Command; 5 | use std::thread; 6 | 7 | use path_absolutize::Absolutize; 8 | use rlua::prelude::*; 9 | 10 | use crate::error::*; 11 | use crate::evloop::{get_event_loop_tx, Message}; 12 | use crate::lua::util::wrap_result; 13 | 14 | pub struct LaunchConfig { 15 | launch_command: String, 16 | path_prefix: Option, 17 | extra_args: Vec, 18 | } 19 | 20 | fn run_map(map_path: &str, config: LaunchConfig) -> Result<(), anyhow::Error> { 21 | let map_path: PathBuf = map_path.into(); 22 | let map_path = map_path.absolutize()?; 23 | let map_path = map_path 24 | .to_str() 25 | .ok_message("path to map must be valid UTF-8")?; 26 | let map_path = config.path_prefix.map_or_else( 27 | || Cow::Borrowed(map_path), 28 | |prefix| Cow::Owned(prefix + map_path), 29 | ); 30 | 31 | let mut cmd = Command::new(config.launch_command); 32 | 33 | let log_file = fs::File::create("war3.log").context("could not create wc3 log file")?; 34 | cmd.arg("-loadfile") 35 | .arg(map_path.as_ref()) 36 | .arg("-launch") 37 | .stdout( 38 | log_file 39 | .try_clone() 40 | .context("could not clone log file handle to stdout")?, 41 | ) 42 | .stderr( 43 | log_file 44 | .try_clone() 45 | .context("could not clone log file handle to stderr")?, 46 | ); 47 | 48 | for arg in config.extra_args { 49 | cmd.arg(arg); 50 | } 51 | 52 | let tx = get_event_loop_tx(); 53 | 54 | thread::spawn(move || { 55 | println!("Starting Warcraft III with command line:\n{:?}", cmd); 56 | let child = cmd.spawn().context("could not launch Warcraft III"); 57 | 58 | match child { 59 | Ok(mut child) => { 60 | if let Err(error) = child.wait() { 61 | println!("Process terminated errorfully: {}", error) 62 | } 63 | } 64 | Err(error) => println!("An error occured while starting WC3: {}", error), 65 | } 66 | 67 | tx.send(Message::ChildTerminated) 68 | }); 69 | 70 | Ok(()) 71 | } 72 | 73 | fn lua_run_map(path: LuaString, config: LuaTable) -> Result { 74 | let map_path = path.to_str()?; 75 | 76 | let launch_command: String = config 77 | .get("command") 78 | .context("could not read 'command' field")?; 79 | let path_prefix: Option = config 80 | .get("prefix") 81 | .context("could not read 'prefix' field")?; 82 | let args: Option> = config.get("args").context("could not read 'args' field")?; 83 | 84 | let config = LaunchConfig { 85 | launch_command, 86 | path_prefix, 87 | extra_args: args.unwrap_or_default(), 88 | }; 89 | 90 | run_map(map_path, config)?; 91 | 92 | Ok(true) 93 | } 94 | 95 | pub fn get_runmap_luafn(ctx: LuaContext) -> LuaFunction { 96 | ctx.create_function(|ctx, (path, config): (LuaString, LuaTable)| { 97 | let result = lua_run_map(path, config); 98 | 99 | Ok(wrap_result(ctx, result)) 100 | }) 101 | .unwrap() 102 | } 103 | -------------------------------------------------------------------------------- /ceres-core/src/lua/macros.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::HashMap; 3 | use std::ops::Deref; 4 | use std::rc::Rc; 5 | use std::thread_local; 6 | 7 | use rlua::prelude::*; 8 | 9 | use compiler::MacroProvider; 10 | 11 | use crate::compiler; 12 | use crate::error::*; 13 | use crate::lua::util::evaluate_macro_args; 14 | 15 | pub struct LuaMacroProvider { 16 | registered_macros: RefCell>, 17 | } 18 | 19 | impl LuaMacroProvider { 20 | fn register_macro<'lua>(&self, ctx: LuaContext<'lua>, id: &str, func: LuaFunction<'lua>) { 21 | let registry_key = ctx.create_registry_value(func).unwrap(); 22 | 23 | self.registered_macros 24 | .borrow_mut() 25 | .insert(id.into(), registry_key); 26 | } 27 | } 28 | 29 | impl MacroProvider for LuaMacroProvider { 30 | fn is_macro_id(&self, id: &str) -> bool { 31 | self.registered_macros.borrow().contains_key(id) 32 | } 33 | 34 | fn handle_macro( 35 | &self, 36 | ctx: LuaContext, 37 | id: &str, 38 | compilation_data: &mut compiler::CompilationData, 39 | macro_invocation: compiler::MacroInvocation, 40 | ) -> Result<(), MacroInvocationError> { 41 | let args = evaluate_macro_args(ctx, macro_invocation.args).unwrap(); 42 | let callback: LuaFunction = { 43 | let registered_macros = self.registered_macros.borrow(); 44 | 45 | let registry_key = registered_macros.get(id).unwrap(); 46 | ctx.registry_value(registry_key).unwrap() 47 | }; 48 | 49 | let value = callback.call::<_, LuaValue>(args).unwrap(); 50 | 51 | if let LuaValue::String(value) = value { 52 | compilation_data.src += value.to_str().unwrap(); 53 | } 54 | 55 | Ok(()) 56 | } 57 | } 58 | 59 | impl MacroProvider for Rc { 60 | fn is_macro_id(&self, id: &str) -> bool { 61 | (self.deref()).is_macro_id(id) 62 | } 63 | 64 | fn handle_macro( 65 | &self, 66 | ctx: LuaContext, 67 | id: &str, 68 | compilation_data: &mut compiler::CompilationData, 69 | macro_invocation: compiler::MacroInvocation, 70 | ) -> Result<(), MacroInvocationError> { 71 | (self.deref()).handle_macro(ctx, id, compilation_data, macro_invocation) 72 | } 73 | } 74 | 75 | thread_local! { 76 | static LUA_MACRO_PROVIDER: RefCell>> = RefCell::new(None); 77 | } 78 | 79 | pub fn get_threadlocal_macro_provider() -> Rc { 80 | LUA_MACRO_PROVIDER.with(|macro_provider| { 81 | let mut macro_provider = macro_provider.borrow_mut(); 82 | 83 | if macro_provider.is_none() { 84 | let macro_provider_new = LuaMacroProvider { 85 | registered_macros: Default::default(), 86 | }; 87 | 88 | macro_provider.replace(Rc::new(macro_provider_new)); 89 | } 90 | 91 | Rc::clone(macro_provider.as_ref().unwrap()) 92 | }) 93 | } 94 | 95 | pub fn get_register_luafn(ctx: LuaContext) -> LuaFunction { 96 | ctx.create_function::<_, (), _>(|ctx, (id, callback): (String, LuaFunction)| { 97 | let lua_macro_provider = get_threadlocal_macro_provider(); 98 | 99 | lua_macro_provider.register_macro(ctx, &id, callback); 100 | 101 | Ok(()) 102 | }) 103 | .unwrap() 104 | } 105 | -------------------------------------------------------------------------------- /ceres-binaries/src/bin/ceres.rs: -------------------------------------------------------------------------------- 1 | use clap::clap_app; 2 | 3 | fn main() { 4 | dotenv::dotenv().ok(); 5 | 6 | let matches = clap_app!(Ceres => 7 | (version: "0.3.6") 8 | (author: "mori ") 9 | (about: "Ceres is a build tool, script compiler and map preprocessor for WC3 Lua maps.") 10 | (@subcommand build => 11 | (about: "Uses the build.lua file in the current directory to build a map.") 12 | (setting: clap::AppSettings::TrailingVarArg) 13 | (@arg dir: --dir -d +takes_value "Sets the project directory.") 14 | (@arg BUILD_ARGS: ... "Arguments to pass to the build script.") 15 | ) 16 | (@subcommand run => 17 | (about: "Uses the build.lua file in the current directory to build and run a map.") 18 | (setting: clap::AppSettings::TrailingVarArg) 19 | (@arg dir: --dir -d +takes_value "Sets the project directory.") 20 | (@arg BUILD_ARGS: ... "Arguments to pass to the build script.") 21 | ) 22 | (@subcommand exec => 23 | (about: "Executes the specified lua file using Ceres runtime") 24 | (setting: clap::AppSettings::TrailingVarArg) 25 | (@arg script: +required +takes_value) 26 | (@arg BUILD_ARGS: ... "Arguments to pass to the build script.") 27 | ) 28 | ) 29 | .get_matches(); 30 | 31 | std::process::exit(match run(matches) { 32 | Err(error) => { 33 | println!("[ERROR] An error has occured. Error chain:"); 34 | println!("{}", error); 35 | 36 | let mut cause = error.source(); 37 | while let Some(inner_cause) = cause { 38 | println!("{}", &inner_cause); 39 | cause = inner_cause.source(); 40 | } 41 | 42 | 1 43 | } 44 | Ok(_) => 0, 45 | }); 46 | } 47 | 48 | fn run_build(arg: &clap::ArgMatches, mode: ceres_core::CeresRunMode) -> Result<(), anyhow::Error> { 49 | let project_dir = arg 50 | .value_of("dir") 51 | .map(std::path::PathBuf::from) 52 | .unwrap_or_else(|| std::env::current_dir().unwrap()); 53 | 54 | let script_args = arg 55 | .values_of("BUILD_ARGS") 56 | .map(std::iter::Iterator::collect) 57 | .unwrap_or_else(Vec::new); 58 | 59 | ceres_core::run_build_script(mode, project_dir, script_args)?; 60 | 61 | Ok(()) 62 | } 63 | 64 | fn exec(arg: &clap::ArgMatches) -> Result<(), anyhow::Error> { 65 | let script = arg 66 | .value_of("script") 67 | .map(std::path::PathBuf::from) 68 | .unwrap(); 69 | 70 | let script = std::fs::read_to_string(script)?; 71 | 72 | let script_args = arg 73 | .values_of("BUILD_ARGS") 74 | .map(std::iter::Iterator::collect) 75 | .unwrap_or_else(Vec::new); 76 | 77 | ceres_core::execute_script(ceres_core::CeresRunMode::Build, script_args, |ctx| { 78 | ctx.load(&script).exec()?; 79 | 80 | Ok(()) 81 | })?; 82 | 83 | Ok(()) 84 | } 85 | 86 | fn run(matches: clap::ArgMatches) -> Result<(), anyhow::Error> { 87 | if let Some(arg) = matches.subcommand_matches("build") { 88 | run_build(arg, ceres_core::CeresRunMode::Build)?; 89 | } else if let Some(arg) = matches.subcommand_matches("run") { 90 | run_build(arg, ceres_core::CeresRunMode::RunMap)?; 91 | } else if let Some(arg) = matches.subcommand_matches("exec") { 92 | exec(arg)?; 93 | } 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /ceres-data/data/units/orcupgradefunc.txt: -------------------------------------------------------------------------------- 1 | [Roch] 2 | Art=ReplaceableTextures\CommandButtons\BTNOrcMeleeUpOne.blp 3 | 4 | [Rome] 5 | Art=ReplaceableTextures\CommandButtons\BTNOrcMeleeUpOne.blp,ReplaceableTextures\CommandButtons\BTNOrcMeleeUpTwo.blp,ReplaceableTextures\CommandButtons\BTNOrcMeleeUpThree.blp 6 | Buttonpos=0,0 7 | Requirescount=3 8 | Requires= 9 | Requires1=ostr 10 | Requires2=ofrt 11 | 12 | [Rora] 13 | Art=ReplaceableTextures\CommandButtons\BTNSteelRanged.blp,ReplaceableTextures\CommandButtons\BTNThoriumRanged.blp,ReplaceableTextures\CommandButtons\BTNArcaniteRanged.blp 14 | Buttonpos=1,0 15 | Requirescount=3 16 | Requires= 17 | Requires1=ostr 18 | Requires2=ofrt 19 | 20 | [Roar] 21 | Art=ReplaceableTextures\CommandButtons\BTNSteelArmor.blp,ReplaceableTextures\CommandButtons\BTNThoriumArmor.blp,ReplaceableTextures\CommandButtons\BTNArcaniteArmor.blp 22 | Buttonpos=0,1 23 | Requirescount=3 24 | Requires= 25 | Requires1=ostr 26 | Requires2=ofrt 27 | 28 | [Rwdm] 29 | Art=ReplaceableTextures\CommandButtons\BTNDrum.blp 30 | Buttonpos=2,2 31 | Requires=ofrt,ofor 32 | 33 | // Pillage (formerly salvage) 34 | [Ropg] 35 | Art=ReplaceableTextures\CommandButtons\BTNPillage.blp 36 | Buttonpos=2,0 37 | 38 | [Robs] 39 | Art=ReplaceableTextures\CommandButtons\BTNBerserk.blp 40 | Buttonpos=0,2 41 | Requires=ostr 42 | 43 | [Rows] 44 | Art=ReplaceableTextures\CommandButtons\BTNSmash.blp 45 | Buttonpos=1,2 46 | Requires=ofor,ofrt 47 | 48 | [Roen] 49 | Art=ReplaceableTextures\CommandButtons\BTNEnsnare.blp 50 | Buttonpos=0,2 51 | 52 | 53 | 54 | [Rovs] 55 | Art=ReplaceableTextures\CommandButtons\BTNEnvenomedSpear.blp 56 | Buttonpos=1,2 57 | Requires=ofrt 58 | 59 | [Rowd] 60 | Art=ReplaceableTextures\CommandButtons\BTNWitchDoctorAdept.blp,ReplaceableTextures\CommandButtons\BTNWitchDoctorMaster.blp 61 | Buttonpos=1,2 62 | Requirescount=2 63 | Requires= 64 | Requires1=ofrt 65 | 66 | [Rost] 67 | Art=ReplaceableTextures\CommandButtons\BTNShamanAdept.blp,ReplaceableTextures\CommandButtons\BTNShamanMaster.blp 68 | Buttonpos=0,2 69 | Requirescount=2 70 | Requires= 71 | Requires1=ofrt 72 | 73 | [Rosp] 74 | Art=ReplaceableTextures\CommandButtons\BTNSpikedBarricades.blp,ReplaceableTextures\CommandButtons\BTNImprovedSpikedBarricades.blp,ReplaceableTextures\CommandButtons\BTNAdvancedSpikedBarricades.blp 75 | Buttonpos=2,0 76 | Requirescount=3 77 | Requires= 78 | Requires1=ostr 79 | Requires2=ofrt 80 | 81 | [Rotr] 82 | Art=ReplaceableTextures\CommandButtons\BTNRegenerate.blp 83 | Buttonpos=1,2 84 | Requires=ostr,ofor 85 | 86 | [Rolf] 87 | Art=ReplaceableTextures\CommandButtons\BTNLiquidFire.blp 88 | Buttonpos=2,1 89 | Requires=ofrt,ovln 90 | 91 | [Ropm] 92 | Art=ReplaceableTextures\CommandButtons\BTNPackBeast.blp 93 | Buttonpos=3,0 94 | Requires=ovln 95 | 96 | [Rowt] 97 | Art=ReplaceableTextures\CommandButtons\BTNSpiritWalkerAdeptTraining.blp,ReplaceableTextures\CommandButtons\BTNSpiritWalkerMasterTraining.blp 98 | Buttonpos=0,2 99 | Requirescount=2 100 | Requires= 101 | Requires1=ofrt 102 | 103 | [Robk] 104 | Art=ReplaceableTextures\CommandButtons\BTNHeadHunterBerserker.blp 105 | Buttonpos=1,1 106 | Requires=ofrt,ofor 107 | 108 | [Rorb] 109 | Requires=ostr 110 | Art=ReplaceableTextures\CommandButtons\BTNReinforcedBurrows.blp 111 | Buttonpos=2,1 112 | 113 | [Robf] 114 | Requires=ofrt 115 | Art=ReplaceableTextures\CommandButtons\BTNFireRocks.blp 116 | Buttonpos=2,1 117 | 118 | 119 | -------------------------------------------------------------------------------- /ceres-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | extern crate ceres_data as w3data; 4 | extern crate ceres_mpq as mpq; 5 | 6 | use std::error::Error; 7 | use std::fs; 8 | use std::path::PathBuf; 9 | use std::rc::Rc; 10 | 11 | use rlua::prelude::*; 12 | 13 | use crate::error::ContextError; 14 | use crate::evloop::wait_on_evloop; 15 | 16 | pub(crate) mod lua; 17 | pub(crate) mod error; 18 | pub(crate) mod compiler; 19 | pub(crate) mod evloop; 20 | 21 | #[derive(Copy, Clone)] 22 | pub enum CeresRunMode { 23 | Build, 24 | RunMap, 25 | LiveReload, 26 | } 27 | 28 | pub fn lua_error_root_cause(error: &LuaError) -> anyhow::Error { 29 | match error { 30 | LuaError::CallbackError { traceback, cause } => { 31 | anyhow::anyhow!("{}\n{}", lua_error_root_cause(cause), traceback) 32 | } 33 | LuaError::ExternalError(external) => { 34 | if let Some(error) = Error::downcast_ref::(external.as_ref()) { 35 | lua_error_root_cause(error) 36 | } else { 37 | anyhow::anyhow!("{}", external) 38 | } 39 | } 40 | other => anyhow::anyhow!("{}", other), 41 | } 42 | } 43 | 44 | pub fn handle_lua_result(result: anyhow::Result<()>) { 45 | if let Err(err) = result { 46 | match err.downcast::() { 47 | Ok(err) => { 48 | println!("{}", lua_error_root_cause(&err)); 49 | } 50 | Err(err) => println!("{}", err), 51 | } 52 | } 53 | } 54 | 55 | pub fn execute_script( 56 | run_mode: CeresRunMode, 57 | script_args: Vec<&str>, 58 | action: F, 59 | ) -> Result<(), anyhow::Error> 60 | where 61 | F: FnOnce(LuaContext) -> Result<(), anyhow::Error>, 62 | { 63 | const DEFAULT_BUILD_SCRIPT: &str = include_str!("resource/buildscript_default.lua"); 64 | 65 | let lua = Rc::new(Lua::new()); 66 | 67 | let result: Result<(), anyhow::Error> = lua.context(|ctx| { 68 | lua::setup_ceres_environ( 69 | ctx, 70 | run_mode, 71 | script_args.into_iter().map(|s| s.into()).collect(), 72 | ); 73 | 74 | action(ctx)?; 75 | 76 | Ok(()) 77 | }); 78 | 79 | if result.is_err() { 80 | handle_lua_result(result); 81 | std::process::exit(1); 82 | } 83 | 84 | wait_on_evloop(Rc::clone(&lua)); 85 | 86 | Ok(()) 87 | } 88 | 89 | pub fn run_build_script( 90 | run_mode: CeresRunMode, 91 | project_dir: PathBuf, 92 | script_args: Vec<&str>, 93 | ) -> Result<(), anyhow::Error> { 94 | const DEFAULT_BUILD_SCRIPT: &str = include_str!("resource/buildscript_default.lua"); 95 | 96 | let build_script_path = project_dir.join("build.lua"); 97 | 98 | let build_script = if build_script_path.is_file() { 99 | Some( 100 | fs::read_to_string(&build_script_path) 101 | .map_err(|cause| ContextError::new("Could not read custom build script", cause))?, 102 | ) 103 | } else { 104 | None 105 | }; 106 | 107 | execute_script(run_mode, script_args, |ctx| { 108 | if let Some(build_script) = build_script { 109 | ctx.load(&build_script) 110 | .set_name("custom build script") 111 | .unwrap() 112 | .exec()?; 113 | } 114 | 115 | ctx.load(DEFAULT_BUILD_SCRIPT) 116 | .set_name("buildscript_default.lua") 117 | .unwrap() 118 | .exec()?; 119 | 120 | Ok(()) 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /ceres-data/data/units/nightelfupgradefunc.txt: -------------------------------------------------------------------------------- 1 | [Resm] 2 | Art=ReplaceableTextures\CommandButtons\BTNStrengthOfTheMoon.blp,ReplaceableTextures\CommandButtons\BTNImprovedStrengthOfTheMoon.blp,ReplaceableTextures\CommandButtons\BTNAdvancedStrengthOfTheMoon.blp 3 | Buttonpos=0,0 4 | Requirescount=3 5 | Requires= 6 | Requires1=etoa 7 | Requires2=etoe 8 | 9 | [Resw] 10 | Art=ReplaceableTextures\CommandButtons\BTNStrengthOfTheWild.blp,ReplaceableTextures\CommandButtons\BTNImprovedStrengthOfTheWild.blp,ReplaceableTextures\CommandButtons\BTNAdvancedStrengthOfTheWild.blp 11 | Buttonpos=1,0 12 | Requirescount=3 13 | Requires= 14 | Requires1=etoa 15 | Requires2=etoe 16 | 17 | [Rema] 18 | Art=ReplaceableTextures\CommandButtons\BTNMoonArmor.blp,ReplaceableTextures\CommandButtons\BTNImprovedMoonArmor.blp,ReplaceableTextures\CommandButtons\BTNAdvancedMoonArmor.blp 19 | Buttonpos=0,1 20 | Requirescount=3 21 | Requires= 22 | Requires1=etoa 23 | Requires2=etoe 24 | 25 | [Rerh] 26 | Art=ReplaceableTextures\CommandButtons\BTNReinforcedHides.blp,ReplaceableTextures\CommandButtons\BTNImprovedReinforcedHides.blp,ReplaceableTextures\CommandButtons\BTNAdvancedReinforcedHides.blp 27 | Buttonpos=1,1 28 | Requirescount=3 29 | Requires= 30 | Requires1=etoa 31 | Requires2=etoe 32 | 33 | [Reuv] 34 | Art=ReplaceableTextures\CommandButtons\BTNUltravision.blp 35 | Buttonpos=2,0 36 | 37 | [Renb] 38 | Art=ReplaceableTextures\CommandButtons\BTNNaturesBlessing.blp 39 | Buttonpos=2,0 40 | Requires=etoa 41 | 42 | [Reib] 43 | Art=ReplaceableTextures\CommandButtons\BTNImprovedBows.blp 44 | Buttonpos=0,2 45 | Requires=etoa 46 | 47 | [Remk] 48 | Art=ReplaceableTextures\CommandButtons\BTNMarksmanship.blp 49 | Buttonpos=0,1 50 | Requires=edob,etoe 51 | 52 | [Resc] 53 | Art=ReplaceableTextures\CommandButtons\BTNSentinel.blp 54 | Buttonpos=1,2 55 | Requires=edob 56 | 57 | [Remg] 58 | Art=ReplaceableTextures\CommandButtons\BTNUpgradeMoonGlaive.blp 59 | Buttonpos=1,1 60 | Requires=edob,etoa 61 | 62 | [Redt] 63 | Art=ReplaceableTextures\CommandButtons\BTNDOTAdeptTraining.blp,ReplaceableTextures\CommandButtons\BTNDOTMasterTraining.blp 64 | Buttonpos=1,2 65 | Requirescount=2 66 | Requires= 67 | Requires1=etoe 68 | 69 | [Redc] 70 | Art=ReplaceableTextures\CommandButtons\BTNDOCAdeptTraining.blp,ReplaceableTextures\CommandButtons\BTNDOCMasterTraining.blp 71 | Buttonpos=1,2 72 | Requirescount=2 73 | Requires= 74 | Requires1=etoe 75 | 76 | [Resi] 77 | Art=ReplaceableTextures\CommandButtons\BTNDryadDispelMagic.blp 78 | Buttonpos=0,2 79 | 80 | [Reht] 81 | Art=ReplaceableTextures\CommandButtons\BTNTameHippogriff.blp 82 | Buttonpos=0,2 83 | 84 | [Recb] 85 | Art=ReplaceableTextures\CommandButtons\BTNCorrosiveBreath.blp 86 | Buttonpos=0,2 87 | 88 | [Repb] 89 | Art=ReplaceableTextures\CommandButtons\BTNVorpalBlades.blp 90 | Buttonpos=2,2 91 | Requires=etoa,edob 92 | 93 | [Rehs] 94 | Art=ReplaceableTextures\CommandButtons\BTNHardenedSkin.blp 95 | Buttonpos=2,1 96 | Requires=etoe,eden 97 | 98 | [Rers] 99 | Art=ReplaceableTextures\CommandButtons\BTNResistantSkin.blp 100 | Buttonpos=2,2 101 | Requires=etoe,eden 102 | 103 | [Reeb] 104 | Art=ReplaceableTextures\CommandButtons\BTNEnchantedBears.blp 105 | Buttonpos=1,1 106 | Requires=Redc 107 | Requiresamount=2 108 | 109 | [Reec] 110 | Art=ReplaceableTextures\CommandButtons\BTNEnchantedCrows.blp 111 | Buttonpos=1,1 112 | Requires=Redt 113 | Requiresamount=2 114 | 115 | [Rews] 116 | Art=ReplaceableTextures\CommandButtons\BTNWellSpring.blp 117 | Buttonpos=3,0 118 | Requires=etoe 119 | 120 | [Repm] 121 | Art=ReplaceableTextures\CommandButtons\BTNPackBeast.blp 122 | Buttonpos=3,0 123 | Requires=eden -------------------------------------------------------------------------------- /ceres-parsers/src/test-cases/vararg.lua: -------------------------------------------------------------------------------- 1 | -- $Id: vararg.lua,v 1.25 2016/11/07 13:11:28 roberto Exp $ 2 | -- See Copyright Notice in file all.lua 3 | 4 | print('testing vararg') 5 | 6 | function f(a, ...) 7 | local arg = {n = select('#', ...), ...} 8 | for i=1,arg.n do assert(a[i]==arg[i]) end 9 | return arg.n 10 | end 11 | 12 | function c12 (...) 13 | assert(arg == _G.arg) -- no local 'arg' 14 | local x = {...}; x.n = #x 15 | local res = (x.n==2 and x[1] == 1 and x[2] == 2) 16 | if res then res = 55 end 17 | return res, 2 18 | end 19 | 20 | function vararg (...) return {n = select('#', ...), ...} end 21 | 22 | local call = function (f, args) return f(table.unpack(args, 1, args.n)) end 23 | 24 | assert(f() == 0) 25 | assert(f({1,2,3}, 1, 2, 3) == 3) 26 | assert(f({"alo", nil, 45, f, nil}, "alo", nil, 45, f, nil) == 5) 27 | 28 | assert(c12(1,2)==55) 29 | a,b = assert(call(c12, {1,2})) 30 | assert(a == 55 and b == 2) 31 | a = call(c12, {1,2;n=2}) 32 | assert(a == 55 and b == 2) 33 | a = call(c12, {1,2;n=1}) 34 | assert(not a) 35 | assert(c12(1,2,3) == false) 36 | local a = vararg(call(next, {_G,nil;n=2})) 37 | local b,c = next(_G) 38 | assert(a[1] == b and a[2] == c and a.n == 2) 39 | a = vararg(call(call, {c12, {1,2}})) 40 | assert(a.n == 2 and a[1] == 55 and a[2] == 2) 41 | a = call(print, {'+'}) 42 | assert(a == nil) 43 | 44 | local t = {1, 10} 45 | function t:f (...) local arg = {...}; return self[...]+#arg end 46 | assert(t:f(1,4) == 3 and t:f(2) == 11) 47 | print('+') 48 | 49 | lim = 20 50 | local i, a = 1, {} 51 | while i <= lim do a[i] = i+0.3; i=i+1 end 52 | 53 | function f(a, b, c, d, ...) 54 | local more = {...} 55 | assert(a == 1.3 and more[1] == 5.3 and 56 | more[lim-4] == lim+0.3 and not more[lim-3]) 57 | end 58 | 59 | function g(a,b,c) 60 | assert(a == 1.3 and b == 2.3 and c == 3.3) 61 | end 62 | 63 | call(f, a) 64 | call(g, a) 65 | 66 | a = {} 67 | i = 1 68 | while i <= lim do a[i] = i; i=i+1 end 69 | assert(call(math.max, a) == lim) 70 | 71 | print("+") 72 | 73 | 74 | -- new-style varargs 75 | 76 | function oneless (a, ...) return ... end 77 | 78 | function f (n, a, ...) 79 | local b 80 | assert(arg == _G.arg) -- no local 'arg' 81 | if n == 0 then 82 | local b, c, d = ... 83 | return a, b, c, d, oneless(oneless(oneless(...))) 84 | else 85 | n, b, a = n-1, ..., a 86 | assert(b == ...) 87 | return f(n, a, ...) 88 | end 89 | end 90 | 91 | a,b,c,d,e = assert(f(10,5,4,3,2,1)) 92 | assert(a==5 and b==4 and c==3 and d==2 and e==1) 93 | 94 | a,b,c,d,e = f(4) 95 | assert(a==nil and b==nil and c==nil and d==nil and e==nil) 96 | 97 | 98 | -- varargs for main chunks 99 | f = load[[ return {...} ]] 100 | x = f(2,3) 101 | assert(x[1] == 2 and x[2] == 3 and x[3] == nil) 102 | 103 | 104 | f = load[[ 105 | local x = {...} 106 | for i=1,select('#', ...) do assert(x[i] == select(i, ...)) end 107 | assert(x[select('#', ...)+1] == nil) 108 | return true 109 | ]] 110 | 111 | assert(f("a", "b", nil, {}, assert)) 112 | assert(f()) 113 | 114 | a = {select(3, table.unpack{10,20,30,40})} 115 | assert(#a == 2 and a[1] == 30 and a[2] == 40) 116 | a = {select(1)} 117 | assert(next(a) == nil) 118 | a = {select(-1, 3, 5, 7)} 119 | assert(a[1] == 7 and a[2] == nil) 120 | a = {select(-2, 3, 5, 7)} 121 | assert(a[1] == 5 and a[2] == 7 and a[3] == nil) 122 | pcall(select, 10000) 123 | pcall(select, -10000) 124 | 125 | 126 | -- bug in 5.2.2 127 | 128 | function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, 129 | p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, 130 | p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, 131 | p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, 132 | p41, p42, p43, p44, p45, p46, p48, p49, p50, ...) 133 | local a1,a2,a3,a4,a5,a6,a7 134 | local a8,a9,a10,a11,a12,a13,a14 135 | end 136 | 137 | -- assertion fail here 138 | f() 139 | 140 | 141 | print('OK') 142 | 143 | -------------------------------------------------------------------------------- /ceres-data/data/units/humanupgradefunc.txt: -------------------------------------------------------------------------------- 1 | [Rhss] 2 | Requires=hvlt,hkee 3 | Art=ReplaceableTextures\CommandButtons\BTNControlMagic.blp 4 | Buttonpos=2,1 5 | 6 | [Rhme] 7 | Art=ReplaceableTextures\CommandButtons\BTNSteelMelee.blp,ReplaceableTextures\CommandButtons\BTNThoriumMelee.blp,ReplaceableTextures\CommandButtons\BTNArcaniteMelee.blp 8 | Buttonpos=0,0 9 | Requirescount=3 10 | Requires= 11 | Requires1=hkee 12 | Requires2=hcas 13 | 14 | [Rhra] 15 | Art=ReplaceableTextures\CommandButtons\BTNHumanMissileUpOne.blp,ReplaceableTextures\CommandButtons\BTNHumanMissileUpTwo.blp,ReplaceableTextures\CommandButtons\BTNHumanMissileUpThree.blp 16 | Buttonpos=1,0 17 | Requirescount=3 18 | Requires= 19 | Requires1=hkee 20 | Requires2=hcas 21 | 22 | [Rhar] 23 | Art=ReplaceableTextures\CommandButtons\BTNHumanArmorUpOne.blp,ReplaceableTextures\CommandButtons\BTNHumanArmorUpTwo.blp,ReplaceableTextures\CommandButtons\BTNHumanArmorUpThree.blp 24 | Buttonpos=0,1 25 | Requirescount=3 26 | Requires= 27 | Requires1=hkee 28 | Requires2=hcas 29 | 30 | [Rhla] 31 | Art=ReplaceableTextures\CommandButtons\BTNLeatherUpgradeOne.blp,ReplaceableTextures\CommandButtons\BTNLeatherUpgradeTwo.blp,ReplaceableTextures\CommandButtons\BTNLeatherUpgradeThree.blp 32 | Buttonpos=1,1 33 | Requirescount=3 34 | Requires= 35 | Requires1=hkee 36 | Requires2=hcas 37 | 38 | [Rhac] 39 | Art=ReplaceableTextures\CommandButtons\BTNStoneArchitecture.blp,ReplaceableTextures\CommandButtons\BTNArcaniteArchitecture.blp,ReplaceableTextures\CommandButtons\BTNImbuedMasonry.blp 40 | Buttonpos=1,0 41 | Requirescount=3 42 | Requires= 43 | Requires1=hkee 44 | Requires2=hcas 45 | 46 | [Rhgb] 47 | Art=ReplaceableTextures\CommandButtons\BTNHumanArtilleryUpOne.blp 48 | Buttonpos=0,1 49 | Requires=hcas 50 | 51 | [Rhlh] 52 | Art=ReplaceableTextures\CommandButtons\BTNHumanLumberUpgrade1.blp,ReplaceableTextures\CommandButtons\BTNHumanLumberUpgrade2.blp 53 | Buttonpos=0,0 54 | Requirescount=2 55 | Requires=hkee 56 | Requires1=hcas 57 | 58 | [Rhde] 59 | Art=ReplaceableTextures\CommandButtons\BTNDefend.blp 60 | Buttonpos=0,2 61 | 62 | [Rhan] 63 | Art=ReplaceableTextures\CommandButtons\BTNAnimalWarTraining.blp 64 | Buttonpos=2,2 65 | Requires=hlum,hcas,hbla 66 | 67 | [Rhpt] 68 | Art=ReplaceableTextures\CommandButtons\BTNPriestAdept.blp,ReplaceableTextures\CommandButtons\BTNPriestMaster.blp 69 | Buttonpos=1,2 70 | Requirescount=2 71 | Requires= 72 | Requires1=hcas 73 | 74 | [Rhst] 75 | Art=ReplaceableTextures\CommandButtons\BTNSorceressAdept.blp,ReplaceableTextures\CommandButtons\BTNSorceressMaster.blp 76 | Buttonpos=0,2 77 | Requirescount=2 78 | Requires= 79 | Requires1=hcas 80 | 81 | [Rhri] 82 | Art=ReplaceableTextures\CommandButtons\BTNDwarvenLongRifle.blp 83 | Buttonpos=1,2 84 | Requires=hkee 85 | 86 | [Rhse] 87 | Art=ReplaceableTextures\CommandButtons\BTNMagicalSentry.blp 88 | Buttonpos=2,2 89 | 90 | [Rhfl] 91 | Art=ReplaceableTextures\CommandButtons\BTNFlare.blp 92 | Buttonpos=1,2 93 | Requires=hkee 94 | 95 | [Rhhb] 96 | Requires=hcas 97 | Art=ReplaceableTextures\CommandButtons\BTNStormHammer.blp 98 | Buttonpos=1,2 99 | 100 | [Rhrt] 101 | Art=ReplaceableTextures\CommandButtons\BTNScatterRockets.blp 102 | Buttonpos=2,1 103 | Requires=hcas 104 | 105 | [Rhpm] 106 | Art=ReplaceableTextures\CommandButtons\BTNPackBeast.blp 107 | Buttonpos=3,0 108 | Requires=hvlt 109 | 110 | [Rhfc] 111 | Art=ReplaceableTextures\CommandButtons\BTNFlakCannons.blp 112 | Buttonpos=0,2 113 | Requires=hkee 114 | 115 | [Rhfs] 116 | Art=ReplaceableTextures\CommandButtons\BTNFragmentationBombs.blp 117 | Buttonpos=1,1 118 | Requires=hcas 119 | 120 | [Rhcd] 121 | Requires=hcas 122 | Art=ReplaceableTextures\CommandButtons\BTNCloudOfFog.blp 123 | Buttonpos=0,2 124 | Requires=hvlt 125 | 126 | [Rhsb] 127 | Art=ReplaceableTextures\CommandButtons\BTNSunderingBlades.blp 128 | Buttonpos=2,1 129 | Requires=hlum,hcas,hbla -------------------------------------------------------------------------------- /ceres-data/data/units/commonabilityfunc.txt: -------------------------------------------------------------------------------- 1 | //Allied building 2 | [Aall] 3 | 4 | //purchase item 5 | [Apit] 6 | Effectsound=ReceiveGold 7 | 8 | //Burrow Detection (Fliers) 9 | [Abdt] 10 | 11 | [Amou] 12 | Art=ReplaceableTextures\CommandButtons\BTNTemp.blp 13 | Unart=ReplaceableTextures\CommandButtons\BTNTemp.blp 14 | Buttonpos=1,1 15 | Unbuttonpos=1,1 16 | Order=mount 17 | Unorder=dismount 18 | 19 | [AHer] 20 | Casterart=Abilities\Spells\Other\Levelup\LevelupCaster.mdl 21 | 22 | [Amov] 23 | Specialart=Abilities\Spells\Human\MassTeleport\MassTeleportTarget.mdl 24 | Specialattach=origin 25 | 26 | [Ahar] 27 | Art=ReplaceableTextures\CommandButtons\BTNGatherGold.blp 28 | Unart=ReplaceableTextures\CommandButtons\BTNReturnGoods.blp 29 | Buttonpos=3,1 30 | UnButtonpos=3,1 31 | Order=harvest 32 | 33 | [Ahrl] 34 | Order=harvest 35 | // Lumber Harvest uses button art and position from the [Ahar] Harvest ability. 36 | 37 | [Aawa] 38 | Targetart=Abilities\Spells\Other\Awaken\Awaken.mdl 39 | Order=awaken 40 | 41 | [Arev] 42 | Targetart=Abilities\Spells\Human\ReviveHuman\ReviveHuman.mdl,Abilities\Spells\Orc\ReviveOrc\ReviveOrc.mdl,Abilities\Spells\Undead\ReviveUndead\ReviveUndead.mdl,Abilities\Spells\NightElf\ReviveNightElf\ReviveNightElf.mdl,Abilities\Spells\Demon\ReviveDemon\ReviveDemon.mdl 43 | Order=revive 44 | 45 | [Aque] 46 | Targetart=Abilities\Spells\Human\ReviveHuman\ReviveHuman.mdl,Abilities\Spells\Orc\ReviveOrc\ReviveOrc.mdl,Abilities\Spells\Undead\ReviveUndead\ReviveUndead.mdl,Abilities\Spells\NightElf\ReviveNightElf\ReviveNightElf.mdl,Abilities\Spells\Demon\ReviveDemon\ReviveDemon.mdl 47 | Order=revive 48 | 49 | // passive 'detector' ability 50 | [Adet] 51 | Buttonpos=0,2 52 | Casterart= 53 | 54 | [Adt1] 55 | Buttonpos=0,2 56 | 57 | [Adta] 58 | Art=ReplaceableTextures\CommandButtons\BTNReveal.blp 59 | Buttonpos=0,0 60 | 61 | [Xbdt] 62 | Effectart=Abilities\Spells\Other\Andt\Andt.mdl 63 | 64 | [Bdet] 65 | Buffart=ReplaceableTextures\CommandButtons\BTNDustOfAppearance.blp 66 | Targetart= 67 | 68 | [Bvul] 69 | Buffart=ReplaceableTextures\CommandButtons\BTNInvulnerable.blp 70 | Targetart=Abilities\Spells\Human\DivineShield\DivineShieldTarget.mdl 71 | Targetattach=origin 72 | 73 | [Bspe] 74 | Buffart=ReplaceableTextures\CommandButtons\BTNBoots.blp 75 | Targetart=Abilities\Spells\Items\AIsp\SpeedTarget.mdl 76 | 77 | [Bfro] 78 | Buffart=ReplaceableTextures\CommandButtons\BTNFrost.blp 79 | Targetart=Abilities\Spells\Other\FrostDamage\FrostDamage.mdl 80 | 81 | // peon/peasant repair ability 82 | [Arep] 83 | Art=ReplaceableTextures\CommandButtons\BTNRepairOn.blp 84 | Unart=ReplaceableTextures\CommandButtons\BTNRepairOff.blp 85 | Buttonpos=1,1 86 | Unbuttonpos=1,1 87 | Order=repair 88 | Orderon=repairon 89 | Orderoff=repairoff 90 | Animnames=stand,work 91 | 92 | // Stun buff 93 | [BSTN] 94 | Buffart=ReplaceableTextures\CommandButtons\BTNStun.blp 95 | Targetart=Abilities\Spells\Human\Thunderclap\ThunderclapTarget.mdl 96 | Targetattach=overhead 97 | 98 | // Pause buff 99 | [BPSE] 100 | Buffart=ReplaceableTextures\CommandButtons\BTNStun.blp 101 | Targetart=Abilities\Spells\Human\Thunderclap\ThunderclapTarget.mdl 102 | Targetattach=overhead 103 | 104 | [ARal] 105 | Art=ReplaceableTextures\CommandButtons\BTNRallyPoint.blp,ReplaceableTextures\CommandButtons\BTNOrcRallyPoint.blp,ReplaceableTextures\CommandButtons\BTNRallyPointUndead.blp,ReplaceableTextures\CommandButtons\BTNRallyPointNightElf.blp 106 | Buttonpos=3,1 107 | 108 | [AEpa] 109 | Art=ReplaceableTextures\CommandButtons\BTNSearingArrowsOn.blp 110 | Unart=ReplaceableTextures\CommandButtons\BTNSearingArrowsOff.blp 111 | Researchart=ReplaceableTextures\CommandButtons\BTNSearingArrows.blp 112 | Buttonpos=1,2 113 | Unbuttonpos=1,2 114 | Researchbuttonpos=1,0 115 | Missileart=Abilities\Weapons\SearingArrow\SearingArrowMissile.mdl 116 | Missilespeed=1500 117 | MissileHoming=1 118 | Order=poisonarrowstarg 119 | Orderon=poisonarrows 120 | Orderoff=unpoisonarrows 121 | Animnames=attack 122 | -------------------------------------------------------------------------------- /ceres-parsers/src/test-cases/locals.lua: -------------------------------------------------------------------------------- 1 | -- $Id: locals.lua,v 1.37 2016/11/07 13:11:28 roberto Exp $ 2 | -- See Copyright Notice in file all.lua 3 | 4 | print('testing local variables and environments') 5 | 6 | local debug = require"debug" 7 | 8 | 9 | -- bug in 5.1: 10 | 11 | local function f(x) x = nil; return x end 12 | assert(f(10) == nil) 13 | 14 | local function f() local x; return x end 15 | assert(f(10) == nil) 16 | 17 | local function f(x) x = nil; local y; return x, y end 18 | assert(f(10) == nil and select(2, f(20)) == nil) 19 | 20 | do 21 | local i = 10 22 | do local i = 100; assert(i==100) end 23 | do local i = 1000; assert(i==1000) end 24 | assert(i == 10) 25 | if i ~= 10 then 26 | local i = 20 27 | else 28 | local i = 30 29 | assert(i == 30) 30 | end 31 | end 32 | 33 | 34 | 35 | f = nil 36 | 37 | local f 38 | x = 1 39 | 40 | a = nil 41 | load('local a = {}')() 42 | assert(a == nil) 43 | 44 | function f (a) 45 | local _1, _2, _3, _4, _5 46 | local _6, _7, _8, _9, _10 47 | local x = 3 48 | local b = a 49 | local c,d = a,b 50 | if (d == b) then 51 | local x = 'q' 52 | x = b 53 | assert(x == 2) 54 | else 55 | assert(nil) 56 | end 57 | assert(x == 3) 58 | local f = 10 59 | end 60 | 61 | local b=10 62 | local a; repeat local b; a,b=1,2; assert(a+1==b); until a+b==3 63 | 64 | 65 | assert(x == 1) 66 | 67 | f(2) 68 | assert(type(f) == 'function') 69 | 70 | 71 | local function getenv (f) 72 | local a,b = debug.getupvalue(f, 1) 73 | assert(a == '_ENV') 74 | return b 75 | end 76 | 77 | -- test for global table of loaded chunks 78 | assert(getenv(load"a=3") == _G) 79 | local c = {}; local f = load("a = 3", nil, nil, c) 80 | assert(getenv(f) == c) 81 | assert(c.a == nil) 82 | f() 83 | assert(c.a == 3) 84 | 85 | -- old test for limits for special instructions (now just a generic test) 86 | do 87 | local i = 2 88 | local p = 4 -- p == 2^i 89 | repeat 90 | for j=-3,3 do 91 | assert(load(string.format([[local a=%s; 92 | a=a+%s; 93 | assert(a ==2^%s)]], j, p-j, i), '')) () 94 | assert(load(string.format([[local a=%s; 95 | a=a-%s; 96 | assert(a==-2^%s)]], -j, p-j, i), '')) () 97 | assert(load(string.format([[local a,b=0,%s; 98 | a=b-%s; 99 | assert(a==-2^%s)]], -j, p-j, i), '')) () 100 | end 101 | p = 2 * p; i = i + 1 102 | until p <= 0 103 | end 104 | 105 | print'+' 106 | 107 | 108 | if rawget(_G, "querytab") then 109 | -- testing clearing of dead elements from tables 110 | collectgarbage("stop") -- stop GC 111 | local a = {[{}] = 4, [3] = 0, alo = 1, 112 | a1234567890123456789012345678901234567890 = 10} 113 | 114 | local t = querytab(a) 115 | 116 | for k,_ in pairs(a) do a[k] = nil end 117 | collectgarbage() -- restore GC and collect dead fiels in `a' 118 | for i=0,t-1 do 119 | local k = querytab(a, i) 120 | assert(k == nil or type(k) == 'number' or k == 'alo') 121 | end 122 | end 123 | 124 | 125 | -- testing lexical environments 126 | 127 | assert(_ENV == _G) 128 | 129 | do 130 | local dummy 131 | local _ENV = (function (...) return ... end)(_G, dummy) -- { 132 | 133 | do local _ENV = {assert=assert}; assert(true) end 134 | mt = {_G = _G} 135 | local foo,x 136 | A = false -- "declare" A 137 | do local _ENV = mt 138 | function foo (x) 139 | A = x 140 | do local _ENV = _G; A = 1000 end 141 | return function (x) return A .. x end 142 | end 143 | end 144 | assert(getenv(foo) == mt) 145 | x = foo('hi'); assert(mt.A == 'hi' and A == 1000) 146 | assert(x('*') == mt.A .. '*') 147 | 148 | do local _ENV = {assert=assert, A=10}; 149 | do local _ENV = {assert=assert, A=20}; 150 | assert(A==20);x=A 151 | end 152 | assert(A==10 and x==20) 153 | end 154 | assert(x==20) 155 | 156 | 157 | print('OK') 158 | 159 | return 5,f 160 | 161 | end -- } 162 | 163 | -------------------------------------------------------------------------------- /ceres-core/src/lua/util.rs: -------------------------------------------------------------------------------- 1 | use pest::iterators::Pair; 2 | use rlua::prelude::*; 3 | 4 | use ceres_formats::{ObjectId, ValueType}; 5 | use ceres_formats::metadata::FieldDesc; 6 | use ceres_formats::object::Value; 7 | use ceres_parsers::lua; 8 | 9 | use crate::error::*; 10 | 11 | pub fn evaluate_macro_args<'lua>( 12 | ctx: LuaContext<'lua>, 13 | args: Pair, 14 | ) -> Result, LuaError> { 15 | if let Some(inner) = args.into_inner().next() { 16 | let chunk = ctx.load(inner.as_str()); 17 | 18 | chunk.eval() 19 | } else { 20 | Ok(LuaMultiValue::new()) 21 | } 22 | } 23 | 24 | pub fn is_value_stringable(value: &LuaValue) -> bool { 25 | match value { 26 | LuaValue::Boolean(_) => true, 27 | LuaValue::String(_) => true, 28 | LuaValue::Integer(_) => true, 29 | LuaValue::Number(_) => true, 30 | LuaValue::Table(_) => true, 31 | _ => false, 32 | } 33 | } 34 | 35 | pub fn lvalue_to_str(value: LuaValue) -> Option { 36 | if !is_value_stringable(&value) { 37 | return None; 38 | } 39 | 40 | match value { 41 | LuaValue::Boolean(b) => { 42 | if b { 43 | Some("true".into()) 44 | } else { 45 | Some("false".into()) 46 | } 47 | } 48 | LuaValue::String(s) => { 49 | let s = s.to_str().unwrap().escape_debug().to_string(); 50 | 51 | Some(format!("\"{}\"", s)) 52 | } 53 | LuaValue::Integer(i) => Some(i.to_string()), 54 | LuaValue::Number(n) => Some(n.to_string()), 55 | LuaValue::Table(t) => Some(ltable_to_str(t)), 56 | 57 | _ => unreachable!(), 58 | } 59 | } 60 | 61 | pub fn ltable_to_str(table: LuaTable) -> String { 62 | let mut out = String::new(); 63 | 64 | out += "{"; 65 | 66 | for kv in table.pairs::() { 67 | let (k, v) = kv.unwrap(); 68 | 69 | if !is_value_stringable(&k) || !is_value_stringable(&v) { 70 | continue; 71 | } 72 | 73 | if let LuaValue::Table(_) = k { 74 | continue; 75 | } 76 | 77 | let ks = lvalue_to_str(k).unwrap(); 78 | let vs = lvalue_to_str(v).unwrap(); 79 | 80 | out += "["; 81 | out += &ks; 82 | out += "] = "; 83 | out += &vs; 84 | out += ","; 85 | } 86 | 87 | out += "}"; 88 | 89 | out 90 | } 91 | 92 | pub fn wrap_result<'lua, V>(ctx: LuaContext<'lua>, value: Result) -> LuaMultiValue 93 | where 94 | V: ToLuaMulti<'lua>, 95 | { 96 | match value { 97 | Ok(value) => value.to_lua_multi(ctx).unwrap(), 98 | Err(error) => ( 99 | LuaValue::Boolean(false), 100 | error.to_string().to_lua(ctx).unwrap(), 101 | ) 102 | .to_lua_multi(ctx) 103 | .unwrap(), 104 | } 105 | } 106 | 107 | pub fn lvalue_to_objid(value: LuaValue) -> Result { 108 | match value { 109 | LuaValue::String(value) => ObjectId::from_bytes(value.as_bytes()) 110 | .ok_or_else(|| StringError::new("invalid byte sequence for id").into()), 111 | LuaValue::Integer(value) => Ok(ObjectId::new(value as u32)), 112 | _ => Err(StringError::new("cannot coerce type to object id").into()), 113 | } 114 | } 115 | 116 | pub fn value_to_lvalue<'lua>(ctx: LuaContext<'lua>, value: &Value) -> LuaValue<'lua> { 117 | match value { 118 | Value::Unreal(value) | Value::Real(value) => LuaValue::Number(*value as LuaNumber), 119 | Value::Int(value) => LuaValue::Integer(*value as LuaInteger), 120 | Value::String(value) => LuaValue::String(ctx.create_string(value).unwrap()), 121 | } 122 | } 123 | 124 | pub fn lvalue_to_value<'lua>( 125 | ctx: LuaContext<'lua>, 126 | value: LuaValue<'lua>, 127 | field_meta: &FieldDesc, 128 | ) -> Result { 129 | Ok(match field_meta.value_ty { 130 | ValueType::String => Value::String(FromLua::from_lua(value, ctx)?), 131 | ValueType::Int => Value::Int(FromLua::from_lua(value, ctx)?), 132 | ValueType::Real => Value::Real(FromLua::from_lua(value, ctx)?), 133 | ValueType::Unreal => Value::Unreal(FromLua::from_lua(value, ctx)?), 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /ceres-data/data/ui/aieditordata.txt: -------------------------------------------------------------------------------- 1 | //*************************************************************************** 2 | [AIFunctions] 3 | // Defines function calls used by the AI Editor 4 | // Note that this section should match the format of the [TriggerCalls] section 5 | // in TriggerData.txt, and the strings for these functions are in TriggerStrings.txt. 6 | // 7 | // Key: Function name 8 | // Value 0: first game version in which this function is valid 9 | // Value 1: flag (0 or 1) indicating if the call can be used in events (meaningless for AI Editor) 10 | // Value 2: return type 11 | // Value 3+: argument types 12 | // 13 | 14 | // --- Boolean 15 | // - Hero 16 | 17 | CaptainIsHome=1,0,boolean 18 | _CaptainIsHome_Category=TC_AI_HERO 19 | 20 | CaptainIsFull=1,0,boolean 21 | _CaptainIsFull_Category=TC_AI_HERO 22 | 23 | CaptainIsEmpty=1,0,boolean 24 | _CaptainIsEmpty_Category=TC_AI_HERO 25 | 26 | CaptainRetreating=1,0,boolean 27 | _CaptainRetreating_Category=TC_AI_HERO 28 | 29 | CaptainAtGoal=1,0,boolean 30 | _CaptainAtGoal_Category=TC_AI_HERO 31 | 32 | CaptainInCombat=1,0,boolean,aicaptaintype 33 | _CaptainInCombat_Defaults=AICaptainAttack 34 | _CaptainInCombat_Category=TC_AI_HERO 35 | 36 | // - Misc 37 | 38 | CreepsOnMap=1,0,boolean 39 | _CreepsOnMap_Category=TC_AI_MISC 40 | 41 | // - Town 42 | 43 | TownThreatened=1,0,boolean 44 | _TownThreatened_Category=TC_AI_TOWN 45 | 46 | TownHasMine=1,0,boolean,integer 47 | _TownHasMine_Category=TC_AI_TOWN 48 | 49 | TownHasHall=1,0,boolean,integer 50 | _TownHasHall_Category=TC_AI_TOWN 51 | 52 | ExpansionNeeded=1,0,boolean 53 | _ExpansionNeeded_Category=TC_AI_TOWN 54 | 55 | // --- Integer 56 | 57 | // - Command 58 | 59 | CommandsWaiting=1,0,integer 60 | _CommandsWaiting_Category=TC_AI_COMMAND 61 | 62 | CheckLastCommand=1,0,integer,aicommandpop 63 | _CheckLastCommand_Defaults=AICommandPop 64 | _CheckLastCommand_Category=TC_AI_COMMAND 65 | 66 | CheckLastCommandData=1,0,integer,aicommandpop 67 | _CheckLastCommandData_Defaults=AICommandLeave 68 | _CheckLastCommandData_Category=TC_AI_COMMAND 69 | 70 | // - Hero 71 | 72 | CaptainGroupSize=1,0,integer 73 | _CaptainGroupSize_Category=TC_AI_HERO 74 | 75 | CaptainReadinessHP=1,0,integer 76 | _CaptainReadinessHP_Category=TC_AI_HERO 77 | 78 | CaptainReadinessMa=1,0,integer 79 | _CaptainReadinessMa_Category=TC_AI_HERO 80 | 81 | // - Misc 82 | 83 | CurrentAttackWave=1,0,integer 84 | _CurrentAttackWave_Category=TC_AI_MISC 85 | 86 | MeleeDifficulty=1,0,integer 87 | _MeleeDifficulty_Category=TC_AI_MISC 88 | 89 | // - Resources 90 | 91 | GetGold=1,0,integer 92 | _GetGold_Category=TC_AI_RESOURCES 93 | 94 | GetWood=1,0,integer 95 | _GetWood_Category=TC_AI_RESOURCES 96 | 97 | GetGoldOwned=1,0,integer 98 | _GetGoldOwned_Category=TC_AI_RESOURCES 99 | 100 | FoodUsed=1,0,integer 101 | _FoodUsed_Category=TC_AI_RESOURCES 102 | 103 | TotalFoodProduced=1,0,integer 104 | _TotalFoodProduced_Category=TC_AI_RESOURCES 105 | 106 | GetFoodMade=1,0,integer,unitcode 107 | _GetFoodMade_Category=TC_AI_RESOURCES 108 | 109 | // - Town 110 | 111 | GetMinesOwned=1,0,integer 112 | _GetMinesOwned_Category=TC_AI_TOWN 113 | 114 | TownWithMine=1,0,integer 115 | _TownWithMine_Category=TC_AI_TOWN 116 | 117 | GetNextExpansion=1,0,integer 118 | _GetNextExpansion_Category=TC_AI_TOWN 119 | 120 | // - Unit 121 | 122 | GetUnitCount=1,0,integer,unitcode 123 | _GetUnitCount_Category=TC_AI_UNIT 124 | 125 | GetUnitCountDone=1,0,integer,unitcode 126 | _GetUnitCountDone_Category=TC_AI_UNIT 127 | 128 | GetTownUnitCount=1,0,integer,unitcode,integer,boolean 129 | _GetTownUnitCount_Defaults=_,0,false 130 | _GetTownUnitCount_Category=TC_AI_UNIT 131 | 132 | GetUnitGoldCost=1,0,integer,unitcode 133 | _GetUnitGoldCost_Category=TC_AI_UNIT 134 | 135 | GetUnitWoodCost=1,0,integer,unitcode 136 | _GetUnitWoodCost_Category=TC_AI_UNIT 137 | 138 | GetUnitBuildTime=1,0,integer,unitcode 139 | _GetUnitBuildTime_Category=TC_AI_UNIT 140 | 141 | // - Upgrade 142 | 143 | GetUpgradeLevel=1,0,integer,techcode 144 | _GetUpgradeLevel_Category=TC_AI_UPGRADE 145 | 146 | GetUpgradeGoldCost=1,0,integer,techcode 147 | _GetUpgradeGoldCost_Category=TC_AI_UPGRADE 148 | 149 | GetUpgradeWoodCost=1,0,integer,techcode 150 | _GetUpgradeWoodCost_Category=TC_AI_UPGRADE 151 | -------------------------------------------------------------------------------- /ceres-parsers/src/test-cases/verybig.lua: -------------------------------------------------------------------------------- 1 | -- $Id: verybig.lua,v 1.25 2016/11/07 13:11:28 roberto Exp $ 2 | -- See Copyright Notice in file all.lua 3 | 4 | print "testing RK" 5 | 6 | -- testing opcodes with RK arguments larger than K limit 7 | local function foo () 8 | local dummy = { 9 | -- fill first 256 entries in table of constants 10 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 11 | 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 12 | 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 13 | 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 14 | 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 15 | 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 16 | 97, 98, 99, 100, 101, 102, 103, 104, 17 | 105, 106, 107, 108, 109, 110, 111, 112, 18 | 113, 114, 115, 116, 117, 118, 119, 120, 19 | 121, 122, 123, 124, 125, 126, 127, 128, 20 | 129, 130, 131, 132, 133, 134, 135, 136, 21 | 137, 138, 139, 140, 141, 142, 143, 144, 22 | 145, 146, 147, 148, 149, 150, 151, 152, 23 | 153, 154, 155, 156, 157, 158, 159, 160, 24 | 161, 162, 163, 164, 165, 166, 167, 168, 25 | 169, 170, 171, 172, 173, 174, 175, 176, 26 | 177, 178, 179, 180, 181, 182, 183, 184, 27 | 185, 186, 187, 188, 189, 190, 191, 192, 28 | 193, 194, 195, 196, 197, 198, 199, 200, 29 | 201, 202, 203, 204, 205, 206, 207, 208, 30 | 209, 210, 211, 212, 213, 214, 215, 216, 31 | 217, 218, 219, 220, 221, 222, 223, 224, 32 | 225, 226, 227, 228, 229, 230, 231, 232, 33 | 233, 234, 235, 236, 237, 238, 239, 240, 34 | 241, 242, 243, 244, 245, 246, 247, 248, 35 | 249, 250, 251, 252, 253, 254, 255, 256, 36 | } 37 | assert(24.5 + 0.6 == 25.1) 38 | local t = {foo = function (self, x) return x + self.x end, x = 10} 39 | t.t = t 40 | assert(t:foo(1.5) == 11.5) 41 | assert(t.t:foo(0.5) == 10.5) -- bug in 5.2 alpha 42 | assert(24.3 == 24.3) 43 | assert((function () return t.x end)() == 10) 44 | end 45 | 46 | 47 | foo() 48 | foo = nil 49 | 50 | if _soft then return 10 end 51 | 52 | print "testing large programs (>64k)" 53 | 54 | -- template to create a very big test file 55 | prog = [[$ 56 | 57 | local a,b 58 | 59 | b = {$1$ 60 | b30009 = 65534, 61 | b30010 = 65535, 62 | b30011 = 65536, 63 | b30012 = 65537, 64 | b30013 = 16777214, 65 | b30014 = 16777215, 66 | b30015 = 16777216, 67 | b30016 = 16777217, 68 | b30017 = 0x7fffff, 69 | b30018 = -0x7fffff, 70 | b30019 = 0x1ffffff, 71 | b30020 = -0x1ffffd, 72 | b30021 = -65534, 73 | b30022 = -65535, 74 | b30023 = -65536, 75 | b30024 = -0xffffff, 76 | b30025 = 15012.5, 77 | $2$ 78 | }; 79 | 80 | assert(b.a50008 == 25004 and b["a11"] == -5.5) 81 | assert(b.a33007 == -16503.5 and b.a50009 == -25004.5) 82 | assert(b["b"..30024] == -0xffffff) 83 | 84 | function b:xxx (a,b) return a+b end 85 | assert(b:xxx(10, 12) == 22) -- pushself with non-constant index 86 | b.xxx = nil 87 | 88 | s = 0; n=0 89 | for a,b in pairs(b) do s=s+b; n=n+1 end 90 | -- with 32-bit floats, exact value of 's' depends on summation order 91 | assert(81800000.0 < s and s < 81860000 and n == 70001) 92 | 93 | a = nil; b = nil 94 | print'+' 95 | 96 | function f(x) b=x end 97 | 98 | a = f{$3$} or 10 99 | 100 | assert(a==10) 101 | assert(b[1] == "a10" and b[2] == 5 and b[#b-1] == "a50009") 102 | 103 | 104 | function xxxx (x) return b[x] end 105 | 106 | assert(xxxx(3) == "a11") 107 | 108 | a = nil; b=nil 109 | xxxx = nil 110 | 111 | return 10 112 | 113 | ]] 114 | 115 | -- functions to fill in the $n$ 116 | 117 | local function sig (x) 118 | return (x % 2 == 0) and '' or '-' 119 | end 120 | 121 | F = { 122 | function () -- $1$ 123 | for i=10,50009 do 124 | io.write('a', i, ' = ', sig(i), 5+((i-10)/2), ',\n') 125 | end 126 | end, 127 | 128 | function () -- $2$ 129 | for i=30026,50009 do 130 | io.write('b', i, ' = ', sig(i), 15013+((i-30026)/2), ',\n') 131 | end 132 | end, 133 | 134 | function () -- $3$ 135 | for i=10,50009 do 136 | io.write('"a', i, '", ', sig(i), 5+((i-10)/2), ',\n') 137 | end 138 | end, 139 | } 140 | 141 | file = os.tmpname() 142 | io.output(file) 143 | for s in string.gmatch(prog, "$([^$]+)") do 144 | local n = tonumber(s) 145 | if not n then io.write(s) else F[n]() end 146 | end 147 | io.close() 148 | result = dofile(file) 149 | assert(os.remove(file)) 150 | print'OK' 151 | return result 152 | 153 | -------------------------------------------------------------------------------- /ceres-core/src/resource/map_header.lua: -------------------------------------------------------------------------------- 1 | --[[ ceres map header start ]] 2 | ceres = ceres or {} 3 | ceres.modules = {} 4 | 5 | ceres.initialized = ceres.initialized or false 6 | 7 | do 8 | function _G.print(...) 9 | local args = { ... } 10 | local msgs = {} 11 | 12 | for k, v in pairs(args) do 13 | table.insert(msgs, tostring(v)) 14 | end 15 | 16 | local msg = table.concat(msgs, " ") 17 | DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, msg) 18 | end 19 | 20 | ceres.hooks = ceres.hooks or { 21 | ["reload::before"] = {}, 22 | ["reload::after"] = {}, 23 | } 24 | 25 | function ceres.hookCall(hookName) 26 | for _, callback in ipairs(ceres.hooks[hookName]) do 27 | callback() 28 | end 29 | end 30 | 31 | function ceres.addHook(hookName, callback) 32 | if not ceres.hooks[hookName] then 33 | error(("can't register non-existent Ceres hook '%s'"):format(hookName)) 34 | end 35 | 36 | table.insert(ceres.hooks[hookName], ceres.wrapSafeCall(callback)) 37 | end 38 | 39 | function ceres.safeCall(callback, ...) 40 | local success, err = pcall(callback, ...) 41 | 42 | if not success then 43 | print("ERROR: " .. err) 44 | else 45 | return err 46 | end 47 | end 48 | 49 | function ceres.wrapSafeCall(callback) 50 | return function(...) 51 | ceres.safeCall(callback, ...) 52 | end 53 | end 54 | 55 | _G.require = function(name, optional) 56 | local module = ceres.modules[name] 57 | 58 | if module ~= nil then 59 | if module.initialized then 60 | return module.cached 61 | else 62 | module.initialized = true 63 | local compiled, err = load(module.source, "module " .. name) 64 | if not compiled then 65 | error("failed to compile module " .. name .. ": " .. err) 66 | end 67 | 68 | module.cached = compiled() 69 | return module.cached 70 | end 71 | elseif not optional then 72 | error("module not found") 73 | end 74 | end 75 | 76 | local mainSuppressed = false 77 | local configSuppressed = false 78 | 79 | function ceres.suppressDefaultMain() 80 | mainSuppressed = true 81 | end 82 | 83 | function ceres.suppressDefaultConfig() 84 | configSuppressed = true 85 | end 86 | 87 | function ceres.init() 88 | if not ceres.initialized then 89 | ceres.oldMain = main or function() end 90 | ceres.oldConfig = config or function() end 91 | 92 | local success, err 93 | function _G.main() 94 | if ceres.modules["init"] and not success then 95 | print("|c00ff0000CRITICAL ERROR:|r Init script failed to load:\n") 96 | print(err) 97 | end 98 | 99 | if ceres.modules["main"] then 100 | ceres.safeCall(require, "main") 101 | if not mainSuppressed then 102 | ceres.safeCall(ceres.oldMain) 103 | end 104 | else 105 | ceres.safeCall(ceres.oldMain) 106 | end 107 | 108 | ceres.initialized = true 109 | end 110 | 111 | function _G.config() 112 | if ceres.modules["config"] then 113 | ceres.safeCall(require, "config") 114 | if configSuppressed then 115 | ceres.safeCall(ceres.oldConfig) 116 | end 117 | else 118 | ceres.safeCall(ceres.oldConfig) 119 | end 120 | end 121 | 122 | if ceres.modules["init"] then 123 | success, err = pcall(require, "init") 124 | end 125 | else 126 | ceres.hookCall("reload::before") 127 | ceres.hooks["reload::before"] = {} 128 | ceres.hooks["reload::after"] = {} 129 | local success, error = pcall(require, "main") 130 | if not success then 131 | print("|c00ff0000CRITICAL ERROR:|r Main map script failed to REload:\n") 132 | print(tostring(error)) 133 | return 134 | end 135 | ceres.hookCall("reload::after") 136 | end 137 | end 138 | end 139 | --[[ ceres map header end ]] -------------------------------------------------------------------------------- /ceres-binaries/src/bin/util.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use anyhow::Result; 5 | use clap::clap_app; 6 | use serde::Serialize; 7 | 8 | use ceres_formats::*; 9 | use ceres_formats::objectstore::ObjectStore; 10 | 11 | fn main() { 12 | dotenv::dotenv().ok(); 13 | 14 | let matches = clap_app!(Util => 15 | (version: "0.0.0") 16 | (author: "mori ") 17 | (about: "Utilities") 18 | (@subcommand dump => 19 | (about: "Dump info") 20 | (@arg type: --type +takes_value) 21 | (@arg format: --format -f +takes_value) 22 | ) 23 | (@subcommand parseobj => 24 | (about: "Parse obj file and dump info") 25 | (@arg type: --type +takes_value) 26 | (@arg format: --format -f +takes_value) 27 | (@arg FILE: +required +takes_value) 28 | ) 29 | (@subcommand rwobj => 30 | (about: "Parse and write obj file") 31 | (@arg FILE: +required +takes_value) 32 | ) 33 | (@subcommand dbg => 34 | (about: "dbg") 35 | ) 36 | ) 37 | .get_matches(); 38 | 39 | std::process::exit(match run(matches) { 40 | Err(error) => { 41 | println!("[ERROR] An error has occured. Error chain:"); 42 | println!("{:?}", error); 43 | 44 | 1 45 | } 46 | Ok(_) => 0, 47 | }); 48 | } 49 | 50 | fn serialize_obj(object: O, out_format: &str) { 51 | match out_format { 52 | "dbg" => { 53 | println!("{:#?}", object); 54 | } 55 | "bin" => { 56 | bincode::serialize_into(std::io::stdout().lock(), &object).unwrap(); 57 | } 58 | "ron" => { 59 | let pretty_config = ron::ser::PrettyConfig { 60 | depth_limit: 10, 61 | new_line: "\n".into(), 62 | indentor: " ".into(), 63 | separate_tuple_members: false, 64 | enumerate_arrays: false, 65 | }; 66 | 67 | println!( 68 | "{}", 69 | ron::ser::to_string_pretty(&object, pretty_config).unwrap() 70 | ); 71 | } 72 | _ => { 73 | panic!("wow"); 74 | } 75 | } 76 | } 77 | 78 | fn run(matches: clap::ArgMatches) -> Result<()> { 79 | let data_dir = std::env::var("DATA_DIR")?; 80 | 81 | let meta = metadata::read_metadata_dir(&data_dir); 82 | eprintln!("loaded metadata information"); 83 | let data = objectstore::read_data_dir(&data_dir, &meta); 84 | eprintln!("loaded data information"); 85 | 86 | if let Some(arg) = matches.subcommand_matches("dump") { 87 | let dump_type = arg.value_of("type").unwrap_or_else(|| "all"); 88 | let format = arg.value_of("format").unwrap_or_else(|| "dbg"); 89 | 90 | match dump_type { 91 | "all" => serialize_obj(&(&meta, &data), format), 92 | "meta" => serialize_obj(&meta, format), 93 | "obj" => serialize_obj(&data, format), 94 | _ => panic!("hello"), 95 | } 96 | } else if let Some(arg) = matches.subcommand_matches("parseobj") { 97 | let file_path: PathBuf = arg.value_of("FILE").unwrap().into(); 98 | let format = arg.value_of("format").unwrap_or_else(|| "dbg"); 99 | let kind = file_path.extension().unwrap().to_string_lossy(); 100 | let mut data = ObjectStore::default(); 101 | 102 | parser::w3obj::read::read_object_file( 103 | &fs::read(&file_path)?, 104 | &mut data, 105 | ObjectKind::from_ext(&kind), 106 | )?; 107 | 108 | serialize_obj(&data, format); 109 | } else if let Some(_arg) = matches.subcommand_matches("dbg") { 110 | dbg!(meta.field_by_id(ObjectId::from_bytes(b"amac").unwrap())); 111 | } else if let Some(arg) = matches.subcommand_matches("rwobj") { 112 | let file_path: PathBuf = arg.value_of("FILE").unwrap().into(); 113 | let kind = file_path.extension().unwrap().to_string_lossy(); 114 | let mut data = ObjectStore::default(); 115 | 116 | parser::w3obj::read::read_object_file( 117 | &fs::read(&file_path)?, 118 | &mut data, 119 | ObjectKind::from_ext(&kind), 120 | )?; 121 | 122 | parser::w3obj::write::write_object_file( 123 | std::io::stdout(), 124 | &meta, 125 | &data, 126 | ObjectKind::from_ext(&kind), 127 | )?; 128 | } 129 | 130 | Ok(()) 131 | } 132 | -------------------------------------------------------------------------------- /ceres-data/data/units_en/commonabilitystrings.txt: -------------------------------------------------------------------------------- 1 | //allied building 2 | [Aall] 3 | Name="Shop Sharing, Allied Bldg." 4 | 5 | //burrow detection, fliers 6 | [Abdt] 7 | Name=Burrow Detection 8 | EditorSuffix=" (Fliers, Obsolete)" 9 | 10 | //purchase (shop) 11 | [Apit] 12 | Name=Shop Purchase Item 13 | 14 | [Amou] 15 | Name=Mount 16 | Tip=Mo|cffffcc00r|rph 17 | Untip=De-Mo|cffffcc00r|rph 18 | Ubertip="Morphs the hero into an alternate form." 19 | Unubertip="Returns the hero to normal form." 20 | Hotkey=R 21 | Unhotkey=R 22 | 23 | [Ahar] 24 | Name=Harvest 25 | Tip=|cffffcc00G|rather 26 | Ubertip="Mines gold from gold mines and harvests lumber from trees." 27 | Untip=R|cffffcc00e|rturn Resources 28 | Unubertip="Return the carried resources to the nearest town hall." 29 | Hotkey=G 30 | Unhotkey=E 31 | EditorSuffix= (Gold and Lumber) 32 | 33 | [Ahrl] 34 | Name=Harvest 35 | Tip=|cffffcc00G|rather 36 | Ubertip="Harvests lumber from trees." 37 | Untip=R|cffffcc00e|rturn Resources 38 | Unubertip="Return the carried resources to the nearest Necropolis or Graveyard." 39 | Hotkey=G 40 | Unhotkey=E 41 | EditorSuffix= (Ghouls Lumber) 42 | 43 | [Arev] 44 | Name=Revive Hero 45 | 46 | [Aawa] 47 | Name=Revive Hero Instantly 48 | 49 | // passive 'detector' ability 50 | [Adet] 51 | Name=Detector 52 | 53 | [Adt1] 54 | Name=Detector 55 | 56 | [Adta] 57 | Name=Reveal 58 | Tip=|cffffcc00R|reveal 59 | Hotkey=R 60 | 61 | [Bdet] 62 | Bufftip=Detected 63 | Buffubertip="This unit is detected; an enemy player can see it." 64 | 65 | [Bvul] 66 | Bufftip=Invulnerable 67 | Buffubertip="This unit is invulnerable; it will not take damage from attacks and cannot be targeted by spells." 68 | 69 | [Bspe] 70 | Bufftip=Speed Bonus 71 | Buffubertip="This unit has a speed bonus; it moves more quickly than it normally does." 72 | 73 | [Bfro] 74 | Bufftip=Slowed 75 | Buffubertip="This unit is slowed; it moves more slowly than it normally does." 76 | 77 | // peon/peasant repair ability 78 | [Arep] 79 | Name=Repair 80 | Tip=|cffffcc00R|repair 81 | Ubertip="Repairs mechanical units and structures at the cost of resources." 82 | Untip="|cffc3dbffRight-click to activate auto-casting.|r" 83 | Unubertip="|cffc3dbffRight-click to deactivate auto-casting.|r" 84 | Hotkey=R 85 | Unhotkey=R 86 | 87 | // Abilities that need names in the editor 88 | [AEpa] 89 | Name=Poison Arrows 90 | Tip=Poison A|cffffcc00r|rrows - [|cffffcc00Level 1|r],Poison A|cffffcc00r|rrows - [|cffffcc00Level 2|r],Poison A|cffffcc00r|rrows - [|cffffcc00Level 3|r] 91 | Ubertip="Adds bonus fire damage to an attack against enemies, but drains mana with each shot fired.","Adds bonus fire damage to an attack, but drains mana with each shot fired.","Adds bonus fire damage to an attack, but drains mana with each shot fired." 92 | Untip="|cffc3dbffRight-click to activate auto-casting.|r" 93 | Unubertip="|cffc3dbffRight-click to deactivate auto-casting.|r" 94 | Hotkey=R 95 | Unhotkey=R 96 | Researchtip="Learn Poison A|cffffcc00r|rrows - [|cffffcc00Level %d|r]" 97 | Researchubertip="Increases the damage of the Priestess' attack by adding fire. |n|n|cffffcc00Level 1|r - bonus damage. |n|cffffcc00Level 2|r - bonus damage. |n|cffffcc00Level 3|r - bonus damage." 98 | Researchhotkey=R 99 | 100 | [AEbu] 101 | Name=Build (Night Elf) 102 | 103 | [AGbu] 104 | Name=Build (Naga) 105 | 106 | [AHbu] 107 | Name=Build (Human) 108 | 109 | [AHer] 110 | Name=Hero 111 | 112 | [ANbu] 113 | Name=Build (Neutral) 114 | 115 | [AObu] 116 | Name=Build (Orc) 117 | 118 | [ARal] 119 | Name=Rally 120 | 121 | [AUbu] 122 | Name=Build (Undead) 123 | 124 | [Aalr] 125 | Name=Alarm 126 | 127 | [Aatk] 128 | Name=Attack 129 | 130 | [Afih] 131 | Name=On Fire (Human) 132 | 133 | [Afin] 134 | Name=On Fire (Night Elf) 135 | 136 | [Afio] 137 | Name=On Fire (Orc) 138 | 139 | [Afir] 140 | Name=On Fire 141 | 142 | [Afiu] 143 | Name=On Fire (Undead) 144 | 145 | [Aloc] 146 | Name=Locust 147 | 148 | [Amov] 149 | Name=Move 150 | 151 | [Atdp] 152 | Name=Drop Pilot 153 | 154 | [Atlp] 155 | Name=Load Pilot 156 | 157 | [Attu] 158 | Name=Turret 159 | 160 | // Stun buff 161 | [BSTN] 162 | Bufftip=Stunned 163 | Buffubertip="This unit is stunned; it cannot move, attack or cast spells." 164 | 165 | // Pause buff 166 | [BPSE] 167 | Bufftip=Stunned 168 | Buffubertip="This unit will not move." 169 | EditorSuffix= (Pause) 170 | 171 | [Xbdt] 172 | EditorName=Reveal (Effect) 173 | 174 | [Xbli] 175 | EditorName=Blight (Effect) 176 | 177 | [Xdis] 178 | EditorName=Hero Dissipate (Effect) 179 | -------------------------------------------------------------------------------- /ceres-core/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::path::Path; 3 | use std::path::PathBuf; 4 | 5 | use pest::error::Error as PestError; 6 | use rlua::prelude::LuaError; 7 | use thiserror::Error; 8 | 9 | use ceres_parsers::lua; 10 | 11 | #[derive(Error, Debug)] 12 | #[error("{}", message)] 13 | pub struct StringError { 14 | message: String, 15 | } 16 | 17 | impl StringError { 18 | pub fn new>(message: S) -> StringError { 19 | StringError { 20 | message: message.into(), 21 | } 22 | } 23 | } 24 | 25 | impl From for LuaError { 26 | fn from(other: StringError) -> LuaError { 27 | LuaError::external(other) 28 | } 29 | } 30 | 31 | #[derive(Error, Debug)] 32 | #[error("{}: {}", context, cause)] 33 | pub struct ContextError { 34 | context: String, 35 | cause: E, 36 | } 37 | 38 | impl ContextError { 39 | pub fn new>(context: S, cause: E) -> Self { 40 | ContextError { 41 | context: context.into(), 42 | cause, 43 | } 44 | } 45 | } 46 | 47 | impl From> for LuaError { 48 | fn from(other: ContextError) -> LuaError { 49 | LuaError::external(other) 50 | } 51 | } 52 | 53 | #[derive(Error, Debug)] 54 | #[error("IO Error [{:?}]: {}", path, cause)] 55 | pub struct IoError { 56 | path: PathBuf, 57 | cause: std::io::Error, 58 | } 59 | 60 | impl IoError { 61 | pub fn new>(path: P, cause: std::io::Error) -> Self { 62 | IoError { 63 | path: path.as_ref().into(), 64 | cause, 65 | } 66 | } 67 | } 68 | 69 | #[derive(Error, Debug)] 70 | #[error("Could not compile file {:?}\nCause: {}", path, cause)] 71 | pub struct FileCompilationError { 72 | path: PathBuf, 73 | cause: CompilerError, 74 | } 75 | 76 | impl FileCompilationError { 77 | pub fn new(path: PathBuf, cause: CompilerError) -> FileCompilationError { 78 | FileCompilationError { path, cause } 79 | } 80 | } 81 | 82 | #[derive(Error, Debug)] 83 | pub enum CompilerError { 84 | #[error("Module not found: {}", module_name)] 85 | ModuleNotFound { module_name: String }, 86 | #[error("Could not parse file:\n{}", error)] 87 | ParserFailed { error: PestError }, 88 | #[error( 89 | "Could not compile module [{}] ({:?}):\n{}", 90 | module_name, 91 | module_path, 92 | error 93 | )] 94 | ModuleError { 95 | module_name: String, 96 | module_path: PathBuf, 97 | error: Box, 98 | }, 99 | #[error("Cyclical dependency found involving module {}", module_name)] 100 | CyclicalDependency { module_name: String }, 101 | #[error("Macro invocation failed: {}\n{}", error, diagnostic)] 102 | MacroError { 103 | error: Box, 104 | diagnostic: PestError, 105 | }, 106 | } 107 | 108 | impl From> for CompilerError { 109 | fn from(error: PestError) -> CompilerError { 110 | CompilerError::ParserFailed { error } 111 | } 112 | } 113 | 114 | #[derive(Error, Debug)] 115 | pub enum MacroInvocationError { 116 | #[error("Lua error while invoking macro: {}", error)] 117 | LuaError { error: LuaError }, 118 | #[error("Error while invoking macro: {}", message)] 119 | MessageError { message: String }, 120 | #[error("Compiler error while invoking macro: {}", error)] 121 | CompilerError { error: CompilerError }, 122 | } 123 | 124 | impl MacroInvocationError { 125 | pub fn message(message: String) -> MacroInvocationError { 126 | MacroInvocationError::MessageError { message } 127 | } 128 | } 129 | 130 | impl From for MacroInvocationError { 131 | fn from(error: LuaError) -> MacroInvocationError { 132 | MacroInvocationError::LuaError { error } 133 | } 134 | } 135 | 136 | impl From for MacroInvocationError { 137 | fn from(error: CompilerError) -> MacroInvocationError { 138 | MacroInvocationError::CompilerError { error } 139 | } 140 | } 141 | 142 | pub trait ResultExt 143 | where 144 | E: Error, 145 | { 146 | fn context(self, context: S) -> Result> 147 | where 148 | S: Into; 149 | } 150 | 151 | impl ResultExt for Result 152 | where 153 | E: Error, 154 | { 155 | fn context(self, context: S) -> Result> 156 | where 157 | S: Into, 158 | { 159 | self.map_err(|cause| ContextError::new(context, cause)) 160 | } 161 | } 162 | 163 | pub trait OptionExt { 164 | fn ok_message(self, message: S) -> Result 165 | where 166 | S: Into; 167 | } 168 | 169 | impl OptionExt for Option { 170 | fn ok_message(self, message: S) -> Result 171 | where 172 | S: Into, 173 | { 174 | self.ok_or_else(|| StringError::new(message)) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /ceres-parsers/src/lua.pest: -------------------------------------------------------------------------------- 1 | 2 | WHITESPACE = _{ " " | "\t" | "\r" | "\n" } 3 | 4 | COMMENT = _{ 5 | CommentStart ~ LongBracketStart ~ StringContentLong ~ LongBracketEnd | 6 | // CommentStart ~ PreprocessDirective | 7 | CommentStart ~ SimpleCommentContent 8 | } 9 | 10 | keyword = _{ 11 | "and" | "break" | "do" | "elseif" | "else" | "end" | 12 | "false" | "for " | "function" | "goto" | "if" | "in" | 13 | "local" | "nil" | "not" | "or" | "repeat" | "return" | 14 | "then" | "true" | "until" | "while" 15 | } 16 | 17 | CommentStart = _{ "--" } 18 | LongBracketContent = _{ "="* } 19 | LongBracketStart = _{ "[" ~ PUSH(LongBracketContent) ~ "[" } 20 | LongBracketEnd = _{ "]" ~ POP ~ "]" } 21 | LongBracketEndPeek = _{ "]" ~ PEEK ~ "]" } 22 | SimpleCommentContent = { (!("\n") ~ ANY)* } 23 | StringContentLong = { (!(LongBracketEndPeek) ~ ANY)* } 24 | 25 | Ident = @{ 26 | !(ASCII_DIGIT | WHITESPACE) ~ !(keyword ~ (!ASCII_ALPHA ~ ANY)) ~ (!(WHITESPACE) ~ (ASCII_ALPHA | "_" | ASCII_DIGIT))+ 27 | } 28 | 29 | DecimalExponent = @{ (^"e" ~ ("-" | "+")? ~ ASCII_DIGIT+) } 30 | BinaryExponent = @{ (^"p" ~ ("-" | "+")? ~ ASCII_HEX_DIGIT+) } 31 | 32 | NumberNormal = {ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT*)?} 33 | NumberFractionalOnly = {"." ~ ASCII_DIGIT+} 34 | 35 | HexNormal = {ASCII_HEX_DIGIT+ ~ ("." ~ ASCII_HEX_DIGIT*)?} 36 | HexFractionalOnly = {"." ~ ASCII_HEX_DIGIT+} 37 | 38 | LiteralNumberDec = @{ ("-")? ~ (NumberNormal | NumberFractionalOnly) ~ DecimalExponent? } 39 | LiteralNumberHex = @{ ("-")? ~ "0" ~ ^"x" ~ (HexNormal | HexFractionalOnly) ~ (DecimalExponent | BinaryExponent)? } 40 | 41 | LiteralNumber = _{ 42 | LiteralNumberHex | LiteralNumberDec 43 | } 44 | 45 | LiteralString = ${ 46 | "'" ~ StringContentSQ ~ "'" | 47 | "\"" ~ StringContentDQ ~ "\"" | 48 | LongBracketStart ~ StringContentLong ~ LongBracketEnd 49 | } 50 | 51 | StringEscape = @{ "\\" ~ ANY } 52 | 53 | StringContentSQ = @{ (!("\'" | "\\") ~ ANY)* ~ (StringEscape ~ StringContentSQ)? } 54 | StringContentDQ = @{ (!("\"" | "\\") ~ ANY)* ~ (StringEscape ~ StringContentDQ)? } 55 | 56 | LiteralNil = { "nil" } 57 | LiteralFalse = { "false" } 58 | LiteralTrue = { "true" } 59 | 60 | Chunk = {SOI ~ Block ~ EOI} 61 | Block = {Stmt* ~ StmtReturn?} 62 | 63 | StmtAssign = { VarList ~ "=" ~ ExpList } 64 | StmtFuncCall = { FunctionCall } 65 | StmtLabel = { "::" ~ Ident ~ "::" } 66 | StmtBreak = { "break" } 67 | StmtGoto = { "goto" ~ Ident } 68 | StmtDo = { "do" ~ Block ~ "end"} 69 | StmtWhile = { "while" ~ Exp ~ "do" ~ Block ~ "end" } 70 | StmtRepeat = { "repeat" ~ Block ~ "until" ~ Exp } 71 | StmtIf = { "if" ~ Exp ~ "then" ~ Block ~ ("elseif" ~ Exp ~ "then" ~ Block)* ~ ("else" ~ Block)? ~ "end" } 72 | StmtForIndex = { "for" ~ Ident ~ "=" ~ Exp ~ "," ~ Exp ~ ("," ~ Exp)? ~ "do" ~ Block ~ "end" } 73 | StmtForEach = { "for" ~ IdentList ~ "in" ~ ExpList ~ "do" ~ Block ~ "end" } 74 | StmtFuncDef = { "function" ~ FuncName ~ FuncBody } 75 | StmtLocalFuncDef = { "local" ~ "function" ~ Ident ~ FuncBody } 76 | StmtLocalDef = { "local" ~ IdentList ~ ("=" ~ ExpList)? } 77 | 78 | StmtReturn = { "return" ~ (ExpList)? ~ (";")? } 79 | 80 | Stmt = { 81 | ";" | 82 | StmtAssign | 83 | StmtFuncCall | 84 | StmtLabel | 85 | StmtBreak | 86 | StmtGoto | 87 | StmtDo | 88 | StmtWhile | 89 | StmtRepeat | 90 | StmtIf | 91 | StmtForIndex | 92 | StmtForEach | 93 | StmtLocalDef | 94 | StmtFuncDef | 95 | StmtLocalFuncDef 96 | } 97 | 98 | FuncName = { Ident ~ ("." ~ Ident)* ~ (":" ~ Ident)? } 99 | VarList = { Var ~ ("," ~ Var)* } 100 | IdentList = { Ident ~ ("," ~ Ident)* } 101 | ExpList = { Exp ~ ("," ~ Exp)* } 102 | 103 | VarArg = { "..." } 104 | 105 | ExpAtom = _{ 106 | LiteralNil | 107 | LiteralTrue | 108 | LiteralFalse | 109 | LiteralNumber | 110 | LiteralString | 111 | VarArg | 112 | AnonFuncDef | 113 | Value | 114 | TableConstructor | 115 | UnaryOpExp 116 | } 117 | 118 | UnaryOpExp = { 119 | UnaryOp ~ ExpAtom 120 | } 121 | 122 | BinaryOpExp = { 123 | ExpAtom ~ (BinaryOp ~ ExpAtom)+ 124 | } 125 | 126 | Exp = { 127 | BinaryOpExp | 128 | ExpAtom 129 | } 130 | 131 | AtomicExp = { 132 | Ident | 133 | "(" ~ Exp ~ ")" 134 | } 135 | 136 | Index = { 137 | "[" ~ Exp ~ "]" | 138 | "." ~ Ident 139 | } 140 | 141 | SimpleCall = { Args } 142 | MethodCall = { ":" ~ Ident ~ Args } 143 | 144 | Call = { 145 | SimpleCall | 146 | MethodCall 147 | } 148 | 149 | Var = { 150 | AtomicExp ~ ( 151 | Call* ~ Index | 152 | Index 153 | )* 154 | } 155 | 156 | Value = { 157 | FunctionCall | 158 | Var 159 | } 160 | 161 | FunctionCall = { 162 | Var ~ Call+ 163 | } 164 | 165 | Args = { "(" ~ ExpList? ~ ")" | TableConstructor | LiteralString } 166 | 167 | AnonFuncDef = { "function" ~ FuncBody } 168 | 169 | FuncBody = { "(" ~ ParList? ~ ")" ~ Block ~ "end" } 170 | 171 | ParList = { IdentList ~ ("," ~ "...")? | "..." } 172 | 173 | TableConstructor = { "{" ~ (FieldList)* ~ "}" } 174 | 175 | FieldList = { Field ~ (FieldSep ~ Field)* ~ FieldSep? } 176 | 177 | Field = { "[" ~ Exp ~ "]" ~ "=" ~ Exp | Ident ~ "=" ~ Exp | Exp} 178 | 179 | FieldSep = { "," | ";" } 180 | UnaryOp = { "-" | "not" | "#" | (!("~=") ~ "~") } 181 | BinaryOp = { 182 | ">>" | 183 | "<<" | 184 | "<=" | 185 | ">=" | 186 | "==" | 187 | "~=" | 188 | "//" | 189 | "+" | 190 | "-" | 191 | "*" | 192 | "/" | 193 | "^" | 194 | "%" | 195 | 196 | "&" | 197 | "~" | 198 | "|" | 199 | ".." | 200 | "<" | 201 | ">" | 202 | "and" | 203 | "or" } 204 | -------------------------------------------------------------------------------- /ceres-data/data/units_en/undeadupgradestrings.txt: -------------------------------------------------------------------------------- 1 | [Rusp] 2 | Name=Destroyer Form 3 | Tip=Research Des|cffffcc00t|rroyer Form 4 | Ubertip="Allows the Obsidian Statue to transform into a Destroyer, a large flying unit that must devour magic to sustain its mana. The Destroyer has Spell Immunity, Devour Magic, Absorb Mana, and Orb of Annihilation. |n|n|cffffcc00Attacks land and air units.|r" 5 | Hotkey=T 6 | 7 | [Rume] 8 | Name=Unholy Strength,Improved Unholy Strength,Advanced Unholy Strength 9 | Tip=Upgrade to Unholy |cffffcc00S|rtrength,Upgrade to Improved Unholy |cffffcc00S|rtrength,Upgrade to Advanced Unholy |cffffcc00S|rtrength 10 | Ubertip="Increases the attack damage of Ghouls, Meat Wagons, Abominations, Skeleton Warriors, and Skeletal Mages.","Further increases the attack damage of Ghouls, Meat Wagons, Abominations, Skeleton Warriors, and Skeletal Mages.","Further increases the attack damage of Ghouls, Meat Wagons, Abominations, Skeleton Warriors, and Skeletal Mages." 11 | Hotkey=S,S,S 12 | 13 | [Rura] 14 | Name=Creature Attack,Improved Creature Attack,Advanced Creature Attack 15 | Tip=Upgrade to Creature |cffffcc00A|rttack,Upgrade to Improved Creature |cffffcc00A|rttack,Upgrade to Advanced Creature |cffffcc00A|rttack 16 | Ubertip="Increases the attack damage of Crypt Fiends, Gargoyles, Destroyers, and Frost Wyrms.","Further increases the attack damage of Crypt Fiends, Gargoyles, Destroyers, and Frost Wyrms.","Further increases the attack damage of Crypt Fiends, Gargoyles, Destroyers, and Frost Wyrms." 17 | Hotkey=A,A,A 18 | 19 | [Ruar] 20 | Name=Unholy Armor,Improved Unholy Armor,Advanced Unholy Armor 21 | Tip=Upgrade to |cffffcc00U|rnholy Armor,Upgrade to Improved |cffffcc00U|rnholy Armor,Upgrade to Advanced |cffffcc00U|rnholy Armor 22 | Ubertip="Increases the armor of Ghouls, Abominations, Skeleton Warriors, and Skeletal Mages.","Further increases the armor of Ghouls, Abominations, Skeleton Warriors, and Skeletal Mages.","Further increases the armor of Ghouls, Abominations, Skeleton Warriors, and Skeletal Mages." 23 | Hotkey=U,U,U 24 | 25 | [Rucr] 26 | Name=Creature Carapace,Improved Creature Carapace,Advanced Creature Carapace 27 | Tip=Upgrade to Creature |cffffcc00C|rarapace,Upgrade to Improved Creature |cffffcc00C|rarapace,Upgrade to Advanced Creature |cffffcc00C|rarapace 28 | Ubertip="Increases the armor of Crypt Fiends, Gargoyles, Destroyers, and Frost Wyrms.","Further increases the armor of Crypt Fiends, Gargoyles, Destroyers, and Frost Wyrms.","Further increases the armor of the Crypt Fiends, Gargoyles, Destroyers, and Frost Wyrms." 29 | Hotkey=C,C,C 30 | 31 | [Ruac] 32 | Name=Cannibalize 33 | Tip=Research |cffffcc00C|rannibalize 34 | Ubertip="Enables Ghouls and Abominations to use the Cannibalize ability. Cannibalize consumes a nearby corpse to restore health." 35 | Hotkey=C 36 | 37 | [Rugf] 38 | Name=Ghoul Frenzy 39 | Tip=Research Ghoul Fren|cffffcc00z|ry 40 | Ubertip="Increases the attack rate of Ghouls by %, and increases their movement speed." 41 | Hotkey=Z 42 | 43 | [Ruwb] 44 | Name=Web 45 | Tip=Research |cffffcc00W|reb 46 | Ubertip="Enables Crypt Fiends to use the Web ability. Web binds a target enemy air unit in webbing, forcing it to the ground. Webbed units can be hit as though they were land units." 47 | Hotkey=W 48 | 49 | [Rusf] 50 | Name=Stone Form 51 | Tip=Research |cffffcc00S|rtone Form 52 | Ubertip="Enables the ability for Gargoyles to assume Stone Form. Stone Form transforms the Gargoyle into a statue with high armor, spell immunity, and regeneration. The Gargoyle cannot attack in this form." 53 | Hotkey=S 54 | 55 | [Rune] 56 | Name=Necromancer Adept Training,Necromancer Master Training 57 | Tip=N|cffffcc00e|rcromancer Adept Training,N|cffffcc00e|rcromancer Master Training 58 | Ubertip="Increases Necromancers' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Raise Dead.","Increases Necromancers' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Incite Unholy Frenzy." 59 | Hotkey=E,E 60 | 61 | [Ruba] 62 | Name=Banshee Adept Training,Banshee Master Training 63 | Tip=B|cffffcc00a|rnshee Adept Training,B|cffffcc00a|rnshee Master Training 64 | Ubertip="Increases Banshees' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Anti-magic Shell.","Increases Banshees' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Possession." 65 | Hotkey=A,A 66 | 67 | [Rufb] 68 | Name=Freezing Breath 69 | Tip=Research Freezing |cffffcc00B|rreath 70 | Ubertip="Enables Frost Wyrms to use the Freezing Breath ability. When cast on a building, temporarily stops production." 71 | Hotkey=B 72 | 73 | [Rusl] 74 | Name=Skeletal Longevity 75 | Tip=Research |cffffcc00S|rkeletal Longevity 76 | Ubertip="Increases the duration of raised Skeleton Warriors and Skeletal Mages by seconds." 77 | Hotkey=S 78 | 79 | [Rupc] 80 | Name=Disease Cloud 81 | Tip=Research |cffffcc00D|risease Cloud 82 | Ubertip="Gives Abominations a Disease Cloud aura that deals damage per second for seconds to nearby enemy units. Meat Wagons will cause Disease Clouds wherever they attack that deal damage per second for seconds to nearby enemy units. |nUndead are immune to Disease Cloud." 83 | Hotkey=D 84 | 85 | [Rusm] 86 | Name=Skeletal Mastery 87 | Tip=Research Skeletal |cffffcc00M|rastery 88 | Ubertip="Causes one of the two skeletons created by Raise Dead to be a Skeletal Mage and increases the duration of raised Skeleton Warriors and Skeletal Mages by seconds." 89 | Hotkey=M 90 | 91 | [Rubu] 92 | Name=Burrow 93 | Tip=Research |cffffcc00B|rurrow 94 | Ubertip="Crypt Fiends gain the ability to burrow. Burrowed Crypt Fiends are invisible and gain increased hit point regeneration, but cannot attack." 95 | Hotkey=B 96 | 97 | 98 | [Ruex] 99 | Name=Exhume Corpses 100 | Tip=Research |cffffcc00E|rxhume Corpses 101 | Ubertip="Gives Meat Wagons the ability to generate corpses." 102 | Hotkey=E 103 | 104 | [Rupm] 105 | Name=Backpack 106 | EditorSuffix= (Undead) 107 | Tip=Research |cffffcc00B|rackpack 108 | Ubertip="Gives specific Undead ground units the ability to carry items." 109 | Hotkey=B -------------------------------------------------------------------------------- /ceres-parsers/src/test-cases/goto.lua: -------------------------------------------------------------------------------- 1 | -- $Id: goto.lua,v 1.13 2016/11/07 13:11:28 roberto Exp $ 2 | -- See Copyright Notice in file all.lua 3 | 4 | collectgarbage() 5 | 6 | local function errmsg (code, m) 7 | local st, msg = load(code) 8 | assert(not st and string.find(msg, m)) 9 | end 10 | 11 | -- cannot see label inside block 12 | errmsg([[ goto l1; do ::l1:: end ]], "label 'l1'") 13 | errmsg([[ do ::l1:: end goto l1; ]], "label 'l1'") 14 | 15 | -- repeated label 16 | errmsg([[ ::l1:: ::l1:: ]], "label 'l1'") 17 | 18 | 19 | -- undefined label 20 | errmsg([[ goto l1; local aa ::l1:: ::l2:: print(3) ]], "local 'aa'") 21 | 22 | -- jumping over variable definition 23 | errmsg([[ 24 | do local bb, cc; goto l1; end 25 | local aa 26 | ::l1:: print(3) 27 | ]], "local 'aa'") 28 | 29 | -- jumping into a block 30 | errmsg([[ do ::l1:: end goto l1 ]], "label 'l1'") 31 | errmsg([[ goto l1 do ::l1:: end ]], "label 'l1'") 32 | 33 | -- cannot continue a repeat-until with variables 34 | errmsg([[ 35 | repeat 36 | if x then goto cont end 37 | local xuxu = 10 38 | ::cont:: 39 | until xuxu < x 40 | ]], "local 'xuxu'") 41 | 42 | -- simple gotos 43 | local x 44 | do 45 | local y = 12 46 | goto l1 47 | ::l2:: x = x + 1; goto l3 48 | ::l1:: x = y; goto l2 49 | end 50 | ::l3:: ::l3_1:: assert(x == 13) 51 | 52 | 53 | -- long labels 54 | do 55 | local prog = [[ 56 | do 57 | local a = 1 58 | goto l%sa; a = a + 1 59 | ::l%sa:: a = a + 10 60 | goto l%sb; a = a + 2 61 | ::l%sb:: a = a + 20 62 | return a 63 | end 64 | ]] 65 | local label = string.rep("0123456789", 40) 66 | prog = string.format(prog, label, label, label, label) 67 | assert(assert(load(prog))() == 31) 68 | end 69 | 70 | -- goto to correct label when nested 71 | do goto l3; ::l3:: end -- does not loop jumping to previous label 'l3' 72 | 73 | -- ok to jump over local dec. to end of block 74 | do 75 | goto l1 76 | local a = 23 77 | x = a 78 | ::l1::; 79 | end 80 | 81 | while true do 82 | goto l4 83 | goto l1 -- ok to jump over local dec. to end of block 84 | goto l1 -- multiple uses of same label 85 | local x = 45 86 | ::l1:: ;;; 87 | end 88 | ::l4:: assert(x == 13) 89 | 90 | if print then 91 | goto l1 -- ok to jump over local dec. to end of block 92 | error("should not be here") 93 | goto l2 -- ok to jump over local dec. to end of block 94 | local x 95 | ::l1:: ; ::l2:: ;; 96 | else end 97 | 98 | -- to repeat a label in a different function is OK 99 | local function foo () 100 | local a = {} 101 | goto l3 102 | ::l1:: a[#a + 1] = 1; goto l2; 103 | ::l2:: a[#a + 1] = 2; goto l5; 104 | ::l3:: 105 | ::l3a:: a[#a + 1] = 3; goto l1; 106 | ::l4:: a[#a + 1] = 4; goto l6; 107 | ::l5:: a[#a + 1] = 5; goto l4; 108 | ::l6:: assert(a[1] == 3 and a[2] == 1 and a[3] == 2 and 109 | a[4] == 5 and a[5] == 4) 110 | if not a[6] then a[6] = true; goto l3a end -- do it twice 111 | end 112 | 113 | ::l6:: foo() 114 | 115 | 116 | do -- bug in 5.2 -> 5.3.2 117 | local x 118 | ::L1:: 119 | local y -- cannot join this SETNIL with previous one 120 | assert(y == nil) 121 | y = true 122 | if x == nil then 123 | x = 1 124 | goto L1 125 | else 126 | x = x + 1 127 | end 128 | assert(x == 2 and y == true) 129 | end 130 | 131 | -------------------------------------------------------------------------------- 132 | -- testing closing of upvalues 133 | 134 | local debug = require 'debug' 135 | 136 | local function foo () 137 | local t = {} 138 | do 139 | local i = 1 140 | local a, b, c, d 141 | t[1] = function () return a, b, c, d end 142 | ::l1:: 143 | local b 144 | do 145 | local c 146 | t[#t + 1] = function () return a, b, c, d end -- t[2], t[4], t[6] 147 | if i > 2 then goto l2 end 148 | do 149 | local d 150 | t[#t + 1] = function () return a, b, c, d end -- t[3], t[5] 151 | i = i + 1 152 | local a 153 | goto l1 154 | end 155 | end 156 | end 157 | ::l2:: return t 158 | end 159 | 160 | local a = foo() 161 | assert(#a == 6) 162 | 163 | -- all functions share same 'a' 164 | for i = 2, 6 do 165 | assert(debug.upvalueid(a[1], 1) == debug.upvalueid(a[i], 1)) 166 | end 167 | 168 | -- 'b' and 'c' are shared among some of them 169 | for i = 2, 6 do 170 | -- only a[1] uses external 'b'/'b' 171 | assert(debug.upvalueid(a[1], 2) ~= debug.upvalueid(a[i], 2)) 172 | assert(debug.upvalueid(a[1], 3) ~= debug.upvalueid(a[i], 3)) 173 | end 174 | 175 | for i = 3, 5, 2 do 176 | -- inner functions share 'b'/'c' with previous ones 177 | assert(debug.upvalueid(a[i], 2) == debug.upvalueid(a[i - 1], 2)) 178 | assert(debug.upvalueid(a[i], 3) == debug.upvalueid(a[i - 1], 3)) 179 | -- but not with next ones 180 | assert(debug.upvalueid(a[i], 2) ~= debug.upvalueid(a[i + 1], 2)) 181 | assert(debug.upvalueid(a[i], 3) ~= debug.upvalueid(a[i + 1], 3)) 182 | end 183 | 184 | -- only external 'd' is shared 185 | for i = 2, 6, 2 do 186 | assert(debug.upvalueid(a[1], 4) == debug.upvalueid(a[i], 4)) 187 | end 188 | 189 | -- internal 'd's are all different 190 | for i = 3, 5, 2 do 191 | for j = 1, 6 do 192 | assert((debug.upvalueid(a[i], 4) == debug.upvalueid(a[j], 4)) 193 | == (i == j)) 194 | end 195 | end 196 | 197 | -------------------------------------------------------------------------------- 198 | -- testing if x goto optimizations 199 | 200 | local function testG (a) 201 | if a == 1 then 202 | goto l1 203 | error("should never be here!") 204 | elseif a == 2 then goto l2 205 | elseif a == 3 then goto l3 206 | elseif a == 4 then 207 | goto l1 -- go to inside the block 208 | error("should never be here!") 209 | ::l1:: a = a + 1 -- must go to 'if' end 210 | else 211 | goto l4 212 | ::l4a:: a = a * 2; goto l4b 213 | error("should never be here!") 214 | ::l4:: goto l4a 215 | error("should never be here!") 216 | ::l4b:: 217 | end 218 | do return a end 219 | ::l2:: do return "2" end 220 | ::l3:: do return "3" end 221 | ::l1:: return "1" 222 | end 223 | 224 | assert(testG(1) == "1") 225 | assert(testG(2) == "2") 226 | assert(testG(3) == "3") 227 | assert(testG(4) == 5) 228 | assert(testG(5) == 10) 229 | -------------------------------------------------------------------------------- 230 | 231 | 232 | print'OK' 233 | -------------------------------------------------------------------------------- /ceres-parsers/src/test-cases/utf8.lua: -------------------------------------------------------------------------------- 1 | -- $Id: utf8.lua,v 1.12 2016/11/07 13:11:28 roberto Exp $ 2 | -- See Copyright Notice in file all.lua 3 | 4 | print "testing UTF-8 library" 5 | 6 | local utf8 = require'utf8' 7 | 8 | 9 | local function checkerror (msg, f, ...) 10 | local s, err = pcall(f, ...) 11 | assert(not s and string.find(err, msg)) 12 | end 13 | 14 | 15 | local function len (s) 16 | return #string.gsub(s, "[\x80-\xBF]", "") 17 | end 18 | 19 | 20 | local justone = "^" .. utf8.charpattern .. "$" 21 | 22 | -- 't' is the list of codepoints of 's' 23 | local function checksyntax (s, t) 24 | local ts = {"return '"} 25 | for i = 1, #t do ts[i + 1] = string.format("\\u{%x}", t[i]) end 26 | ts[#t + 2] = "'" 27 | ts = table.concat(ts) 28 | assert(assert(load(ts))() == s) 29 | end 30 | 31 | assert(utf8.offset("alo", 5) == nil) 32 | assert(utf8.offset("alo", -4) == nil) 33 | 34 | -- 't' is the list of codepoints of 's' 35 | local function check (s, t) 36 | local l = utf8.len(s) 37 | assert(#t == l and len(s) == l) 38 | assert(utf8.char(table.unpack(t)) == s) 39 | 40 | assert(utf8.offset(s, 0) == 1) 41 | 42 | checksyntax(s, t) 43 | 44 | local t1 = {utf8.codepoint(s, 1, -1)} 45 | assert(#t == #t1) 46 | for i = 1, #t do assert(t[i] == t1[i]) end 47 | 48 | for i = 1, l do 49 | local pi = utf8.offset(s, i) -- position of i-th char 50 | local pi1 = utf8.offset(s, 2, pi) -- position of next char 51 | assert(string.find(string.sub(s, pi, pi1 - 1), justone)) 52 | assert(utf8.offset(s, -1, pi1) == pi) 53 | assert(utf8.offset(s, i - l - 1) == pi) 54 | assert(pi1 - pi == #utf8.char(utf8.codepoint(s, pi))) 55 | for j = pi, pi1 - 1 do 56 | assert(utf8.offset(s, 0, j) == pi) 57 | end 58 | for j = pi + 1, pi1 - 1 do 59 | assert(not utf8.len(s, j)) 60 | end 61 | assert(utf8.len(s, pi, pi) == 1) 62 | assert(utf8.len(s, pi, pi1 - 1) == 1) 63 | assert(utf8.len(s, pi) == l - i + 1) 64 | assert(utf8.len(s, pi1) == l - i) 65 | assert(utf8.len(s, 1, pi) == i) 66 | end 67 | 68 | local i = 0 69 | for p, c in utf8.codes(s) do 70 | i = i + 1 71 | assert(c == t[i] and p == utf8.offset(s, i)) 72 | assert(utf8.codepoint(s, p) == c) 73 | end 74 | assert(i == #t) 75 | 76 | i = 0 77 | for p, c in utf8.codes(s) do 78 | i = i + 1 79 | assert(c == t[i] and p == utf8.offset(s, i)) 80 | end 81 | assert(i == #t) 82 | 83 | i = 0 84 | for c in string.gmatch(s, utf8.charpattern) do 85 | i = i + 1 86 | assert(c == utf8.char(t[i])) 87 | end 88 | assert(i == #t) 89 | 90 | for i = 1, l do 91 | assert(utf8.offset(s, i) == utf8.offset(s, i - l - 1, #s + 1)) 92 | end 93 | 94 | end 95 | 96 | 97 | do -- error indication in utf8.len 98 | local function check (s, p) 99 | local a, b = utf8.len(s) 100 | assert(not a and b == p) 101 | end 102 | check("abc\xE3def", 4) 103 | check("汉字\x80", #("汉字") + 1) 104 | check("\xF4\x9F\xBF", 1) 105 | check("\xF4\x9F\xBF\xBF", 1) 106 | end 107 | 108 | -- error in utf8.codes 109 | checkerror("invalid UTF%-8 code", 110 | function () 111 | local s = "ab\xff" 112 | for c in utf8.codes(s) do assert(c) end 113 | end) 114 | 115 | 116 | -- error in initial position for offset 117 | checkerror("position out of range", utf8.offset, "abc", 1, 5) 118 | checkerror("position out of range", utf8.offset, "abc", 1, -4) 119 | checkerror("position out of range", utf8.offset, "", 1, 2) 120 | checkerror("position out of range", utf8.offset, "", 1, -1) 121 | checkerror("continuation byte", utf8.offset, "𦧺", 1, 2) 122 | checkerror("continuation byte", utf8.offset, "𦧺", 1, 2) 123 | checkerror("continuation byte", utf8.offset, "\x80", 1) 124 | 125 | 126 | 127 | local s = "hello World" 128 | local t = {string.byte(s, 1, -1)} 129 | for i = 1, utf8.len(s) do assert(t[i] == string.byte(s, i)) end 130 | check(s, t) 131 | 132 | check("汉字/漢字", {27721, 23383, 47, 28450, 23383,}) 133 | 134 | do 135 | local s = "áéí\128" 136 | local t = {utf8.codepoint(s,1,#s - 1)} 137 | assert(#t == 3 and t[1] == 225 and t[2] == 233 and t[3] == 237) 138 | checkerror("invalid UTF%-8 code", utf8.codepoint, s, 1, #s) 139 | checkerror("out of range", utf8.codepoint, s, #s + 1) 140 | t = {utf8.codepoint(s, 4, 3)} 141 | assert(#t == 0) 142 | checkerror("out of range", utf8.codepoint, s, -(#s + 1), 1) 143 | checkerror("out of range", utf8.codepoint, s, 1, #s + 1) 144 | end 145 | 146 | assert(utf8.char() == "") 147 | assert(utf8.char(97, 98, 99) == "abc") 148 | 149 | assert(utf8.codepoint(utf8.char(0x10FFFF)) == 0x10FFFF) 150 | 151 | checkerror("value out of range", utf8.char, 0x10FFFF + 1) 152 | 153 | local function invalid (s) 154 | checkerror("invalid UTF%-8 code", utf8.codepoint, s) 155 | assert(not utf8.len(s)) 156 | end 157 | 158 | -- UTF-8 representation for 0x11ffff (value out of valid range) 159 | invalid("\xF4\x9F\xBF\xBF") 160 | 161 | -- overlong sequences 162 | invalid("\xC0\x80") -- zero 163 | invalid("\xC1\xBF") -- 0x7F (should be coded in 1 byte) 164 | invalid("\xE0\x9F\xBF") -- 0x7FF (should be coded in 2 bytes) 165 | invalid("\xF0\x8F\xBF\xBF") -- 0xFFFF (should be coded in 3 bytes) 166 | 167 | 168 | -- invalid bytes 169 | invalid("\x80") -- continuation byte 170 | invalid("\xBF") -- continuation byte 171 | invalid("\xFE") -- invalid byte 172 | invalid("\xFF") -- invalid byte 173 | 174 | 175 | -- empty string 176 | check("", {}) 177 | 178 | -- minimum and maximum values for each sequence size 179 | s = "\0 \x7F\z 180 | \xC2\x80 \xDF\xBF\z 181 | \xE0\xA0\x80 \xEF\xBF\xBF\z 182 | \xF0\x90\x80\x80 \xF4\x8F\xBF\xBF" 183 | s = string.gsub(s, " ", "") 184 | check(s, {0,0x7F, 0x80,0x7FF, 0x800,0xFFFF, 0x10000,0x10FFFF}) 185 | 186 | x = "日本語a-4\0éó" 187 | check(x, {26085, 26412, 35486, 97, 45, 52, 0, 233, 243}) 188 | 189 | 190 | -- Supplementary Characters 191 | check("𣲷𠜎𠱓𡁻𠵼ab𠺢", 192 | {0x23CB7, 0x2070E, 0x20C53, 0x2107B, 0x20D7C, 0x61, 0x62, 0x20EA2,}) 193 | 194 | check("𨳊𩶘𦧺𨳒𥄫𤓓\xF4\x8F\xBF\xBF", 195 | {0x28CCA, 0x29D98, 0x269FA, 0x28CD2, 0x2512B, 0x244D3, 0x10ffff}) 196 | 197 | 198 | local i = 0 199 | for p, c in string.gmatch(x, "()(" .. utf8.charpattern .. ")") do 200 | i = i + 1 201 | assert(utf8.offset(x, i) == p) 202 | assert(utf8.len(x, p) == utf8.len(x) - i + 1) 203 | assert(utf8.len(c) == 1) 204 | for j = 1, #c - 1 do 205 | assert(utf8.offset(x, 0, p + j - 1) == p) 206 | end 207 | end 208 | 209 | print'ok' 210 | 211 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **!!!Notice!!!** 2 | 3 | Ceres is discontinued indefinitely. This is mainly due to my unavailability, and the complete dumpster fire that Reforged turned out to be. 4 | 5 | If you wish to pick up the project, the license permits you to do so. 6 | 7 | # About 8 | 9 | Ceres is a stand-alone scriptable build toolchain for Warcraft III maps. It provides a way to quickly and easily build and package Warcraft III Lua maps, as well as various other utilities and tools. 10 | 11 | It is scriptable and customizable using Lua, and can be used to orchestrate whatever you want, including: 12 | - Building a single Lua map 13 | - Building multiple Lua maps 14 | - Editing your maps' Object Data using Lua 15 | - Managing imports 16 | - And so on 17 | 18 | ## Quick Start 19 | 20 | Ceres can be used with pure Lua, or with TypeScript using a [TypeScript to Lua transpiler](https://github.com/TypeScriptToLua/TypeScriptToLua). There are template repositories for both. 21 | 22 | ### Pure Lua 23 | 24 | If you just want to use Lua and nothing else, setup is very minimal. Clone [this repository](https://github.com/ceres-wc3/ceres-lua-template), follow the instructions in the readme, and customize it to your heart's content. 25 | 26 | ### TypeScript 27 | 28 | If you want to use TypeScript in your maps, Ceres can be used with it too. Setup is a bit more complex than with pure Lua, but you can use [this repository](https://github.com/ceres-wc3/ceres-ts-template) as a template. Make sure to read the readme. 29 | 30 | There is also an example of using NPM to download external dependencies in your project, namely [Cerrie](https://github.com/ceres-wc3/cerrie), which is a library providing an idiomatic set of APIs for TypeScript projects, as well as some utilities such as File I/O and Live Reload. If you want to get started with Cerrie, take a look at [this branch of `ceres-ts-template`](https://github.com/ceres-wc3/ceres-ts-template/tree/cerrie). 31 | 32 | ## API & Docs 33 | 34 | Ceres provides various APIs to enable it to do what it does, to both maps and build scripts. Namely, it has APIs for object editing, MPQ reading/writing, file I/O, Lua script compilation, file preprocessing and so on. The entire API surface has been documented in the form of a [TypeScript declaration file](https://github.com/ceres-wc3/ceres-decl), which you can use as a reference even when not using TypeScript - all APIs are themselves pure Lua and do not require TypeScript. 35 | 36 | Parts of Ceres are also documented more in-depth in the Wiki, which you can check out for extra information. 37 | 38 | ## Build Process 39 | 40 | Ceres works by running a Lua script to build your map, called a *build script*. There is a default configuration that takes a map from a configurable folder and augments it with. If there is a `build.lua` file in the root of your project, it will also run that before building the map, allowing you to configure the build process, or override it entirely. 41 | 42 | Lua code is analyzed by looking at `require` calls to bundle all required modules into one Lua file - since Warcraft III doesn't support multiple Lua files yet. 43 | 44 | Code can also be executed during the build process by means of the `compiletime` macro. 45 | 46 | For example: 47 | 48 | ```lua 49 | --[[ inside build.lua ]] 50 | 51 | -- let's setup some data 52 | function getSomeData() 53 | return {1,2,3,4,5} 54 | end 55 | 56 | --[[ inside main.lua ]] 57 | local a = compiletime(function() 58 | print("This will be printed during the build process!") 59 | 60 | -- all compiletime macros execute in the same Lua context as other macros 61 | -- and `build.lua`, meaning you can share data and functions between them 62 | 63 | local data = getSomeData() 64 | return data 65 | end) 66 | 67 | -- compiletime will embed its return value into the compiled code, 68 | -- allowing you to retain information from the build stage 69 | -- will print '5' 70 | print(a[5]) 71 | ``` 72 | 73 | ## Macros 74 | 75 | Ceres preprocesses your Lua files before including them in the final script, allowing you to execute built-in and custom macros. The `compiletime()` macro has been mentioned before - it simply executes some code (or calculates a provided expression) and embeds the result as a Lua value in the resulting script. There is also `include`, which simply embeds a file into the source code with no preprocessing. 76 | 77 | You can also register custom macros using `macro_define`. As a rather complex example: 78 | ```lua 79 | compiletime(function() 80 | -- this strips out all newlines and extra whitespace from the macro 81 | function prepare_macro_template(s) 82 | return s:gsub("\n", ""):gsub("[ ]+", " "):gsub("^[ ]+", ""):gsub("[ ]+$", "") 83 | end 84 | 85 | -- create a template for our macro 86 | -- this code will be injected into the final script 87 | ASSERT_ARG_TEMPLATE = prepare_macro_template([[ 88 | do 89 | local t = typeId(%s) 90 | if t ~= "%s" then 91 | print(t) 92 | error("argument #%s ('%s') must be a '%s', but got a " .. t .. " instead") 93 | end 94 | end 95 | ]]) 96 | 97 | -- define a global function at compiletime which takes in the macro arguments and returns a string which will be embeded in the code 98 | function MAKE_ASSERT_ARG_TYPE(num, arg, requiredType) 99 | -- asserts can be disabled by setting the ASSERTS_DISABLED variable during the build process 100 | -- if they are disabled, nothing will be embedded in the code 101 | if not ASSERTS_DISABLED then 102 | -- if the asserts aren't disabled, return the formatted macro template according to our args 103 | return string.format(ASSERT_ARG_TEMPLATE, arg, requiredType, num, arg, requiredType) 104 | else 105 | return "" 106 | end 107 | end 108 | end) 109 | 110 | -- macro_define is itself a macro - it registers a custom macro handler 111 | -- which will be invoked when the macro is encountered by the preprocessor 112 | -- the first argument is the macro name 113 | -- the second argument is the macro handler. you can provide a closure here, 114 | -- but in our case we provide MAKE_ASSERT_ARG_TYPE, which we have defined previously 115 | macro_define("ASSERT_ARG_TYPE", MAKE_ASSERT_ARG_TYPE) 116 | 117 | -- example usage 118 | -- asserts that a and b are numbers, otherwise throws an error 119 | -- allows disabling asserts by enabling ASSERTS_DISABLED 120 | function add(a, b) 121 | ASSERT_ARG_TYPE(1, "a", "number") 122 | ASSERT_ARG_TYPE(2, "b", "number") 123 | 124 | return a + b 125 | end 126 | ``` 127 | -------------------------------------------------------------------------------- /ceres-data/data/units_en/humanupgradestrings.txt: -------------------------------------------------------------------------------- 1 | [Rhss] 2 | Name=Control Magic 3 | Tip=Research Control Ma|cffffcc00g|ric 4 | Ubertip="Gives Spell Breakers the ability to take control of enemy summoned units." 5 | Hotkey=G 6 | 7 | [Rhme] 8 | Name=Iron Forged Swords,Steel Forged Swords,Mithril Forged Swords 9 | Tip=Upgrade to Iron Forged |cffffcc00S|rwords,Upgrade to Steel Forged |cffffcc00S|rwords,Upgrade to Mithril Forged |cffffcc00S|rwords 10 | Ubertip="Increases the attack damage of Militia, Footmen, Spell Breakers, Dragonhawk Riders, Gryphon Riders and Knights.","Further increases the attack damage of Militia, Footmen, Spell Breakers, Dragonhawk Riders, Gryphon Riders and Knights.","Further increases the attack damage of Militia, Footmen, Spell Breakers, Dragonhawk Riders, Gryphon Riders and Knights." 11 | Hotkey=S,S,S 12 | 13 | [Rhra] 14 | Name=Black Gunpowder,Refined Gunpowder,Imbued Gunpowder 15 | Tip=Upgrade to Black |cffffcc00G|runpowder,Upgrade to Refined |cffffcc00G|runpowder,Upgrade to Imbued |cffffcc00G|runpowder 16 | Ubertip="Increases the ranged attack damage of Riflemen, Mortar Teams, Siege Engines and Flying Machines.","Further increases the ranged attack damage of Riflemen, Mortar Teams, Siege Engines and Flying Machines.","Further increases the ranged attack damage of Riflemen, Mortar Teams, Siege Engines and Flying Machines." 17 | Hotkey=G,G,G 18 | 19 | [Rhar] 20 | Name=Iron Plating,Steel Plating,Mithril Plating 21 | Tip=Upgrade to Iron |cffffcc00P|rlating,Upgrade to Steel |cffffcc00P|rlating,Upgrade to Mithril |cffffcc00P|rlating 22 | Ubertip="Increases the armor of Militia, Footmen, Spell Breakers, Knights, Flying Machines and Siege Engines.","Further increases the armor of Militia, Footmen, Spell Breakers, Knights, Flying Machines and Siege Engines.","Further increases the armor of Militia, Footmen, Spell Breakers, Knights, Flying Machines and Siege Engines." 23 | Hotkey=P,P,P 24 | 25 | [Rhla] 26 | Name=Studded Leather Armor,Reinforced Leather Armor,Dragonhide Armor 27 | Tip=Upgrade to Studded Leather |cffffcc00A|rrmor,Upgrade to Reinforced Leather |cffffcc00A|rrmor,Upgrade to Dragonhide |cffffcc00A|rrmor 28 | Ubertip="Increases the armor of Riflemen, Mortar Teams, Dragonhawk Riders and Gryphon Riders.","Further increases the armor of Riflemen, Mortar Teams, Dragonhawk Riders and Gryphon Riders.","Further increases the armor of Riflemen, Mortar Teams, Dragonhawk Riders and Gryphon Riders." 29 | Hotkey=A,A,A 30 | 31 | [Rhac] 32 | Name=Improved Masonry,Advanced Masonry,Imbued Masonry 33 | Tip=Upgrade to Improved |cffffcc00M|rasonry,Upgrade to Advanced |cffffcc00M|rasonry,Upgrade to Imbued |cffffcc00M|rasonry 34 | Ubertip="Increases the armor and hit points of Human buildings.","Further increases the armor and hit points of Human buildings.","Further increases the armor and hit points of Human buildings." 35 | Hotkey=M,M,M 36 | 37 | [Rhgb] 38 | Name=Flying Machine Bombs 39 | Tip=Research Flying Machine |cffffcc00B|rombs 40 | Ubertip="Allows Flying Machines to attack land units." 41 | Hotkey=B 42 | 43 | [Rhlh] 44 | Name=Improved Lumber Harvesting,Advanced Lumber Harvesting 45 | Tip=Improved |cffffcc00L|rumber Harvesting,Advanced |cffffcc00L|rumber Harvesting 46 | Ubertip="Increases the amount of lumber that Peasants can carry by .","Further increases the amount of lumber that Peasants can carry by ." 47 | Hotkey=L,L 48 | 49 | [Rhde] 50 | Name=Defend 51 | Tip=Research |cffffcc00D|refend 52 | UberTip="Allows Footmen to use the Defend ability, which increases their defensive capabilities against Piercing attacks." 53 | Hotkey=D 54 | 55 | [Rhan] 56 | Name=Animal War Training 57 | Tip=Research |cffffcc00A|rnimal War Training 58 | Ubertip="Increases the maximum hit points of Knights, Dragonhawk Riders, and Gryphon Riders by ." 59 | Hotkey=A 60 | 61 | [Rhpt] 62 | Name=Priest Adept Training,Priest Master Training 63 | Tip=Pries|cffffcc00t|r Adept Training,Pries|cffffcc00t|r Master Training 64 | Ubertip="Increases Priests' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Dispel Magic.","Increases Priests' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Inner Fire." 65 | Hotkey=T,T 66 | 67 | [Rhst] 68 | Name=Sorceress Adept Training,Sorceress Master Training 69 | Tip=S|cffffcc00o|rrceress Adept Training,S|cffffcc00o|rrceress Master Training 70 | Ubertip="Increases Sorceresses' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Invisibility.","Increases Sorceresses' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Polymorph." 71 | Hotkey=O,O 72 | 73 | [Rhri] 74 | Name=Long Rifles 75 | Tip=Upgrade to |cffffcc00L|rong Rifles 76 | Ubertip="Increases the range of Riflemen attacks." 77 | Hotkey=L 78 | 79 | [Rhse] 80 | Name=Magic Sentry 81 | Tip=Research |cffffcc00M|ragic Sentry 82 | Ubertip="Provides Human towers with the ability to detect invisible units." 83 | Hotkey=M 84 | 85 | [Rhfl] 86 | Name=Flare 87 | Tip=Research Fla|cffffcc00r|re 88 | Ubertip="Provides Mortar Teams with the Flare ability. Flares can be used to reveal any area of the map. |nCan see invisible units." 89 | Hotkey=R 90 | 91 | [Rhhb] 92 | Name=Storm Hammers 93 | Tip=Research Storm |cffffcc00H|rammers 94 | Ubertip="Causes a Gryphon Rider's attacks to damage multiple units." 95 | Hotkey=H 96 | 97 | [Rhrt] 98 | Name=Barrage 99 | Tip=Research Barra|cffffcc00g|re 100 | Ubertip="Upgrades Siege Engines, giving them the Barrage ability, which allows them to damage nearby enemy air units." 101 | Hotkey=G 102 | 103 | [Rhpm] 104 | Name=Backpack 105 | EditorSuffix= (Human) 106 | Tip=Research |cffffcc00B|rackpack 107 | Ubertip="Gives specific Human ground units the ability to carry items." 108 | Hotkey=B 109 | 110 | [Rhfc] 111 | Name=Flak Cannons 112 | Tip=Research Flak |cffffcc00C|rannons 113 | Ubertip="Upgrades the weapons on Flying Machines to give them an area effect damage attack against air units." 114 | Hotkey=C 115 | 116 | [Rhfs] 117 | Name=Fragmentation Shards 118 | Tip=Research Fragmentation |cffffcc00S|rhards 119 | Ubertip="Upgrades the mortar shells on Mortar Teams and Cannon Towers to increase the damage they deal to Unarmored and Medium armor units." 120 | Hotkey=S 121 | 122 | [Rhcd] 123 | Name=Cloud 124 | Tip=Research |cffffcc00C|rloud 125 | Ubertip="Provides Dragonhawk Riders with the Cloud ability, which stops ranged buildings from attacking." 126 | Hotkey=C 127 | 128 | [Rhsb] 129 | Name=Sundering Blades 130 | Tip=Research Sundering |cffffcc00B|rlades 131 | Ubertip="Upgrades Knights to deal increased damage to targets with Medium armor." 132 | Hotkey=B -------------------------------------------------------------------------------- /ceres-parsers/src/test-cases/closure.lua: -------------------------------------------------------------------------------- 1 | -- $Id: closure.lua,v 1.59 2016/11/07 13:11:28 roberto Exp $ 2 | -- See Copyright Notice in file all.lua 3 | 4 | print "testing closures" 5 | 6 | local A,B = 0,{g=10} 7 | function f(x) 8 | local a = {} 9 | for i=1,1000 do 10 | local y = 0 11 | do 12 | a[i] = function () B.g = B.g+1; y = y+x; return y+A end 13 | end 14 | end 15 | local dummy = function () return a[A] end 16 | collectgarbage() 17 | A = 1; assert(dummy() == a[1]); A = 0; 18 | assert(a[1]() == x) 19 | assert(a[3]() == x) 20 | collectgarbage() 21 | assert(B.g == 12) 22 | return a 23 | end 24 | 25 | local a = f(10) 26 | -- force a GC in this level 27 | local x = {[1] = {}} -- to detect a GC 28 | setmetatable(x, {__mode = 'kv'}) 29 | while x[1] do -- repeat until GC 30 | local a = A..A..A..A -- create garbage 31 | A = A+1 32 | end 33 | assert(a[1]() == 20+A) 34 | assert(a[1]() == 30+A) 35 | assert(a[2]() == 10+A) 36 | collectgarbage() 37 | assert(a[2]() == 20+A) 38 | assert(a[2]() == 30+A) 39 | assert(a[3]() == 20+A) 40 | assert(a[8]() == 10+A) 41 | assert(getmetatable(x).__mode == 'kv') 42 | assert(B.g == 19) 43 | 44 | 45 | -- testing equality 46 | a = {} 47 | for i = 1, 5 do a[i] = function (x) return x + a + _ENV end end 48 | assert(a[3] == a[4] and a[4] == a[5]) 49 | 50 | for i = 1, 5 do a[i] = function (x) return i + a + _ENV end end 51 | assert(a[3] ~= a[4] and a[4] ~= a[5]) 52 | 53 | local function f() 54 | return function (x) return math.sin(_ENV[x]) end 55 | end 56 | assert(f() == f()) 57 | 58 | 59 | -- testing closures with 'for' control variable 60 | a = {} 61 | for i=1,10 do 62 | a[i] = {set = function(x) i=x end, get = function () return i end} 63 | if i == 3 then break end 64 | end 65 | assert(a[4] == nil) 66 | a[1].set(10) 67 | assert(a[2].get() == 2) 68 | a[2].set('a') 69 | assert(a[3].get() == 3) 70 | assert(a[2].get() == 'a') 71 | 72 | a = {} 73 | local t = {"a", "b"} 74 | for i = 1, #t do 75 | local k = t[i] 76 | a[i] = {set = function(x, y) i=x; k=y end, 77 | get = function () return i, k end} 78 | if i == 2 then break end 79 | end 80 | a[1].set(10, 20) 81 | local r,s = a[2].get() 82 | assert(r == 2 and s == 'b') 83 | r,s = a[1].get() 84 | assert(r == 10 and s == 20) 85 | a[2].set('a', 'b') 86 | r,s = a[2].get() 87 | assert(r == "a" and s == "b") 88 | 89 | 90 | -- testing closures with 'for' control variable x break 91 | for i=1,3 do 92 | f = function () return i end 93 | break 94 | end 95 | assert(f() == 1) 96 | 97 | for k = 1, #t do 98 | local v = t[k] 99 | f = function () return k, v end 100 | break 101 | end 102 | assert(({f()})[1] == 1) 103 | assert(({f()})[2] == "a") 104 | 105 | 106 | -- testing closure x break x return x errors 107 | 108 | local b 109 | function f(x) 110 | local first = 1 111 | while 1 do 112 | if x == 3 and not first then return end 113 | local a = 'xuxu' 114 | b = function (op, y) 115 | if op == 'set' then 116 | a = x+y 117 | else 118 | return a 119 | end 120 | end 121 | if x == 1 then do break end 122 | elseif x == 2 then return 123 | else if x ~= 3 then error() end 124 | end 125 | first = nil 126 | end 127 | end 128 | 129 | for i=1,3 do 130 | f(i) 131 | assert(b('get') == 'xuxu') 132 | b('set', 10); assert(b('get') == 10+i) 133 | b = nil 134 | end 135 | 136 | pcall(f, 4); 137 | assert(b('get') == 'xuxu') 138 | b('set', 10); assert(b('get') == 14) 139 | 140 | 141 | local w 142 | -- testing multi-level closure 143 | function f(x) 144 | return function (y) 145 | return function (z) return w+x+y+z end 146 | end 147 | end 148 | 149 | y = f(10) 150 | w = 1.345 151 | assert(y(20)(30) == 60+w) 152 | 153 | -- testing closures x repeat-until 154 | 155 | local a = {} 156 | local i = 1 157 | repeat 158 | local x = i 159 | a[i] = function () i = x+1; return x end 160 | until i > 10 or a[i]() ~= x 161 | assert(i == 11 and a[1]() == 1 and a[3]() == 3 and i == 4) 162 | 163 | 164 | -- testing closures created in 'then' and 'else' parts of 'if's 165 | a = {} 166 | for i = 1, 10 do 167 | if i % 3 == 0 then 168 | local y = 0 169 | a[i] = function (x) local t = y; y = x; return t end 170 | elseif i % 3 == 1 then 171 | goto L1 172 | error'not here' 173 | ::L1:: 174 | local y = 1 175 | a[i] = function (x) local t = y; y = x; return t end 176 | elseif i % 3 == 2 then 177 | local t 178 | goto l4 179 | ::l4a:: a[i] = t; goto l4b 180 | error("should never be here!") 181 | ::l4:: 182 | local y = 2 183 | t = function (x) local t = y; y = x; return t end 184 | goto l4a 185 | error("should never be here!") 186 | ::l4b:: 187 | end 188 | end 189 | 190 | for i = 1, 10 do 191 | assert(a[i](i * 10) == i % 3 and a[i]() == i * 10) 192 | end 193 | 194 | print'+' 195 | 196 | 197 | -- test for correctly closing upvalues in tail calls of vararg functions 198 | local function t () 199 | local function c(a,b) assert(a=="test" and b=="OK") end 200 | local function v(f, ...) c("test", f() ~= 1 and "FAILED" or "OK") end 201 | local x = 1 202 | return v(function() return x end) 203 | end 204 | t() 205 | 206 | 207 | -- test for debug manipulation of upvalues 208 | local debug = require'debug' 209 | 210 | do 211 | local a , b, c = 3, 5, 7 212 | foo1 = function () return a+b end; 213 | foo2 = function () return b+a end; 214 | do 215 | local a = 10 216 | foo3 = function () return a+b end; 217 | end 218 | end 219 | 220 | assert(debug.upvalueid(foo1, 1)) 221 | assert(debug.upvalueid(foo1, 2)) 222 | assert(not pcall(debug.upvalueid, foo1, 3)) 223 | assert(debug.upvalueid(foo1, 1) == debug.upvalueid(foo2, 2)) 224 | assert(debug.upvalueid(foo1, 2) == debug.upvalueid(foo2, 1)) 225 | assert(debug.upvalueid(foo3, 1)) 226 | assert(debug.upvalueid(foo1, 1) ~= debug.upvalueid(foo3, 1)) 227 | assert(debug.upvalueid(foo1, 2) == debug.upvalueid(foo3, 2)) 228 | 229 | assert(debug.upvalueid(string.gmatch("x", "x"), 1) ~= nil) 230 | 231 | assert(foo1() == 3 + 5 and foo2() == 5 + 3) 232 | debug.upvaluejoin(foo1, 2, foo2, 2) 233 | assert(foo1() == 3 + 3 and foo2() == 5 + 3) 234 | assert(foo3() == 10 + 5) 235 | debug.upvaluejoin(foo3, 2, foo2, 1) 236 | assert(foo3() == 10 + 5) 237 | debug.upvaluejoin(foo3, 2, foo2, 2) 238 | assert(foo3() == 10 + 3) 239 | 240 | assert(not pcall(debug.upvaluejoin, foo1, 3, foo2, 1)) 241 | assert(not pcall(debug.upvaluejoin, foo1, 1, foo2, 3)) 242 | assert(not pcall(debug.upvaluejoin, foo1, 0, foo2, 1)) 243 | assert(not pcall(debug.upvaluejoin, print, 1, foo2, 1)) 244 | assert(not pcall(debug.upvaluejoin, {}, 1, foo2, 1)) 245 | assert(not pcall(debug.upvaluejoin, foo1, 1, print, 1)) 246 | 247 | print'OK' 248 | -------------------------------------------------------------------------------- /ceres-data/data/units_en/orcupgradestrings.txt: -------------------------------------------------------------------------------- 1 | [Roch] 2 | Name=Chaos 3 | 4 | [Rome] 5 | Name=Steel Melee Weapons,Thorium Melee Weapons,Arcanite Melee Weapons 6 | Tip=Upgrade to Steel |cffffcc00M|relee Weapons,Upgrade to Thorium |cffffcc00M|relee Weapons,Upgrade to Arcanite |cffffcc00M|relee Weapons 7 | Ubertip="Increases the melee attack damage of Grunts, Raiders and Tauren.","Further increases the melee attack damage of Grunts, Raiders and Tauren.","Further increases the melee attack damage of Grunts, Raiders and Tauren." 8 | Hotkey=M,M,M 9 | 10 | [Rora] 11 | Name=Steel Ranged Weapons,Thorium Ranged Weapons,Arcanite Ranged Weapons 12 | Tip=Upgrade to Steel |cffffcc00R|ranged Weapons,Upgrade to Thorium |cffffcc00R|ranged Weapons,Upgrade to Arcanite |cffffcc00R|ranged Weapons 13 | Ubertip="Increases the ranged attack damage of Headhunters, Wind Riders, Troll Batriders and Demolishers.","Further increases the ranged attack damage of Headhunters, Wind Riders, Troll Batriders and Demolishers.","Further increases the ranged attack damage of Headhunters, Wind Riders, Troll Batriders and Demolishers." 14 | Hotkey=R,R,R 15 | 16 | [Roar] 17 | Name=Steel Armor,Thorium Armor,Arcanite Armor 18 | Tip=Upgrade to Steel Unit |cffffcc00A|rrmor,Upgrade to Thorium Unit |cffffcc00A|rrmor,Upgrade to Arcanite Unit |cffffcc00A|rrmor 19 | Ubertip="Increases the armor of Grunts, Raiders, Troll Batriders, Tauren, Headhunters, Wind Riders and Demolishers.","Further increases the armor of Grunts, Raiders, Troll Batriders, Tauren, Headhunters, Wind Riders and Demolishers.","Further increases the armor of Grunts, Raiders, Troll Batriders, Tauren, Headhunters, Wind Riders and Demolishers." 20 | Hotkey=A,A,A 21 | 22 | [Rwdm] 23 | Name=War Drums Damage Increase 24 | Tip=Upgrade War |cffffcc00D|rrums 25 | Ubertip="Increases the damage bonus that the War Drums aura on the Kodo Beast gives. War Drums increases the damage of friendly units around Kodo Beasts." 26 | Hotkey=D 27 | 28 | // pillage 29 | [Ropg] 30 | Name=Pillage 31 | Tip=Pilla|cffffcc00g|re 32 | Ubertip="Causes Peons', Grunts', and Raiders' attacks to gain resources when hitting enemy buildings." 33 | Hotkey=G 34 | 35 | [Robs] 36 | Name=Berserker Strength 37 | Tip=Research |cffffcc00B|rerserker Strength 38 | Ubertip="Improves the fighting capabilities of Grunts with a hit point increase, and bonus attack damage." 39 | Hotkey=B 40 | 41 | [Rows] 42 | Name=Pulverize Damage Increase 43 | Tip=Upgrade |cffffcc00P|rulverize 44 | Ubertip="Upgrades the totem carried by Tauren, increasing the damage of their Pulverize ability." 45 | Hotkey=P 46 | 47 | [Roen] 48 | Name=Ensnare 49 | Tip=Research E|cffffcc00n|rsnare 50 | Ubertip="Enables Raiders to use the Ensnare ability. Ensnare causes a target enemy unit to be bound to the ground so that it cannot move. Air units that are ensnared can be attacked as though they were land units." 51 | Hotkey=N 52 | 53 | [Rovs] 54 | Name=Envenomed Spears 55 | Tip=Research |cffffcc00E|rnvenomed Spears 56 | Ubertip="Adds an additional poison effect to Wind Riders' attacks. A unit poisoned by Envenomed Spears takes damage over time." 57 | Hotkey=E 58 | 59 | [Rowd] 60 | Name=Witch Doctor Adept Training,Witch Doctor Master Training 61 | Tip=Witch |cffffcc00D|roctor Adept Training,Witch |cffffcc00D|roctor Master Training 62 | Ubertip="Increases Witch Doctors' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Stasis Trap.","Increases Witch Doctors' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Healing Ward." 63 | Hotkey=D,D 64 | 65 | [Rost] 66 | Name=Shaman Adept Training,Shaman Master Training 67 | Tip=Sha|cffffcc00m|ran Adept Training,Sha|cffffcc00m|ran Master Training 68 | Ubertip="Increases Shaman mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Lightning Shield.","Increases Shaman mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Bloodlust." 69 | Hotkey=M,M 70 | 71 | [Rosp] 72 | Name=Spiked Barricades,Improved Spiked Barricades,Advanced Spiked Barricades 73 | Tip=Upgrade to |cffffcc00S|rpiked Barricades,Upgrade to Improved |cffffcc00S|rpiked Barricades,Upgrade to Advanced |cffffcc00S|rpiked Barricades 74 | Ubertip="Surrounds Orc buildings with spikes that damage enemy melee attackers. Deals damage per attack plus an additional % of the attacker's damage.","Increases the additional damage by % of the attacker's damage.","Further increases the amount of damage inflicted on melee attackers by ." 75 | Hotkey=S,S,S 76 | 77 | [Rotr] 78 | Name=Troll Regeneration,Improved Troll Regeneration,Advanced Troll Regeneration 79 | Tip=Research Troll |cffffcc00R|regeneration, Research Improved Troll |cffffcc00R|regeneration, Research Advanced Troll |cffffcc00R|regeneration 80 | Ubertip="Increases the hit point regeneration rate of Headhunters, Witch Doctors and Troll Batriders.","Further increases the hit point regeneration rate of Headhunters, Witch Doctors and Troll Batriders.","Further increases the hit point regeneration rate of Headhunters, Witch Doctors and Troll Batriders." 81 | Hotkey=R 82 | 83 | [Rolf] 84 | Name=Liquid Fire 85 | Tip=Research |cffffcc00L|riquid Fire 86 | Ubertip="Gives Troll Batriders the Liquid Fire attack, which deals damage over time to enemy buildings, stops them from being repaired, and reduces the attack rate of enemy buildings." 87 | Hotkey=L 88 | 89 | [Ropm] 90 | Name=Backpack 91 | EditorSuffix= (Orc) 92 | Tip=Research |cffffcc00B|rackpack 93 | Ubertip="Gives specific Orc ground units the ability to carry items." 94 | Hotkey=B 95 | 96 | [Rowt] 97 | Name=Spirit Walker Adept Training,Spirit Walker Master Training 98 | Tip=Spi|cffffcc00r|rit Walker Adept Training,Spi|cffffcc00r|rit Walker Master Training 99 | Ubertip="Increases Spirit Walkers' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Disenchant.","Increases Spirit Walkers' mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Ancestral Spirit." 100 | Hotkey=R,R 101 | 102 | [Robk] 103 | Name=Berserker Upgrade 104 | Tip=B|cffffcc00e|rrserker Upgrade 105 | Ubertip="Transforms Headhunters into Troll Berserkers, giving them increased hit points and the Berserk ability." 106 | Hotkey=E 107 | 108 | [Rorb] 109 | Name=Reinforced Defenses 110 | Tip=Reinforced |cffffcc00D|refenses 111 | Ubertip="Upgrades Burrows and Watch Towers so that they have Fortified armor." 112 | Hotkey=D 113 | 114 | [Robf] 115 | Name=Burning Oil 116 | Tip=Bur|cffffcc00n|ring Oil 117 | Ubertip="Upgrades Demolishers to fire rocks smothered in burning oil, which causes the ground to burn." 118 | Hotkey=N -------------------------------------------------------------------------------- /ceres-data/data/units_en/nightelfupgradestrings.txt: -------------------------------------------------------------------------------- 1 | [Resm] 2 | Name=Strength of the Moon,Improved Strength of the Moon,Advanced Strength of the Moon 3 | Tip=Upgrade to Strength of the |cffffcc00M|roon,Upgrade to Improved Strength of the |cffffcc00M|roon,Upgrade to Advanced Strength of the |cffffcc00M|roon 4 | Ubertip="Increases the damage of Archers, Huntresses, Glaive Throwers, and Hippogryph Riders.","Further increases the damage of Archers, Huntresses, Glaive Throwers, and Hippogryph Riders.","Further increases the damage of Archers, Huntresses, Glaive Throwers, and Hippogryph Riders." 5 | Hotkey=M,M,M 6 | 7 | [Resw] 8 | Name=Strength of the Wild,Improved Strength of the Wild,Advanced Strength of the Wild 9 | Tip=Upgrade to Strength of the |cffffcc00W|rild,Upgrade to Improved Strength of the |cffffcc00W|rild,Upgrade to Advanced Strength of the |cffffcc00W|rild 10 | Ubertip="Increases the damage of Druids of the Claw in Bear Form, Druids of the Talon in Storm Crow Form, Dryads, Mountain Giants, Faerie Dragons, Hippogryphs, and Chimaeras.","Further increases the damage of Druids of the Claw in Bear Form, Druids of the Talon in Storm Crow Form, Dryads, Mountain Giants, Faerie Dragons, Hippogryphs, and Chimaeras.","Further increases the damage of Druids of the Claw in Bear Form, Druids of the Talon in Storm Crow Form, Dryads, Mountain Giants, Faerie Dragons, Hippogryphs, and Chimaeras." 11 | Hotkey=W,W,W 12 | 13 | [Rema] 14 | Name=Moon Armor,Improved Moon Armor,Advanced Moon Armor 15 | Tip=Upgrade to Moon |cffffcc00A|rrmor,Upgrade to Improved Moon |cffffcc00A|rrmor,Upgrade to Advanced Moon |cffffcc00A|rrmor 16 | Ubertip="Increases the armor of Archers, Huntresses, and Hippogryph Riders.","Further increases the armor of Archers, Huntresses, and Hippogryph Riders.","Further increases the armor of Archers, Huntresses, and Hippogryph Riders." 17 | Hotkey=A,A,A 18 | 19 | [Rerh] 20 | Name=Reinforced Hides,Improved Reinforced Hides,Advanced Reinforced Hides 21 | Tip=Upgrade to |cffffcc00R|reinforced Hides,Upgrade to Improved |cffffcc00R|reinforced Hides,Upgrade to Advanced |cffffcc00R|reinforced Hides 22 | Ubertip="Increases the armor of Druids of the Claw in Bear Form, Druids of the Talon in Storm Crow Form, Dryads, Mountain Giants, Faerie Dragons, Hippogryphs, and Chimaeras.","Further increases the armor of Druids of the Claw in Bear Form, Druids of the Talon in Storm Crow Form, Dryads, Mountain Giants, Faerie Dragons, Hippogryphs, and Chimaeras.","Further increases the armor of Druids of the Claw in Bear Form, Druids of the Talon in Storm Crow Form, Dryads, Mountain Giants, Faerie Dragons, Hippogryphs, and Chimaeras." 23 | Hotkey=R,R,R 24 | 25 | [Reuv] 26 | Name=Ultravision 27 | Tip=Upgrade to |cffffcc00U|rltravision 28 | Ubertip="Gives Night Elves the ability to see as far at night as they do during the day." 29 | Hotkey=U 30 | 31 | [Renb] 32 | Name=Nature's Blessing 33 | Tip=Research |cffffcc00N|rature's Blessing 34 | Ubertip="Upgrades all Ancients' and Treants' movement speed and armor." 35 | Hotkey=N 36 | 37 | [Reib] 38 | Name=Improved Bows 39 | Tip=Research |cffffcc00I|rmproved Bows 40 | Ubertip="Increases the Archer's and Hippogryph Rider's attack range." 41 | Hotkey=I 42 | 43 | [Remk] 44 | Name=Marksmanship 45 | Tip=Research |cffffcc00M|rarksmanship 46 | Ubertip="Increases damage of Archers and Hippogryph Riders by ." 47 | Hotkey=M 48 | 49 | [Resc] 50 | Name=Sentinel 51 | Tip=Research |cffffcc00S|rentinel 52 | Ubertip="Gives the Huntress the ability to send her owl companion to a nearby tree and provide vision. |nCan see invisible units." 53 | Hotkey=S 54 | 55 | [Remg] 56 | Name=Upgrade Moon Glaive 57 | Tip=Upgrade Moon |cffffcc00G|rlaive 58 | Ubertip="Gives the Huntress the ability to strike additional units with her bouncing glaive attacks." 59 | Hotkey=G 60 | 61 | [Redt] 62 | Name=Druid of the Talon Adept Training,Druid of the Talon Master Training 63 | Tip=Druid of the T|cffffcc00a|rlon Adept Training,Druid of the T|cffffcc00a|rlon Master Training 64 | Ubertip="Increases their Night Elf Form's mana capacity, mana regeneration rate, hit points, and gives them the ability Storm Crow Form.","Increases their Night Elf Form's mana capacity, mana regeneration rate, hit points, and gives them the ability to cast Cyclone." 65 | Hotkey=A,A 66 | 67 | [Redc] 68 | Name=Druid of the Claw Adept Training,Druid of the Claw Master Training 69 | Tip=Druid of the C|cffffcc00l|raw Adept Training,Druid of the C|cffffcc00l|raw Master Training 70 | Ubertip="Increases their Night Elf Form's mana capacity, mana regeneration rate, hit points, attack damage and gives them the ability Rejuvenation.","Increases their Night Elf Form's mana capacity, mana regeneration rate, hit points, attack damage and gives them the ability Bear Form." 71 | Hotkey=L,L 72 | 73 | [Resi] 74 | Name=Abolish Magic 75 | Tip=Research Aboli|cffffcc00s|rh Magic 76 | Ubertip="Gives the Dryad the ability to dispel positive buffs from enemy units, and negative buffs from friendly units. |nDamages summoned units." 77 | Hotkey=S 78 | 79 | [Reht] 80 | Name=Hippogryph Taming 81 | Tip=Research H|cffffcc00i|rppogryph Taming 82 | Ubertip="Trains Hippogryphs to allow Archers to mount them. This allows them to attack both air and ground units." 83 | Hotkey=I 84 | 85 | [Recb] 86 | Name=Corrosive Breath 87 | Tip=Research Corrosive |cffffcc00B|rreath 88 | Ubertip="Gives Chimaeras the ability to hurl corrosive bile onto enemy buildings." 89 | Hotkey=B 90 | 91 | [Repb] 92 | Name=Vorpal Blades 93 | Tip=Research Vor|cffffcc00p|ral Blades 94 | Ubertip="Increases the speed of glaives launched by the Glaive Thrower. Also allows them to attack trees." 95 | Hotkey=P 96 | 97 | [Rers] 98 | Name=Resistant Skin 99 | Tip=Research Resis|cffffcc00t|rant Skin 100 | Ubertip="Gives Mountain Giants increased resistance to spell damage." 101 | Hotkey=T 102 | 103 | [Rehs] 104 | Name=Hardened Skin 105 | Tip=Research |cffffcc00H|rardened Skin 106 | Ubertip="Gives Mountain Giants increased resistance to attack damage." 107 | Hotkey=H 108 | 109 | [Reeb] 110 | Name=Mark of the Claw 111 | Tip=Research |cffffcc00M|rark of the Claw 112 | Ubertip="Allows Druids of the Claw to cast Roar while in Bear Form." 113 | Hotkey=M 114 | 115 | [Reec] 116 | Name=Mark of the Talon 117 | Tip=Research |cffffcc00M|rark of the Talon 118 | Ubertip="Allows Druids of the Talon to cast Faerie Fire while in Storm Crow Form." 119 | Hotkey=M 120 | 121 | [Rews] 122 | Name=Well Spring 123 | Tip=Research W|cffffcc00e|rll Spring 124 | Ubertip="Increases the amount of mana that can be stored in Moon Wells by , and their rate of mana regeneration by %." 125 | Hotkey=E 126 | 127 | [Repm] 128 | Name=Backpack 129 | EditorSuffix= (Night Elf) 130 | Tip=Research |cffffcc00B|rackpack 131 | Ubertip="Gives specific Night Elf ground units the ability to carry items." 132 | Hotkey=B -------------------------------------------------------------------------------- /ceres-parsers/src/test-cases/code.lua: -------------------------------------------------------------------------------- 1 | -- $Id: code.lua,v 1.42 2016/11/07 13:04:32 roberto Exp $ 2 | -- See Copyright Notice in file all.lua 3 | 4 | if T==nil then 5 | (Message or print)('\n >>> testC not active: skipping opcode tests <<<\n') 6 | return 7 | end 8 | print "testing code generation and optimizations" 9 | 10 | 11 | -- this code gave an error for the code checker 12 | do 13 | local function f (a) 14 | for k,v,w in a do end 15 | end 16 | end 17 | 18 | 19 | -- testing reuse in constant table 20 | local function checkKlist (func, list) 21 | local k = T.listk(func) 22 | assert(#k == #list) 23 | for i = 1, #k do 24 | assert(k[i] == list[i] and math.type(k[i]) == math.type(list[i])) 25 | end 26 | end 27 | 28 | local function foo () 29 | local a 30 | a = 3; 31 | a = 0; a = 0.0; a = -7 + 7 32 | a = 3.78/4; a = 3.78/4 33 | a = -3.78/4; a = 3.78/4; a = -3.78/4 34 | a = -3.79/4; a = 0.0; a = -0; 35 | a = 3; a = 3.0; a = 3; a = 3.0 36 | end 37 | 38 | checkKlist(foo, {3, 0, 0.0, 3.78/4, -3.78/4, -3.79/4, 3.0}) 39 | 40 | 41 | -- testing opcodes 42 | 43 | function check (f, ...) 44 | local arg = {...} 45 | local c = T.listcode(f) 46 | for i=1, #arg do 47 | -- print(arg[i], c[i]) 48 | assert(string.find(c[i], '- '..arg[i]..' *%d')) 49 | end 50 | assert(c[#arg+2] == nil) 51 | end 52 | 53 | 54 | function checkequal (a, b) 55 | a = T.listcode(a) 56 | b = T.listcode(b) 57 | for i = 1, #a do 58 | a[i] = string.gsub(a[i], '%b()', '') -- remove line number 59 | b[i] = string.gsub(b[i], '%b()', '') -- remove line number 60 | assert(a[i] == b[i]) 61 | end 62 | end 63 | 64 | 65 | -- some basic instructions 66 | check(function () 67 | (function () end){f()} 68 | end, 'CLOSURE', 'NEWTABLE', 'GETTABUP', 'CALL', 'SETLIST', 'CALL', 'RETURN') 69 | 70 | 71 | -- sequence of LOADNILs 72 | check(function () 73 | local a,b,c 74 | local d; local e; 75 | local f,g,h; 76 | d = nil; d=nil; b=nil; a=nil; c=nil; 77 | end, 'LOADNIL', 'RETURN') 78 | 79 | check(function () 80 | local a,b,c,d = 1,1,1,1 81 | d=nil;c=nil;b=nil;a=nil 82 | end, 'LOADK', 'LOADK', 'LOADK', 'LOADK', 'LOADNIL', 'RETURN') 83 | 84 | do 85 | local a,b,c,d = 1,1,1,1 86 | d=nil;c=nil;b=nil;a=nil 87 | assert(a == nil and b == nil and c == nil and d == nil) 88 | end 89 | 90 | 91 | -- single return 92 | check (function (a,b,c) return a end, 'RETURN') 93 | 94 | 95 | -- infinite loops 96 | check(function () while true do local a = -1 end end, 97 | 'LOADK', 'JMP', 'RETURN') 98 | 99 | check(function () while 1 do local a = -1 end end, 100 | 'LOADK', 'JMP', 'RETURN') 101 | 102 | check(function () repeat local x = 1 until true end, 103 | 'LOADK', 'RETURN') 104 | 105 | 106 | -- concat optimization 107 | check(function (a,b,c,d) return a..b..c..d end, 108 | 'MOVE', 'MOVE', 'MOVE', 'MOVE', 'CONCAT', 'RETURN') 109 | 110 | -- not 111 | check(function () return not not nil end, 'LOADBOOL', 'RETURN') 112 | check(function () return not not false end, 'LOADBOOL', 'RETURN') 113 | check(function () return not not true end, 'LOADBOOL', 'RETURN') 114 | check(function () return not not 1 end, 'LOADBOOL', 'RETURN') 115 | 116 | -- direct access to locals 117 | check(function () 118 | local a,b,c,d 119 | a = b*2 120 | c[2], a[b] = -((a + d/2 - a[b]) ^ a.x), b 121 | end, 122 | 'LOADNIL', 123 | 'MUL', 124 | 'DIV', 'ADD', 'GETTABLE', 'SUB', 'GETTABLE', 'POW', 125 | 'UNM', 'SETTABLE', 'SETTABLE', 'RETURN') 126 | 127 | 128 | -- direct access to constants 129 | check(function () 130 | local a,b 131 | a.x = 3.2 132 | a.x = b 133 | a[b] = 'x' 134 | end, 135 | 'LOADNIL', 'SETTABLE', 'SETTABLE', 'SETTABLE', 'RETURN') 136 | 137 | check(function () 138 | local a,b 139 | a = 1 - a 140 | b = 1/a 141 | b = 5-4 142 | end, 143 | 'LOADNIL', 'SUB', 'DIV', 'LOADK', 'RETURN') 144 | 145 | check(function () 146 | local a,b 147 | a[true] = false 148 | end, 149 | 'LOADNIL', 'SETTABLE', 'RETURN') 150 | 151 | 152 | -- constant folding 153 | local function checkK (func, val) 154 | check(func, 'LOADK', 'RETURN') 155 | local k = T.listk(func) 156 | assert(#k == 1 and k[1] == val and math.type(k[1]) == math.type(val)) 157 | assert(func() == val) 158 | end 159 | checkK(function () return 0.0 end, 0.0) 160 | checkK(function () return 0 end, 0) 161 | checkK(function () return -0//1 end, 0) 162 | checkK(function () return 3^-1 end, 1/3) 163 | checkK(function () return (1 + 1)^(50 + 50) end, 2^100) 164 | checkK(function () return (-2)^(31 - 2) end, -0x20000000 + 0.0) 165 | checkK(function () return (-3^0 + 5) // 3.0 end, 1.0) 166 | checkK(function () return -3 % 5 end, 2) 167 | checkK(function () return -((2.0^8 + -(-1)) % 8)/2 * 4 - 3 end, -5.0) 168 | checkK(function () return -((2^8 + -(-1)) % 8)//2 * 4 - 3 end, -7.0) 169 | checkK(function () return 0xF0.0 | 0xCC.0 ~ 0xAA & 0xFD end, 0xF4) 170 | checkK(function () return ~(~0xFF0 | 0xFF0) end, 0) 171 | checkK(function () return ~~-100024.0 end, -100024) 172 | checkK(function () return ((100 << 6) << -4) >> 2 end, 100) 173 | 174 | 175 | -- no foldings 176 | check(function () return -0.0 end, 'LOADK', 'UNM', 'RETURN') 177 | check(function () return 3/0 end, 'DIV', 'RETURN') 178 | check(function () return 0%0 end, 'MOD', 'RETURN') 179 | check(function () return -4//0 end, 'IDIV', 'RETURN') 180 | 181 | -- bug in constant folding for 5.1 182 | check(function () return -nil end, 'LOADNIL', 'UNM', 'RETURN') 183 | 184 | 185 | check(function () 186 | local a,b,c 187 | b[c], a = c, b 188 | b[a], a = c, b 189 | a, b = c, a 190 | a = a 191 | end, 192 | 'LOADNIL', 193 | 'MOVE', 'MOVE', 'SETTABLE', 194 | 'MOVE', 'MOVE', 'MOVE', 'SETTABLE', 195 | 'MOVE', 'MOVE', 'MOVE', 196 | -- no code for a = a 197 | 'RETURN') 198 | 199 | 200 | -- x == nil , x ~= nil 201 | checkequal(function () if (a==nil) then a=1 end; if a~=nil then a=1 end end, 202 | function () if (a==9) then a=1 end; if a~=9 then a=1 end end) 203 | 204 | check(function () if a==nil then a='a' end end, 205 | 'GETTABUP', 'EQ', 'JMP', 'SETTABUP', 'RETURN') 206 | 207 | -- de morgan 208 | checkequal(function () local a; if not (a or b) then b=a end end, 209 | function () local a; if (not a and not b) then b=a end end) 210 | 211 | checkequal(function (l) local a; return 0 <= a and a <= l end, 212 | function (l) local a; return not (not(a >= 0) or not(a <= l)) end) 213 | 214 | 215 | -- if-goto optimizations 216 | check(function (a, b, c, d, e) 217 | if a == b then goto l1 218 | elseif a == c then goto l2 219 | elseif a == d then goto l2 220 | else if a == e then goto l3 221 | else goto l3 222 | end 223 | end 224 | ::l1:: ::l2:: ::l3:: ::l4:: 225 | end, 'EQ', 'JMP', 'EQ', 'JMP', 'EQ', 'JMP', 'EQ', 'JMP', 'JMP', 'RETURN') 226 | 227 | checkequal( 228 | function (a) while a < 10 do a = a + 1 end end, 229 | function (a) ::L2:: if not(a < 10) then goto L1 end; a = a + 1; 230 | goto L2; ::L1:: end 231 | ) 232 | 233 | checkequal( 234 | function (a) while a < 10 do a = a + 1 end end, 235 | function (a) while true do if not(a < 10) then break end; a = a + 1; end end 236 | ) 237 | 238 | print 'OK' 239 | 240 | -------------------------------------------------------------------------------- /ceres-data/data/units/miscgame.txt: -------------------------------------------------------------------------------- 1 | [Misc] 2 | MagicImmunesResistDamage=1 3 | MagicImmunesResistThorns=0 4 | MagicImmunesResistLeech=0 5 | MagicImmunesResistUltimates=0 6 | CycloneStasis=0 7 | DepCheckAlias=1 8 | MassTeleportCluster=1 9 | DarkSummoningCluster=1 10 | TownPortalCluster=1 11 | AmuletOfRecallCluster=1 12 | MorphLandClosest=1 13 | MorphAlternateDisable=0 14 | InvulnSummonDispelDamage=0 15 | ConstructionDamageRefundPenalty=1 16 | UpgradeDamageRefundPenalty=0 17 | AllowMultiBounce=0 18 | EnsnareIsMagic=0 19 | WebIsMagic=0 20 | IllusionsGetAttackBonus=1 21 | IllusionsGetAttackSpeedBonus=1 22 | IllusionsGetMoveSpeedBonus=1 23 | IllusionsGetDefenseBonus=1 24 | IllusionsCanRestoreLife=1 25 | IllusionsCanRestoreMana=1 26 | IllusionsBestowAuras=0 27 | IllusionsGetAutocast=0 28 | InvisibleUnitsBestowAuras=0 29 | PolymorphedUnitsBestowAuras=0 30 | BurrowedUnitsBestowAuras=0 31 | AnimatedUnitsBestowAuras=0 32 | FlyingHeroesBestowAuras=0 33 | MoveSpeedBonusesStack=0 34 | DrainUsesEtheralBonus=0 35 | DrainTransfersLife=1 36 | DrainTransfersMana=1 37 | DrainGivesBonusLife=1 38 | DrainGivesBonusMana=1 39 | EtherealDamageBonusAlly=0 40 | CanDeactivateAvatar=0 41 | CanDeactivateAvengerForm=0 42 | CanDeactivateBarkskin=1 43 | CanDeactivateBearForm=1 44 | CanDeactivateBladestorm=0 45 | CanDeactivateBurrow=1 46 | CanDeactivateCallToArms=1 47 | CanDeactivateChemicalRage=0 48 | CanDeactivateCorporealForm=1 49 | CanDeactivateDefend=1 50 | CanDeactivateDivineShield=0 51 | CanDeactivateImmolation=1 52 | CanDeactivateManaFlare=0 53 | CanDeactivateManaShield=1 54 | CanDeactivateMetamorphosis=0 55 | CanDeactivateRavenForm=1 56 | CanDeactivateRoboGoblin=1 57 | CanDeactivateStoneForm=1 58 | CanDeactivateSubmerge=1 59 | CanDeactivateWindWalk=0 60 | RelativeUpgradeCost=0 61 | DefendDeflection=1 62 | ItemSaleAggroRange=0 63 | UnitSaleAggroRange=600 64 | AbilSaleAggroRange=0 65 | AbolishMagicDispelSmart=1 66 | UpgradeInProgressIdChange=0 67 | 68 | GlobalExperience=1 69 | MaxLevelHeroesDrainExp=1 70 | BuildingKillsGiveExp=0 71 | 72 | DisplayEnemyInventory=1 73 | DisplayBuildingStatus=0 74 | 75 | // Max revival cost of a hero 76 | HeroMaxReviveCostGold=700 77 | HeroMaxReviveCostLumber=0 78 | HeroMaxReviveTime=150 79 | 80 | // Max awaken (tavern) cost of a hero 81 | HeroMaxAwakenCostGold=1400 82 | HeroMaxAwakenCostLumber=350 83 | 84 | // Hero Revive & Awaken returning stats 85 | HeroReviveManaStart=1 86 | HeroReviveManaFactor=0.0 87 | HeroReviveLifeFactor=1.0 88 | HeroAwakenManaStart=0 89 | HeroAwakenManaFactor=0.0 90 | HeroAwakenLifeFactor=0.5 91 | 92 | // the distance at which heroes still gain XP for dying units 93 | HeroExpRange=1200 94 | 95 | // factors for calculating the cost, time to revive a hero 96 | // goldRevivalCost = originalCost * (ReviveBaseFactor + (ReviveLevelFactor*(level-1))) 97 | // but not exceeding originalCost * ReviveMaxFactor 98 | // lumberRevivalCost = originalCost * (ReviveBaseLumberFactor + (ReviveLumberLevelFactor*(level-1))) 99 | // but not exceeding originalCost * ReviveMaxFactor 100 | // revivalTime = originalTime * level * ReviveTimeFactor 101 | // but not exceeding originalTime * ReviveMaxTimeFactor 102 | ReviveBaseFactor=.40 103 | ReviveLevelFactor=.10 104 | ReviveBaseLumberFactor=0 105 | ReviveLumberLevelFactor=0 106 | ReviveMaxFactor=4.0 107 | ReviveTimeFactor=0.65 108 | ReviveMaxTimeFactor=2.0 109 | 110 | AwakenBaseFactor=.80 111 | AwakenLevelFactor=.20 112 | AwakenBaseLumberFactor=.80 113 | AwakenLumberLevelFactor=.20 114 | AwakenMaxFactor=8.0 115 | 116 | // Note: Maps saved with a Reign of Chaos version of the editor will use 25 for the 117 | // min unit speed value since it wasn't increased to 150 until Frozen Throne. 118 | MinUnitSpeed=150 119 | MaxUnitSpeed=400 120 | MinBldgSpeed=25 121 | MaxBldgSpeed=400 122 | 123 | FrostMoveSpeedDecrease=0.5 124 | FrostAttackSpeedDecrease=0.25 125 | 126 | // Experience & Level Information 127 | // 128 | MaxHeroLevel=10 129 | MaxUnitLevel=20 130 | NeedHeroXP=200 131 | GrantHeroXP=100,120,160,220,300 132 | GrantNormalXP=25 133 | HeroFactorXP=80,70,60,50,0 134 | SummonedKillFactor=0.5 135 | StrAttackBonus=1.0 136 | StrHitPointBonus=25 137 | StrRegenBonus=0.05 138 | IntManaBonus=15 139 | IntRegenBonus=0.05 140 | AgiDefenseBonus=0.30 141 | AgiDefenseBase=-2 142 | AgiMoveBonus=0 143 | AgiAttackSpeedBonus=0.02 144 | 145 | // Formula constants for hero levels beyond the tables... 146 | // The three constants are used to define a table as: 147 | // 148 | // f(x) = A*f(x-1) + B*x + C 149 | // 150 | // where A,B,C are the constants given below 151 | // 152 | NeedHeroXPFormulaA=1 153 | NeedHeroXPFormulaB=100 154 | NeedHeroXPFormulaC=0 155 | GrantHeroXPFormulaA=1 156 | GrantHeroXPFormulaB=0 157 | GrantHeroXPFormulaC=100 158 | GrantNormalXPFormulaA=1 159 | GrantNormalXPFormulaB=5 160 | GrantNormalXPFormulaC=5 161 | 162 | // Hero ability level skip 163 | // The required hero level for a given ability level is: 164 | // 165 | // baseReq + levelSkip*abilityLevel 166 | // 167 | 168 | HeroAbilityLevelSkip=2 169 | 170 | // Hero Inventory Items 171 | DropItemRange=100 172 | GiveItemRange=150 173 | PickupItemRange=150 174 | PawnItemRange=300 175 | PawnItemRate=0.50 176 | 177 | // combat related entries 178 | CallForHelp=600 179 | CreepCallForHelp=600 180 | DefenseArmor=0.06 181 | 182 | // Damage bonus lists: SMALL, MEDIUM, LARGE, FORT, NORMAL, HERO, DIVINE, NONE 183 | DamageBonusNormal=1.00,1.50,1.00,0.70,1.00,1.00,0.05,1.00 184 | DamageBonusPierce=2.00,0.75,1.00,0.35,1.00,0.50,0.05,1.50 185 | DamageBonusSiege=1.00,0.50,1.00,1.50,1.00,0.50,0.05,1.50 186 | DamageBonusMagic=1.25,0.75,2.00,0.35,1.00,0.50,0.05,1.00 187 | DamageBonusChaos=1.00,1.00,1.00,1.00,1.00,1.00,1.00,1.00 188 | DamageBonusSpells=1.00,1.00,1.00,1.00,1.00,0.70,0.05,1.00 189 | DamageBonusHero=1.00,1.00,1.00,0.50,1.00,1.00,0.05,1.00 190 | 191 | // Ethereal Damage bonus values: NORMAL, PIERCE, SIEGE, MAGIC, CHAOS, SPELLS, HERO 192 | EtherealDamageBonus=0,0,0,1.66,0,1.66,0 193 | EtherealHealBonus=1.66 194 | 195 | // After a unit has strayed 'GuardDistance' from where it started, 196 | // that unit begins thinking about heading back to its start position. 197 | // If the unit has move 'GuardDistance' away from "home" at any time 198 | // and spends 'GuardReturnTime' seconds chasing a target without getting 199 | // attacked by anyone, the unit indeed turns around and heads home. 200 | // If a creep goes beyond 'MaxGuardDistance' then it always returns home 201 | // regardless of who's attacking it. 202 | // 203 | GuardDistance=600 204 | MaxGuardDistance=1000 205 | GuardReturnTime=5.0 206 | 207 | // refund rates 208 | ConstructionRefundRate=0.75 // for cancelled construction 209 | ResearchRefundRate=1.0 // for cancelled research of spells or unit improvements 210 | ReviveRefundRate=1.0 // for cancelled hero revival 211 | TrainRefundRate=1.0 // currently applied regardless of location in queue 212 | UpgradeRefundRate=0.75 // for a cancelled structure upgrade 213 | 214 | ConstructionLifeDrainRate=10.0 // hp per second drained when building construction is halted 215 | 216 | MissDamageReduction=0.5 -------------------------------------------------------------------------------- /ceres-formats/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rlua::prelude::*; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use anyhow::anyhow; 5 | use bitflags::bitflags; 6 | 7 | pub mod parser { 8 | pub mod slk; 9 | pub mod crlf; 10 | pub mod profile; 11 | pub mod w3obj; 12 | } 13 | 14 | pub mod error; 15 | pub mod metadata; 16 | pub mod object; 17 | pub mod objectstore; 18 | 19 | #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] 20 | /// A WC3 object id, which is conceptually a simple 32-bit integer, 21 | /// but often represented as a 4-char ASCII string. 22 | /// 23 | /// Provides conversion to/from byte arrays for this reason. 24 | pub struct ObjectId { 25 | id: u32, 26 | } 27 | 28 | impl ObjectId { 29 | pub fn new(id: u32) -> ObjectId { 30 | ObjectId { id } 31 | } 32 | 33 | pub fn from_bytes(bytes: &[u8]) -> Option { 34 | if bytes.len() != 4 { 35 | None 36 | } else { 37 | let mut value = 0; 38 | for i in bytes { 39 | value <<= 8; 40 | value += u32::from(*i); 41 | } 42 | 43 | Some(ObjectId { id: value }) 44 | } 45 | } 46 | 47 | pub fn to_u32(self) -> u32 { 48 | self.id 49 | } 50 | 51 | pub fn to_string(self) -> Option { 52 | let bytes: Vec = (&self.id.to_be_bytes()).iter().copied().collect(); 53 | String::from_utf8(bytes).ok() 54 | } 55 | } 56 | 57 | impl std::fmt::Debug for ObjectId { 58 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 59 | if self.id == 0 { 60 | write!(f, "ObjectID(NULL)") 61 | } else { 62 | let bytes = self.id.to_be_bytes(); 63 | let pretty = std::str::from_utf8(&bytes).ok(); 64 | 65 | if let Some(pretty) = pretty { 66 | write!(f, "ObjectID({})", pretty) 67 | } else { 68 | write!(f, "ObjectID({})", self.id) 69 | } 70 | } 71 | } 72 | } 73 | 74 | impl std::fmt::Display for ObjectId { 75 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 76 | if self.id == 0 { 77 | write!(f, "NULL") 78 | } else { 79 | let bytes = self.id.to_be_bytes(); 80 | let pretty = String::from_utf8_lossy(&bytes); 81 | 82 | write!(f, "{}", pretty) 83 | } 84 | } 85 | } 86 | 87 | impl From for ObjectId { 88 | fn from(other: u32) -> Self { 89 | Self { id: other } 90 | } 91 | } 92 | 93 | impl<'lua> FromLua<'lua> for ObjectId { 94 | fn from_lua(value: LuaValue<'lua>, _ctx: LuaContext<'lua>) -> Result { 95 | match value { 96 | LuaValue::String(value) => ObjectId::from_bytes(value.as_bytes()).ok_or_else(|| { 97 | LuaError::FromLuaConversionError { 98 | from: "string", 99 | to: "objectid", 100 | message: Some("invalid byte sequence for object id".into()), 101 | } 102 | }), 103 | LuaValue::Integer(value) => Ok(ObjectId::new(value as u32)), 104 | _ => Err(LuaError::external(anyhow!( 105 | "only strings and integers can be converted to object ids" 106 | ))), 107 | } 108 | } 109 | } 110 | 111 | impl<'lua> ToLua<'lua> for ObjectId { 112 | fn to_lua(self, ctx: LuaContext<'lua>) -> Result, LuaError> { 113 | if let Some(value) = self.to_string() { 114 | Ok(LuaValue::String(ctx.create_string(&value)?)) 115 | } else { 116 | Ok(LuaValue::Integer(self.id as i64)) 117 | } 118 | } 119 | } 120 | 121 | #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)] 122 | /// Represents a WC3 primitive data type. 123 | /// 124 | /// WC3 field metadata specifies many more types than these, 125 | /// but most of them collapse to strings. 126 | pub enum ValueType { 127 | Int, 128 | Real, 129 | Unreal, 130 | String, 131 | } 132 | 133 | impl ValueType { 134 | /// Collapse a WC3 data type into a primitive value type. 135 | /// 136 | /// Mostly supposed to be used with data types specified in SLKs. 137 | pub fn new(input: &str) -> ValueType { 138 | match input { 139 | "real" => ValueType::Real, 140 | "unreal" => ValueType::Unreal, 141 | "int" | "bool" | "attackBits" | "deathType" | "defenseTypeInt" | "detectionType" 142 | | "teamColor" | "morphFlags" | "silenceFlags" | "stackFlags" | "interactionFlags" 143 | | "pickFlags" | "versionFlags" | "fullFlags" | "channelType" | "channelFlags" 144 | | "spellDetail" | "techAvail" => ValueType::Int, 145 | _ => ValueType::String, 146 | } 147 | } 148 | } 149 | 150 | bitflags! { 151 | #[derive(Serialize, Deserialize)] 152 | /// Represents a WC3 object type. 153 | pub struct ObjectKind: u32 { 154 | const ABILITY = 0x1; 155 | const BUFF = 0x2; 156 | const DESTRUCTABLE = 0x4; 157 | const MISC = 0x8; 158 | const UNIT = 0x10; 159 | const UPGRADE = 0x20; 160 | const ITEM = 0x40; 161 | const DOODAD = 0x80; 162 | } 163 | } 164 | 165 | impl ObjectKind { 166 | /// Converts an extension of a WC3 object data file 167 | /// to its corresponding object type. 168 | pub fn from_ext(ext: &str) -> ObjectKind { 169 | match ext { 170 | "w3u" => ObjectKind::UNIT, 171 | "w3a" => ObjectKind::ABILITY, 172 | "w3t" => ObjectKind::ITEM, 173 | "w3b" => ObjectKind::DESTRUCTABLE, 174 | "w3d" => ObjectKind::DOODAD, 175 | "w3h" => ObjectKind::BUFF, 176 | "w3q" => ObjectKind::UPGRADE, 177 | _ => ObjectKind::empty(), 178 | } 179 | } 180 | 181 | pub fn to_ext(self) -> &'static str { 182 | match self { 183 | ObjectKind::UNIT => "w3u", 184 | ObjectKind::ABILITY => "w3a", 185 | ObjectKind::ITEM => "w3t", 186 | ObjectKind::DESTRUCTABLE => "w3b", 187 | ObjectKind::DOODAD => "w3d", 188 | ObjectKind::BUFF => "w3h", 189 | ObjectKind::UPGRADE => "w3q", 190 | _ => "none", 191 | } 192 | } 193 | 194 | /// Returns true if the object type is capable 195 | /// of using data/leveled fields instead of just regular fields. 196 | /// 197 | /// This affects the layout of WC3 object data files. 198 | pub fn is_data_type(self) -> bool { 199 | match self { 200 | ObjectKind::DOODAD | ObjectKind::ABILITY | ObjectKind::UPGRADE => true, 201 | _ => false, 202 | } 203 | } 204 | 205 | pub fn to_typestr(self) -> &'static str { 206 | match self { 207 | ObjectKind::UNIT => "unit", 208 | ObjectKind::ABILITY => "ability", 209 | ObjectKind::ITEM => "item", 210 | ObjectKind::DESTRUCTABLE => "destructable", 211 | ObjectKind::DOODAD => "doodad", 212 | ObjectKind::BUFF => "buff", 213 | ObjectKind::UPGRADE => "upgrade", 214 | _ => "none", 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /ceres-data/data/units/abilitybuffmetadata.slk: -------------------------------------------------------------------------------- 1 | ID;PWXL;N;E 2 | B;X23;Y28;D0 3 | C;X1;Y1;K"ID" 4 | C;X2;K"field" 5 | C;X3;K"slk" 6 | C;X4;K"index" 7 | C;X5;K"category" 8 | C;X6;K"displayName" 9 | C;X7;K"sort" 10 | C;X8;K"type" 11 | C;X9;K"changeFlags" 12 | C;X10;K"importType" 13 | C;X11;K"stringExt" 14 | C;X12;K"caseSens" 15 | C;X13;K"canBeEmpty" 16 | C;X14;K"minVal" 17 | C;X15;K"maxVal" 18 | C;X16;K"forceNonNeg" 19 | C;X17;K"version" 20 | C;X18;K"section" 21 | C;X1;Y2;K"fnam" 22 | C;X2;K"EditorName" 23 | C;X3;K"Profile" 24 | C;X4;K-1 25 | C;X5;K"text" 26 | C;X6;K"WESTRING_FEVAL_FNAM" 27 | C;X8;K"string" 28 | C;X9;K"t" 29 | C;X11;K1 30 | C;X12;K1 31 | C;X13;K1 32 | C;X15;K"TTName" 33 | C;X16;K0 34 | C;X17;K1 35 | C;X1;Y3;K"fnsf" 36 | C;X2;K"EditorSuffix" 37 | C;X3;K"Profile" 38 | C;X4;K-1 39 | C;X5;K"text" 40 | C;X6;K"WESTRING_FEVAL_FNSF" 41 | C;X8;K"string" 42 | C;X9;K"t" 43 | C;X11;K1 44 | C;X12;K1 45 | C;X13;K1 46 | C;X15;K50 47 | C;X16;K0 48 | C;X17;K1 49 | C;X1;Y4;K"ftip" 50 | C;X2;K"Bufftip" 51 | C;X3;K"Profile" 52 | C;X4;K-1 53 | C;X5;K"text" 54 | C;X6;K"WESTRING_FEVAL_FTIP" 55 | C;X8;K"string" 56 | C;X9;K"t" 57 | C;X11;K1 58 | C;X12;K1 59 | C;X13;K1 60 | C;X15;K"TTDesc" 61 | C;X16;K0 62 | C;X17;K1 63 | C;X1;Y5;K"fube" 64 | C;X2;K"Buffubertip" 65 | C;X3;K"Profile" 66 | C;X4;K-1 67 | C;X5;K"text" 68 | C;X6;K"WESTRING_FEVAL_FUBE" 69 | C;X8;K"string" 70 | C;X9;K"t" 71 | C;X11;K1 72 | C;X12;K1 73 | C;X13;K1 74 | C;X15;K"TTUber" 75 | C;X16;K0 76 | C;X17;K1 77 | C;X1;Y6;K"feff" 78 | C;X2;K"isEffect" 79 | C;X3;K"AbilityBuffData" 80 | C;X4;K-1 81 | C;X5;K"stats" 82 | C;X6;K"WESTRING_FEVAL_FEFF" 83 | C;X8;K"bool" 84 | C;X9;K"c" 85 | C;X11;K0 86 | C;X12;K0 87 | C;X13;K0 88 | C;X16;K0 89 | C;X17;K1 90 | C;X23;K1 91 | C;X1;Y7;K"frac" 92 | C;X2;K"race" 93 | C;X3;K"AbilityBuffData" 94 | C;X4;K-1 95 | C;X5;K"stats" 96 | C;X6;K"WESTRING_FEVAL_FRAC" 97 | C;X8;K"unitRace" 98 | C;X9;K"c" 99 | C;X11;K0 100 | C;X12;K0 101 | C;X13;K0 102 | C;X16;K0 103 | C;X17;K1 104 | C;X23;K1 105 | C;X1;Y8;K"fart" 106 | C;X2;K"Buffart" 107 | C;X3;K"Profile" 108 | C;X4;K-1 109 | C;X5;K"art" 110 | C;X6;K"WESTRING_FEVAL_FART" 111 | C;X8;K"icon" 112 | C;X9;K"i" 113 | C;X10;K"Image" 114 | C;X11;K0 115 | C;X12;K1 116 | C;X13;K1 117 | C;X16;K0 118 | C;X17;K1 119 | C;X1;Y9;K"ftat" 120 | C;X2;K"TargetArt" 121 | C;X3;K"Profile" 122 | C;X4;K-1 123 | C;X5;K"art" 124 | C;X6;K"WESTRING_FEVAL_FTAT" 125 | C;X8;K"modelList" 126 | C;X11;K0 127 | C;X12;K1 128 | C;X13;K1 129 | C;X16;K0 130 | C;X17;K1 131 | C;X23;K1 132 | C;X1;Y10;K"fsat" 133 | C;X2;K"SpecialArt" 134 | C;X3;K"Profile" 135 | C;X4;K-1 136 | C;X5;K"art" 137 | C;X6;K"WESTRING_FEVAL_FSAT" 138 | C;X8;K"modelList" 139 | C;X11;K0 140 | C;X12;K1 141 | C;X13;K1 142 | C;X16;K0 143 | C;X17;K1 144 | C;X23;K1 145 | C;X1;Y11;K"feat" 146 | C;X2;K"EffectArt" 147 | C;X3;K"Profile" 148 | C;X4;K-1 149 | C;X5;K"art" 150 | C;X6;K"WESTRING_FEVAL_FEAT" 151 | C;X8;K"modelList" 152 | C;X11;K0 153 | C;X12;K1 154 | C;X13;K1 155 | C;X16;K0 156 | C;X17;K1 157 | C;X23;K1 158 | C;X1;Y12;K"flig" 159 | C;X2;K"LightningEffect" 160 | C;X3;K"Profile" 161 | C;X4;K-1 162 | C;X5;K"art" 163 | C;X6;K"WESTRING_FEVAL_FLIG" 164 | C;X8;K"lightningEffect" 165 | C;X11;K0 166 | C;X12;K1 167 | C;X13;K1 168 | C;X16;K0 169 | C;X17;K1 170 | C;X1;Y13;K"fmat" 171 | C;X2;K"Missileart" 172 | C;X3;K"Profile" 173 | C;X4;K-1 174 | C;X5;K"art" 175 | C;X6;K"WESTRING_FEVAL_FMAT" 176 | C;X8;K"modelList" 177 | C;X11;K0 178 | C;X12;K1 179 | C;X13;K1 180 | C;X16;K0 181 | C;X17;K1 182 | C;X23;K1 183 | C;X1;Y14;K"fmsp" 184 | C;X2;K"Missilespeed" 185 | C;X3;K"Profile" 186 | C;X4;K-1 187 | C;X5;K"art" 188 | C;X6;K"WESTRING_FEVAL_FMSP" 189 | C;X8;K"int" 190 | C;X11;K0 191 | C;X12;K0 192 | C;X13;K0 193 | C;X14;K0 194 | C;X15;K10000 195 | C;X16;K0 196 | C;X17;K1 197 | C;X23;K1 198 | C;X1;Y15;K"fmac" 199 | C;X2;K"Missilearc" 200 | C;X3;K"Profile" 201 | C;X4;K-1 202 | C;X5;K"art" 203 | C;X6;K"WESTRING_FEVAL_FMAC" 204 | C;X8;K"unreal" 205 | C;X11;K0 206 | C;X12;K0 207 | C;X13;K0 208 | C;X14;K0 209 | C;X15;K1 210 | C;X16;K0 211 | C;X17;K1 212 | C;X23;K1 213 | C;X1;Y16;K"fmho" 214 | C;X2;K"MissileHoming" 215 | C;X3;K"Profile" 216 | C;X4;K-1 217 | C;X5;K"art" 218 | C;X6;K"WESTRING_FEVAL_FMHO" 219 | C;X8;K"bool" 220 | C;X11;K0 221 | C;X12;K0 222 | C;X13;K0 223 | C;X16;K0 224 | C;X17;K1 225 | C;X23;K1 226 | C;X1;Y17;K"ftac" 227 | C;X2;K"Targetattachcount" 228 | C;X3;K"Profile" 229 | C;X4;K-1 230 | C;X5;K"art" 231 | C;X6;K"WESTRING_FEVAL_FTAC" 232 | C;X8;K"int" 233 | C;X11;K0 234 | C;X12;K0 235 | C;X13;K0 236 | C;X14;K0 237 | C;X15;K6 238 | C;X16;K0 239 | C;X17;K1 240 | C;X23;K1 241 | C;X1;Y18;K"fta0" 242 | C;X2;K"Targetattach" 243 | C;X3;K"Profile" 244 | C;X4;K-1 245 | C;X5;K"art" 246 | C;X6;K"WESTRING_FEVAL_FTA0" 247 | C;X8;K"stringList" 248 | C;X11;K0 249 | C;X12;K1 250 | C;X13;K0 251 | C;X15;K32 252 | C;X16;K0 253 | C;X17;K1 254 | C;X23;K1 255 | C;X1;Y19;K"fta1" 256 | C;X2;K"Targetattach1" 257 | C;X3;K"Profile" 258 | C;X4;K-1 259 | C;X5;K"art" 260 | C;X6;K"WESTRING_FEVAL_FTA1" 261 | C;X8;K"stringList" 262 | C;X11;K0 263 | C;X12;K1 264 | C;X13;K0 265 | C;X15;K32 266 | C;X16;K0 267 | C;X17;K1 268 | C;X23;K1 269 | C;X1;Y20;K"fta2" 270 | C;X2;K"Targetattach2" 271 | C;X3;K"Profile" 272 | C;X4;K-1 273 | C;X5;K"art" 274 | C;X6;K"WESTRING_FEVAL_FTA2" 275 | C;X8;K"stringList" 276 | C;X11;K0 277 | C;X12;K1 278 | C;X13;K0 279 | C;X15;K32 280 | C;X16;K0 281 | C;X17;K1 282 | C;X23;K1 283 | C;X1;Y21;K"fta3" 284 | C;X2;K"Targetattach3" 285 | C;X3;K"Profile" 286 | C;X4;K-1 287 | C;X5;K"art" 288 | C;X6;K"WESTRING_FEVAL_FTA3" 289 | C;X8;K"stringList" 290 | C;X11;K0 291 | C;X12;K1 292 | C;X13;K0 293 | C;X15;K32 294 | C;X16;K0 295 | C;X17;K1 296 | C;X23;K1 297 | C;X1;Y22;K"fta4" 298 | C;X2;K"Targetattach4" 299 | C;X3;K"Profile" 300 | C;X4;K-1 301 | C;X5;K"art" 302 | C;X6;K"WESTRING_FEVAL_FTA4" 303 | C;X8;K"stringList" 304 | C;X11;K0 305 | C;X12;K1 306 | C;X13;K0 307 | C;X15;K32 308 | C;X16;K0 309 | C;X17;K1 310 | C;X23;K1 311 | C;X1;Y23;K"fta5" 312 | C;X2;K"Targetattach5" 313 | C;X3;K"Profile" 314 | C;X4;K-1 315 | C;X5;K"art" 316 | C;X6;K"WESTRING_FEVAL_FTA5" 317 | C;X8;K"stringList" 318 | C;X11;K0 319 | C;X12;K1 320 | C;X13;K0 321 | C;X15;K32 322 | C;X16;K0 323 | C;X17;K1 324 | C;X23;K1 325 | C;X1;Y24;K"feft" 326 | C;X2;K"Effectattach" 327 | C;X3;K"Profile" 328 | C;X4;K-1 329 | C;X5;K"art" 330 | C;X6;K"WESTRING_FEVAL_FEFT" 331 | C;X8;K"stringList" 332 | C;X11;K0 333 | C;X12;K1 334 | C;X13;K0 335 | C;X15;K32 336 | C;X16;K0 337 | C;X17;K1 338 | C;X23;K1 339 | C;X1;Y25;K"fspt" 340 | C;X2;K"Specialattach" 341 | C;X3;K"Profile" 342 | C;X4;K-1 343 | C;X5;K"art" 344 | C;X6;K"WESTRING_FEVAL_FSPT" 345 | C;X8;K"stringList" 346 | C;X11;K0 347 | C;X12;K1 348 | C;X13;K0 349 | C;X15;K32 350 | C;X16;K0 351 | C;X17;K1 352 | C;X23;K1 353 | C;X1;Y26;K"fefs" 354 | C;X2;K"Effectsound" 355 | C;X3;K"Profile" 356 | C;X4;K-1 357 | C;X5;K"sound" 358 | C;X6;K"WESTRING_FEVAL_FEFS" 359 | C;X8;K"soundLabel" 360 | C;X11;K0 361 | C;X12;K1 362 | C;X13;K1 363 | C;X16;K0 364 | C;X17;K1 365 | C;X23;K1 366 | C;X1;Y27;K"fefl" 367 | C;X2;K"Effectsoundlooped" 368 | C;X3;K"Profile" 369 | C;X4;K-1 370 | C;X5;K"sound" 371 | C;X6;K"WESTRING_FEVAL_FEFL" 372 | C;X8;K"soundLabel" 373 | C;X11;K0 374 | C;X12;K1 375 | C;X13;K1 376 | C;X16;K0 377 | C;X17;K1 378 | C;X23;K1 379 | C;X1;Y28;K"fspd" 380 | C;X2;K"Spelldetail" 381 | C;X3;K"Profile" 382 | C;X4;K-1 383 | C;X5;K"art" 384 | C;X6;K"WESTRING_FEVAL_FSPD" 385 | C;X8;K"spellDetail" 386 | C;X11;K0 387 | C;X12;K0 388 | C;X13;K0 389 | C;X16;K0 390 | C;X17;K1 391 | E 392 | -------------------------------------------------------------------------------- /ceres-formats/src/object.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::metadata::FieldVariant; 6 | use crate::metadata::MetadataStore; 7 | use crate::ObjectId; 8 | use crate::ObjectKind; 9 | use crate::parser::slk; 10 | use crate::ValueType; 11 | 12 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 13 | pub enum Value { 14 | String(String), 15 | Int(i32), 16 | Real(f32), 17 | Unreal(f32), 18 | } 19 | 20 | impl Value { 21 | pub fn from_str_and_ty(value: &str, ty: ValueType) -> Option { 22 | Some(match ty { 23 | ValueType::Unreal => Value::Unreal(value.parse().ok()?), 24 | ValueType::Real => Value::Real(value.parse().ok()?), 25 | ValueType::Int => Value::Int(value.parse().ok()?), 26 | ValueType::String => Value::String(value.into()), 27 | }) 28 | } 29 | 30 | pub fn type_id(&self) -> u32 { 31 | match self { 32 | Value::Int(..) => 0, 33 | Value::Real(..) => 1, 34 | Value::Unreal(..) => 2, 35 | Value::String(..) => 3, 36 | } 37 | } 38 | } 39 | 40 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 41 | pub struct LeveledValue { 42 | pub level: u32, 43 | pub value: Value, 44 | } 45 | 46 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 47 | pub enum FieldKind { 48 | Simple { value: Value }, 49 | Leveled { values: Vec }, 50 | } 51 | 52 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 53 | pub struct Field { 54 | pub id: ObjectId, 55 | pub kind: FieldKind, 56 | } 57 | 58 | #[derive(Debug, Clone, Serialize, Deserialize)] 59 | pub struct Object { 60 | kind: ObjectKind, 61 | id: ObjectId, 62 | parent_id: Option, 63 | fields: BTreeMap, 64 | dirty: bool, 65 | } 66 | 67 | impl Object { 68 | pub fn new(id: ObjectId, kind: ObjectKind) -> Object { 69 | Object { 70 | id, 71 | kind, 72 | parent_id: None, 73 | fields: Default::default(), 74 | dirty: true, 75 | } 76 | } 77 | 78 | pub fn with_parent(id: ObjectId, parent_id: ObjectId, kind: ObjectKind) -> Object { 79 | Object { 80 | id, 81 | kind, 82 | parent_id: Some(parent_id), 83 | fields: Default::default(), 84 | dirty: true, 85 | } 86 | } 87 | 88 | pub fn id(&self) -> ObjectId { 89 | self.id 90 | } 91 | 92 | pub fn set_id(&mut self, id: ObjectId) { 93 | self.id = id 94 | } 95 | 96 | pub fn parent_id(&self) -> Option { 97 | self.parent_id 98 | } 99 | 100 | pub fn set_parent_id(&mut self, parent_id: Option) { 101 | self.parent_id = parent_id 102 | } 103 | 104 | pub fn kind(&self) -> ObjectKind { 105 | self.kind 106 | } 107 | 108 | pub fn is_dirty(&self) -> bool { 109 | self.dirty 110 | } 111 | 112 | pub fn set_dirty(&mut self, dirty: bool) { 113 | self.dirty = dirty 114 | } 115 | 116 | pub fn fields(&self) -> impl Iterator { 117 | self.fields.iter() 118 | } 119 | 120 | pub fn field(&self, id: ObjectId) -> Option<&Field> { 121 | self.fields.get(&id) 122 | } 123 | 124 | pub fn simple_field(&self, id: ObjectId) -> Option<&Value> { 125 | self.fields.get(&id).and_then(|field| match &field.kind { 126 | FieldKind::Simple { value } => Some(value), 127 | _ => None, 128 | }) 129 | } 130 | 131 | pub fn leveled_field(&self, id: ObjectId, level: u32) -> Option<&Value> { 132 | self.fields.get(&id).and_then(|field| match &field.kind { 133 | FieldKind::Leveled { values } => values 134 | .iter() 135 | .find(|value| value.level == level) 136 | .map(|value| &value.value), 137 | _ => None, 138 | }) 139 | } 140 | 141 | pub fn unset_simple_field(&mut self, id: ObjectId) { 142 | self.dirty = true; 143 | 144 | self.fields.remove(&id); 145 | } 146 | 147 | pub fn unset_leveled_field(&mut self, id: ObjectId, level: u32) { 148 | self.dirty = true; 149 | 150 | if let Some(field) = self.fields.get_mut(&id) { 151 | if let FieldKind::Leveled { values } = &mut field.kind { 152 | values.retain(|dv| dv.level != level) 153 | } 154 | } 155 | } 156 | 157 | pub fn set_simple_field(&mut self, id: ObjectId, value: Value) { 158 | self.dirty = true; 159 | 160 | let kind = FieldKind::Simple { value }; 161 | let field = Field { id, kind }; 162 | self.fields.insert(id, field); 163 | } 164 | 165 | pub fn set_leveled_field(&mut self, id: ObjectId, level: u32, value: Value) { 166 | self.dirty = true; 167 | 168 | let field = self.fields.entry(id).or_insert_with(|| Field { 169 | id, 170 | kind: FieldKind::Leveled { 171 | values: Default::default(), 172 | }, 173 | }); 174 | 175 | match &mut field.kind { 176 | FieldKind::Simple { .. } => eprintln!( 177 | "tried to insert data field {} for object {}, but a simple field {} already exists", 178 | id, self.id, field.id 179 | ), 180 | FieldKind::Leveled { values } => { 181 | let new_value = LeveledValue { level, value }; 182 | 183 | if let Some(value) = values.iter_mut().find(|dv| dv.level == level) { 184 | *value = new_value; 185 | } else { 186 | values.push(new_value); 187 | } 188 | } 189 | } 190 | } 191 | 192 | /// Merges this object's data with another object's data 193 | /// Doesn't do field-level merging because it's not needed 194 | /// in our use case. Just override the fields in this object 195 | /// from the fields in the other. 196 | pub fn add_from(&mut self, other: &Object) { 197 | self.dirty = true; 198 | 199 | for (id, field) in &other.fields { 200 | self.fields.insert(*id, field.clone()); 201 | } 202 | } 203 | 204 | pub(crate) fn process_slk_field( 205 | &mut self, 206 | value: &slk::Value, 207 | name: &str, 208 | metadata: &MetadataStore, 209 | ) -> Option<()> { 210 | let (field_meta, level) = metadata.query_slk_field(name, &self)?; 211 | 212 | let value = Value::from_str_and_ty(value.as_inner()?, field_meta.value_ty)?; 213 | let field_id = field_meta.id; 214 | 215 | match field_meta.variant { 216 | FieldVariant::Normal { .. } => self.set_simple_field(field_id, value), 217 | FieldVariant::Leveled { .. } | FieldVariant::Data { .. } => self.set_leveled_field( 218 | field_id, 219 | level.expect("field must have level specified"), 220 | value, 221 | ), 222 | } 223 | 224 | Some(()) 225 | } 226 | 227 | pub(crate) fn process_func_field( 228 | &mut self, 229 | key: &str, 230 | value: &str, 231 | index: i8, 232 | metadata: &MetadataStore, 233 | ) -> Option<()> { 234 | let (field_meta, level) = metadata.query_profile_field(key, &self, index)?; 235 | let value = Value::from_str_and_ty(value, field_meta.value_ty)?; 236 | 237 | if let Some(level) = level { 238 | self.set_leveled_field(field_meta.id, level, value) 239 | } else { 240 | self.set_simple_field(field_meta.id, value) 241 | } 242 | 243 | Some(()) 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.3.6 2 | 3 | * Fixed an issue (for the umptienth time) related to incorrect handling of backwards slashes in Ceres. 4 | * Ceres no longer writes out object data files if there were no object data modifications. This means it doesn't create blank object data files anymore, and it also won't touch your existing object data if you do not modify it in Ceres. 5 | 6 | # 0.3.5 7 | 8 | * Fixed an issue with `map:addDir` and `dir` mapmode. 9 | * Fixed an issue with `fs.watchFile` not working correctly. 10 | * Fixed an issue with script arguments not being passed to `ceres exec`. 11 | * Upstreamed fix from `ceres-mpq` for crashes caused by empty files in MPQs. 12 | * Removed some old unused functionality from Ceres. 13 | * Slightly improved error reporting in certain cases. 14 | 15 | # 0.3.4 16 | 17 | * Attempted to fix an issue where sometimes field types in the OE API would not resolve correctly, causing some fields (such as `weapsOn`) to not work correctly. 18 | 19 | # 0.3.3 20 | 21 | * `fs.watchFile` no longer blocks. instead, Ceres spins up an event loop after the build script finishes where it processes file watchers started by `fs.watchFile`. The event loop will terminate if a WC3 instance launched by Ceres has exited. If WC3 wasn't started, then the event loop will continue running indefenitely until manually terminated. 22 | * The object API now has `getObject`, `setObject`, `getField` and `setField` methods for object storages and objects. They function identically to their indexing counterparts, and are meant for usage in TypeScript where the type system cannot correctly express the types returned from indexing operations in all cases. 23 | * `-launch` is now automatically appended to WC3 arguments when running via Ceres. 24 | * Files added via `map:addFileString`, `map:addFileDisk`, and `map:addDir` can now be read back via `map:readFile`. Previously, `map:readFile` would only return files that already existed in the map mpq/dir. 25 | 26 | # 0.3.2 27 | 28 | * Fixed a small bug where Ceres would not quit after building, even when no Live Reload was enabled. 29 | * Removed all unstable feature gates and dependencies. Ceres now compiles on stable Rust. 30 | 31 | # 0.3.1 32 | 33 | ## Breaking 34 | * `ceres.catch` and `ceres.wrapCatch` were renamed to `ceres.safeCall` and `ceres.wrapSafeCall` to avoid clashing with the `catch` operator in TS 35 | * Ceres no longer suppresses default `main` and `config` functions if their respective modules returned a non-false result. Instead, if you want to suppress default `main` and `config` behaviour, you can call `ceres.suppressDefaultMain()`, and `ceres.suppressDefaultConfig()`. This was a particular pain point for TS users with a `main.ts` module. 36 | * `mpq.new` was renamed to `mpq.create` to avoid clashing with the `new` operator in TS 37 | * Replaced `ceres.layout.srcDirectory` and `ceres.layout.libDireclctory` with one array - `ceres.layout.srcDirectories`, allowing you to specify any number of source directories to instruct Ceres to look in. This is useful for TypeScript projects that can have a pure-Lua directory, a compiled TS directory, and external dependencies in `node_modules`. 38 | 39 | ## Non-Breaking 40 | * When invoking `ceres run`, Ceres will now wait for WC3 to exit before shutting down. This is useful in VS Code on certain platforms, where previously a finished VS Code task running `ceres run` would make WC3 exit immediately. 41 | * Added a `fs.copyFile` function. 42 | * Added a `map:addDir` method to `map` objects, allowing you quickly import an entire directory into the map. 43 | * Pulled in upstream bugfixes from `ceres-mpq` related to path separator issues 44 | * Added a Ceres-specific unit field called `siid` to the Object API, which returns the unit's editor name, which is used by natives like `CreateUnitByName` 45 | 46 | ## Documentation 47 | 48 | The documentation for Ceres has been updated. There are now template repositories for Lua and TypeScript, as well as a [TypeScript library](https://github.com/ceres-wc3/cerrie) which provides idiomatic wrappers over JASS natives and useful utilities such as File I/O and Live Reload. 49 | 50 | Check the readme for more information. 51 | 52 | # 0.3.0 53 | 54 | __Significant breaking changes in this release!__ 55 | 56 | ## Breaking changes 57 | * Changed the way the generated map script works. Now all code `require`d through `main.lua` will run through the `main` function, permitting full safe access to all natives. Previously, you'd have to tip-toe around which WC3 systems were initialized at script-load time and which weren't. Now you can simply call those functions in the script body, without the need for hooks or any other tricks. 58 | * As a consequence, it is no longer possible to add/replace code in the `config` function through `main.lua`. If you still want to run code in the `config` section of the map script, create a `config.lua` file. The contents of this file will be executed in WC3's `config` section. 59 | * The same applies to init-time loading. If you want to run some code before either `config` or `main` execute, create a file called `init.lua`. 60 | * If you want to suppress the default behaviour of `main` or `config` (e.g. if you are doing all map initialization and player setup yourself), then `return true` in the respective files. Otherwise, your code will run before the default actions. 61 | * As a consequence, Ceres hooks `main::after`, `main::before`, `config::after`, and `config::before` no longer exist. Ceres will throw an error if you try to add a hook with either of those names. 62 | ## New features 63 | * The default map script template now has preliminary support for live-reloading. Please note that this __is an unfinished feature__. However, the script will automatically detect if it has been run a second time, and fire the `reload::before` and `reload::after` hooks when it does so, as well as reloading all the modules within itself. 64 | * Ceres now loads individual modules as a string rather than as a function, which allows WC3 to correctly report errors as located inside the offending module, rather than simply reporting them as being inside `war3map.lua`. 65 | * Added a new function `fs.watchFile(path, callback)`, which will execute `callback` with a file's new contents when the said file changes. This can be used to communicate with WC3 by creating Preloader files from inside WC3 and loading a response file inside WC3. This will be used by live reload. 66 | * Modules can now be specified as optional, i.e. `require('mymodule', true)`. Ceres will simply ignore it if the module does not exist. 67 | 68 | ## Bug fixes 69 | * Fixed a bug where certain unit fields were incorrectly parsed in the metadata, causing them to throw errors upon access attempts. 70 | * Fixed a bug in the Lua parser failing to parse `elseif` clauses sometimes. 71 | 72 | # 0.2.5 73 | 74 | * Fixed a bug with script-only compilation 75 | * Made some adjustments to the default build script and header file 76 | * Bumped the version of `ceres-mpq` to 0.1.6, fixing some bugs in the MPQ implementation 77 | 78 | # 0.2.4 79 | 80 | ### Object data manipulation 81 | * Build scripts (as well as compiletime macros) can now manipulate object data of the map 82 | * Default English WC3 data is provided for introspection purposes 83 | 84 | The default build script sets a global variable `currentMap` which exposes the `map` object of the currently processed map. 85 | 86 | Inside `compiletime()` expressions you can now access this variable, and edit objects via an intuitive table-like syntax. For example: 87 | ```lua 88 | compiletime(function() 89 | local customFootman = currentMap.objects.unit['hfoo']:clone() 90 | customFootman.Name = "A Custom Footman" 91 | currentMap.objects.unit['x000'] = customFootman 92 | end) 93 | ``` 94 | 95 | ### Other 96 | 97 | * Various bugs have been fixed with the build pipeline 98 | 99 | # 0.2.2 100 | 101 | * Fixed various issues which broke Ceres 102 | 103 | # 0.2.1 104 | 105 | * Fixed a bug where the Lua compiler would ignore `libDirectory` 106 | 107 | # 0.2.0 108 | 109 | Initial release of the Lua-centric rewrite. -------------------------------------------------------------------------------- /ceres-formats/src/parser/slk.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::iter::Peekable; 3 | use std::str; 4 | 5 | use num_traits::Num; 6 | 7 | use atoi::atoi; 8 | 9 | use crate::parser::crlf::Lines; 10 | 11 | fn strip_field(src: &[u8]) -> (&[u8], &[u8]) { 12 | let mut i = 0; 13 | while i < src.len() && src[i] != b';' { 14 | i += 1; 15 | } 16 | 17 | if i == src.len() { 18 | (&src[..], &[]) 19 | } else { 20 | (&src[0..i], &src[i + 1..src.len()]) 21 | } 22 | } 23 | 24 | #[derive(Debug)] 25 | pub enum Value<'src> { 26 | Str(&'src str), 27 | Num(&'src str), 28 | Empty, 29 | } 30 | 31 | impl<'src> Value<'src> { 32 | fn from_slice(src: &'src [u8]) -> Value<'src> { 33 | if src.is_empty() { 34 | Value::Empty 35 | } else if src[0] == b'"' { 36 | Value::Str(Self::parse_string(src)) 37 | } else { 38 | Value::Num(str::from_utf8(src).unwrap()) 39 | } 40 | } 41 | 42 | fn parse_string(src: &'src [u8]) -> &'src str { 43 | let mut i = 1; 44 | 45 | while i < src.len() && src[i] != b'"' { 46 | i += 1; 47 | } 48 | 49 | str::from_utf8(&src[1..i]).unwrap() 50 | } 51 | 52 | pub fn as_inner(&self) -> Option<&'src str> { 53 | match self { 54 | Value::Str(value) => Some(value), 55 | Value::Num(value) => Some(value), 56 | Value::Empty => None, 57 | } 58 | } 59 | 60 | pub fn as_str(&self) -> Option<&'src str> { 61 | match self { 62 | Value::Str(value) => Some(value), 63 | _ => None, 64 | } 65 | } 66 | 67 | pub fn as_num(&self) -> Option { 68 | match self { 69 | Value::Num(value) => Some(N::from_str_radix(value, 10).ok().unwrap()), 70 | _ => None, 71 | } 72 | } 73 | } 74 | 75 | #[derive(Debug)] 76 | enum Field<'src> { 77 | Col(u32), 78 | Row(u32), 79 | Value(&'src [u8]), 80 | Unknown, 81 | } 82 | 83 | impl<'src> Field<'src> { 84 | fn from_slice(src: &'src [u8]) -> (Option>, &'src [u8]) { 85 | let (field, rest) = strip_field(src); 86 | 87 | if field.is_empty() { 88 | return (None, rest); 89 | } 90 | 91 | let field = match field[0] { 92 | b'X' => atoi(&field[1..]).map(Field::Col), 93 | b'Y' => atoi(&field[1..]).map(Field::Row), 94 | b'K' => Some(Field::Value(&field[1..])), 95 | _ => Some(Field::Unknown), 96 | }; 97 | 98 | (field, rest) 99 | } 100 | } 101 | 102 | #[derive(Debug)] 103 | pub struct Cell<'src> { 104 | column: u32, 105 | row: Option, 106 | value: Value<'src>, 107 | } 108 | 109 | impl<'src> Cell<'src> { 110 | fn from_bytes(src: &'src [u8]) -> Option> { 111 | let (field, mut rest) = strip_field(src); 112 | 113 | let mut cell = if field == b"C" { 114 | Cell { 115 | column: 0, 116 | row: None, 117 | value: Value::Empty, 118 | } 119 | } else { 120 | return None; 121 | }; 122 | 123 | while !rest.is_empty() { 124 | let (field, new_rest) = Field::from_slice(rest); 125 | rest = new_rest; 126 | 127 | if let Some(field) = field { 128 | cell.apply_field(field); 129 | } 130 | } 131 | 132 | Some(cell) 133 | } 134 | 135 | fn apply_field(&mut self, field: Field<'src>) { 136 | match field { 137 | Field::Col(field_col) => self.column = field_col, 138 | Field::Row(field_row) => self.row = Some(field_row), 139 | Field::Value(field_value) => self.value = Value::from_slice(field_value), 140 | _ => {} 141 | } 142 | } 143 | 144 | pub fn value(&self) -> &Value<'src> { 145 | &self.value 146 | } 147 | 148 | pub fn column(&self) -> u32 { 149 | self.column 150 | } 151 | } 152 | 153 | #[derive(Debug)] 154 | struct Parser<'src> { 155 | lines: Lines<'src>, 156 | } 157 | 158 | impl<'src> Parser<'src> { 159 | pub fn new(source: &'src [u8]) -> Parser<'src> { 160 | Parser { 161 | lines: Lines::new(source), 162 | } 163 | } 164 | 165 | pub fn next_record(&mut self) -> Option> { 166 | while let Some(line) = self.lines.next() { 167 | if let Some(cell) = Cell::from_bytes(line) { 168 | return Some(cell); 169 | } 170 | } 171 | 172 | None 173 | } 174 | } 175 | 176 | struct Cells<'src> { 177 | parser: Parser<'src>, 178 | } 179 | 180 | impl<'src> Cells<'src> { 181 | fn into_rows(self) -> Rows<'src> { 182 | Rows { 183 | cells: self.peekable(), 184 | } 185 | } 186 | } 187 | 188 | impl<'src> Iterator for Cells<'src> { 189 | type Item = Cell<'src>; 190 | 191 | fn next(&mut self) -> Option { 192 | self.parser.next_record() 193 | } 194 | } 195 | 196 | impl<'src> IntoIterator for Parser<'src> { 197 | type IntoIter = Cells<'src>; 198 | type Item = Cell<'src>; 199 | 200 | fn into_iter(self) -> Self::IntoIter { 201 | Cells { parser: self } 202 | } 203 | } 204 | 205 | #[derive(Debug)] 206 | pub struct Row<'src> { 207 | pub position: u32, 208 | pub cells: Vec>, 209 | } 210 | 211 | struct Rows<'src> { 212 | cells: Peekable>, 213 | } 214 | 215 | impl<'src> Iterator for Rows<'src> { 216 | type Item = Row<'src>; 217 | 218 | fn next(&mut self) -> Option { 219 | if self.cells.peek().is_none() { 220 | None 221 | } else { 222 | let row_start = self.cells.find(|r| r.row.is_some()); 223 | 224 | if let Some(row_start) = row_start { 225 | let mut cells = Vec::new(); 226 | let position = row_start.row.unwrap(); 227 | cells.push(row_start); 228 | 229 | while let Some(peeked) = self.cells.peek() { 230 | if peeked.row.is_some() { 231 | break; 232 | } else { 233 | cells.push(self.cells.next().unwrap()) 234 | } 235 | } 236 | 237 | Some(Row { position, cells }) 238 | } else { 239 | None 240 | } 241 | } 242 | } 243 | } 244 | 245 | #[derive(Clone)] 246 | pub struct Legend<'src> { 247 | name_to_idx: HashMap<&'src str, u32>, 248 | idx_to_name: HashMap, 249 | } 250 | 251 | impl<'src> Legend<'src> { 252 | fn new(row: Row<'src>) -> Legend<'src> { 253 | let mut name_to_idx = HashMap::default(); 254 | let mut idx_to_name = HashMap::default(); 255 | 256 | for cell in row.cells { 257 | let name = cell.value.as_str(); 258 | 259 | if let Some(name) = name { 260 | name_to_idx.insert(name, cell.column); 261 | idx_to_name.insert(cell.column, name); 262 | } 263 | } 264 | 265 | Legend { 266 | name_to_idx, 267 | idx_to_name, 268 | } 269 | } 270 | 271 | pub fn cell_by_name<'r>(&self, row: &'r Row<'src>, name: &str) -> Option<&'r Cell<'src>> { 272 | let col_idx = *self.name_to_idx.get(name)?; 273 | row.cells.iter().find(|c| c.column == col_idx) 274 | } 275 | 276 | pub fn name_by_cell(&self, cell: &Cell) -> Option<&'src str> { 277 | self.idx_to_name.get(&cell.column).copied() 278 | } 279 | } 280 | 281 | pub struct Table<'src> { 282 | rows: Rows<'src>, 283 | legend: Legend<'src>, 284 | } 285 | 286 | impl<'src> Table<'src> { 287 | pub fn new(source: &'src [u8]) -> Option> { 288 | let mut rows = Parser::new(source).into_iter().into_rows(); 289 | let legend = Legend::new(rows.next()?); 290 | 291 | Some(Table { rows, legend }) 292 | } 293 | 294 | pub fn next_row(&mut self) -> Option> { 295 | self.rows.next() 296 | } 297 | 298 | pub fn has_next(&mut self) -> bool { 299 | self.rows.cells.peek().is_some() 300 | } 301 | 302 | pub fn legend(&self) -> Legend<'src> { 303 | self.legend.clone() 304 | } 305 | } 306 | 307 | pub fn read_row_str<'src>(row: &Row<'src>, legend: &Legend<'src>, name: &str) -> Option<&'src str> { 308 | legend 309 | .cell_by_name(row, name) 310 | .map(|r| r.value()) 311 | .and_then(|r| r.as_str()) 312 | } 313 | 314 | pub fn read_row_num<'src, N: Num>(row: &Row<'src>, legend: &Legend<'src>, name: &str) -> Option { 315 | legend 316 | .cell_by_name(row, name) 317 | .map(|r| r.value()) 318 | .and_then(|r| r.as_num()) 319 | } 320 | --------------------------------------------------------------------------------