├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── config.json ├── maps └── map.w3x │ ├── conversation.json │ ├── war3map.doo │ ├── war3map.lua │ ├── war3map.mmp │ ├── war3map.shd │ ├── war3map.w3c │ ├── war3map.w3e │ ├── war3map.w3i │ ├── war3map.w3r │ ├── war3map.wct │ ├── war3map.wpm │ ├── war3map.wtg │ ├── war3map.wts │ ├── war3mapMap.blp │ └── war3mapUnits.doo ├── package.json ├── scripts ├── build.ts ├── dev.ts ├── test.ts └── utils.ts ├── src ├── main.ts └── war3map.d.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | dist 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 trigger 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wc3-ts-template 2 | An easy to use template to get you started coding in TypeScript for Warcraft III maps. 3 | 4 | Setup Guide: [Getting Started](https://cipherxof.github.io/w3ts/docs/getting-started). 5 | 6 | ## Features 7 | * TypeScript API and wrappers for most handles ([w3ts](https://github.com/cipherxof/w3ts)) 8 | * Support for object data manipulation (read/write) ([war3-objectdata](https://github.com/cipherxof/war3-objectdata)) 9 | * Evalute code at compile-time and embed the results into your map. ([war3-transformer](https://github.com/cipherxof/war3-transformer)) 10 | * Build w3x archives from your project's map folder. 11 | * Work on your map in the World Editor while also coding in TypeScript. 12 | * Automatically create definitions for global variables generated by the World Editor such as regions, cameras, or preplaced units. 13 | * Works on Windows and Mac OS out of the box and with a couple modifications it works on Linux as well. 14 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mapFolder": "map.w3x", 3 | "minifyScript": false, 4 | "gameExecutable": "E:\\Games\\Warcraft III\\_retail_\\x86_64\\Warcraft III.exe", 5 | "outputFolder": "./dist/bin", 6 | "launchArgs": [ 7 | "-launch", 8 | "-windowmode", 9 | "windowed" 10 | ] 11 | } -------------------------------------------------------------------------------- /maps/map.w3x/conversation.json: -------------------------------------------------------------------------------- 1 | { 2 | "stringTablePath": "war3map.wts", 3 | "conversation": {} 4 | } -------------------------------------------------------------------------------- /maps/map.w3x/war3map.doo: -------------------------------------------------------------------------------- 1 | W3do -------------------------------------------------------------------------------- /maps/map.w3x/war3map.lua: -------------------------------------------------------------------------------- 1 | gg_trg_Initialization = nil 2 | gg_unit_Hblm_0003 = nil 3 | gg_unit_Hpal_0002 = nil 4 | function InitGlobals() 5 | end 6 | 7 | function CreateUnitsForPlayer0() 8 | local p = Player(0) 9 | local u 10 | local unitID 11 | local t 12 | local life 13 | gg_unit_Hblm_0003 = BlzCreateUnitWithSkin(p, FourCC("Hblm"), 44.7, -860.1, 3.208, FourCC("Hblm")) 14 | end 15 | 16 | function CreateUnitsForPlayer1() 17 | local p = Player(1) 18 | local u 19 | local unitID 20 | local t 21 | local life 22 | gg_unit_Hpal_0002 = BlzCreateUnitWithSkin(p, FourCC("Hpal"), 237.9, -832.4, 269.898, FourCC("Hpal")) 23 | end 24 | 25 | function CreatePlayerBuildings() 26 | end 27 | 28 | function CreatePlayerUnits() 29 | CreateUnitsForPlayer0() 30 | CreateUnitsForPlayer1() 31 | end 32 | 33 | function CreateAllUnits() 34 | CreatePlayerBuildings() 35 | CreatePlayerUnits() 36 | end 37 | 38 | -- 39 | function Trig_Initialization_Actions() 40 | SelectUnitForPlayerSingle(gg_unit_Hblm_0003, Player(0)) 41 | SelectUnitForPlayerSingle(gg_unit_Hpal_0002, Player(1)) 42 | end 43 | 44 | function InitTrig_Initialization() 45 | gg_trg_Initialization = CreateTrigger() 46 | TriggerAddAction(gg_trg_Initialization, Trig_Initialization_Actions) 47 | end 48 | 49 | function InitCustomTriggers() 50 | InitTrig_Initialization() 51 | end 52 | 53 | function RunInitializationTriggers() 54 | ConditionalTriggerExecute(gg_trg_Initialization) 55 | end 56 | 57 | function InitCustomPlayerSlots() 58 | SetPlayerStartLocation(Player(0), 0) 59 | ForcePlayerStartLocation(Player(0), 0) 60 | SetPlayerColor(Player(0), ConvertPlayerColor(0)) 61 | SetPlayerRacePreference(Player(0), RACE_PREF_HUMAN) 62 | SetPlayerRaceSelectable(Player(0), false) 63 | SetPlayerController(Player(0), MAP_CONTROL_USER) 64 | SetPlayerStartLocation(Player(1), 1) 65 | ForcePlayerStartLocation(Player(1), 1) 66 | SetPlayerColor(Player(1), ConvertPlayerColor(1)) 67 | SetPlayerRacePreference(Player(1), RACE_PREF_ORC) 68 | SetPlayerRaceSelectable(Player(1), false) 69 | SetPlayerController(Player(1), MAP_CONTROL_USER) 70 | SetPlayerStartLocation(Player(2), 2) 71 | ForcePlayerStartLocation(Player(2), 2) 72 | SetPlayerColor(Player(2), ConvertPlayerColor(2)) 73 | SetPlayerRacePreference(Player(2), RACE_PREF_UNDEAD) 74 | SetPlayerRaceSelectable(Player(2), false) 75 | SetPlayerController(Player(2), MAP_CONTROL_USER) 76 | SetPlayerStartLocation(Player(3), 3) 77 | ForcePlayerStartLocation(Player(3), 3) 78 | SetPlayerColor(Player(3), ConvertPlayerColor(3)) 79 | SetPlayerRacePreference(Player(3), RACE_PREF_NIGHTELF) 80 | SetPlayerRaceSelectable(Player(3), false) 81 | SetPlayerController(Player(3), MAP_CONTROL_USER) 82 | SetPlayerStartLocation(Player(11), 4) 83 | ForcePlayerStartLocation(Player(11), 4) 84 | SetPlayerColor(Player(11), ConvertPlayerColor(11)) 85 | SetPlayerRacePreference(Player(11), RACE_PREF_NIGHTELF) 86 | SetPlayerRaceSelectable(Player(11), false) 87 | SetPlayerController(Player(11), MAP_CONTROL_COMPUTER) 88 | end 89 | 90 | function InitCustomTeams() 91 | SetPlayerTeam(Player(0), 0) 92 | SetPlayerTeam(Player(1), 0) 93 | SetPlayerTeam(Player(2), 0) 94 | SetPlayerTeam(Player(3), 0) 95 | SetPlayerAllianceStateAllyBJ(Player(0), Player(1), true) 96 | SetPlayerAllianceStateAllyBJ(Player(0), Player(2), true) 97 | SetPlayerAllianceStateAllyBJ(Player(0), Player(3), true) 98 | SetPlayerAllianceStateAllyBJ(Player(1), Player(0), true) 99 | SetPlayerAllianceStateAllyBJ(Player(1), Player(2), true) 100 | SetPlayerAllianceStateAllyBJ(Player(1), Player(3), true) 101 | SetPlayerAllianceStateAllyBJ(Player(2), Player(0), true) 102 | SetPlayerAllianceStateAllyBJ(Player(2), Player(1), true) 103 | SetPlayerAllianceStateAllyBJ(Player(2), Player(3), true) 104 | SetPlayerAllianceStateAllyBJ(Player(3), Player(0), true) 105 | SetPlayerAllianceStateAllyBJ(Player(3), Player(1), true) 106 | SetPlayerAllianceStateAllyBJ(Player(3), Player(2), true) 107 | SetPlayerAllianceStateVisionBJ(Player(0), Player(1), true) 108 | SetPlayerAllianceStateVisionBJ(Player(0), Player(2), true) 109 | SetPlayerAllianceStateVisionBJ(Player(0), Player(3), true) 110 | SetPlayerAllianceStateVisionBJ(Player(1), Player(0), true) 111 | SetPlayerAllianceStateVisionBJ(Player(1), Player(2), true) 112 | SetPlayerAllianceStateVisionBJ(Player(1), Player(3), true) 113 | SetPlayerAllianceStateVisionBJ(Player(2), Player(0), true) 114 | SetPlayerAllianceStateVisionBJ(Player(2), Player(1), true) 115 | SetPlayerAllianceStateVisionBJ(Player(2), Player(3), true) 116 | SetPlayerAllianceStateVisionBJ(Player(3), Player(0), true) 117 | SetPlayerAllianceStateVisionBJ(Player(3), Player(1), true) 118 | SetPlayerAllianceStateVisionBJ(Player(3), Player(2), true) 119 | SetPlayerTeam(Player(11), 1) 120 | end 121 | 122 | function InitAllyPriorities() 123 | SetStartLocPrioCount(0, 3) 124 | SetStartLocPrio(0, 0, 1, MAP_LOC_PRIO_HIGH) 125 | SetStartLocPrio(0, 1, 2, MAP_LOC_PRIO_HIGH) 126 | SetStartLocPrio(0, 2, 3, MAP_LOC_PRIO_HIGH) 127 | SetStartLocPrioCount(1, 3) 128 | SetStartLocPrio(1, 0, 0, MAP_LOC_PRIO_HIGH) 129 | SetStartLocPrio(1, 1, 2, MAP_LOC_PRIO_HIGH) 130 | SetStartLocPrio(1, 2, 3, MAP_LOC_PRIO_HIGH) 131 | SetStartLocPrioCount(2, 3) 132 | SetStartLocPrio(2, 0, 0, MAP_LOC_PRIO_HIGH) 133 | SetStartLocPrio(2, 1, 1, MAP_LOC_PRIO_HIGH) 134 | SetStartLocPrio(2, 2, 3, MAP_LOC_PRIO_HIGH) 135 | SetStartLocPrioCount(3, 3) 136 | SetStartLocPrio(3, 0, 0, MAP_LOC_PRIO_HIGH) 137 | SetStartLocPrio(3, 1, 1, MAP_LOC_PRIO_HIGH) 138 | SetStartLocPrio(3, 2, 2, MAP_LOC_PRIO_HIGH) 139 | SetStartLocPrioCount(4, 3) 140 | SetStartLocPrio(4, 0, 0, MAP_LOC_PRIO_HIGH) 141 | SetStartLocPrio(4, 1, 2, MAP_LOC_PRIO_HIGH) 142 | end 143 | 144 | function main() 145 | SetCameraBounds(-3328.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), -3584.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM), 3328.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), 3072.0 - GetCameraMargin(CAMERA_MARGIN_TOP), -3328.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), 3072.0 - GetCameraMargin(CAMERA_MARGIN_TOP), 3328.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), -3584.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM)) 146 | SetDayNightModels("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTerrain\\DNCLordaeronTerrain.mdl", "Environment\\DNC\\DNCLordaeron\\DNCLordaeronUnit\\DNCLordaeronUnit.mdl") 147 | NewSoundEnvironment("Default") 148 | SetAmbientDaySound("LordaeronSummerDay") 149 | SetAmbientNightSound("LordaeronSummerNight") 150 | SetMapMusic("Music", true, 0) 151 | CreateAllUnits() 152 | InitBlizzard() 153 | InitGlobals() 154 | InitCustomTriggers() 155 | RunInitializationTriggers() 156 | end 157 | 158 | function config() 159 | SetMapName("TRIGSTR_001") 160 | SetMapDescription("TRIGSTR_003") 161 | SetPlayers(5) 162 | SetTeams(5) 163 | SetGamePlacement(MAP_PLACEMENT_TEAMS_TOGETHER) 164 | DefineStartLocation(0, 128.0, -896.0) 165 | DefineStartLocation(1, 128.0, -896.0) 166 | DefineStartLocation(2, 128.0, -896.0) 167 | DefineStartLocation(3, 128.0, -896.0) 168 | DefineStartLocation(4, 128.0, -896.0) 169 | InitCustomPlayerSlots() 170 | InitCustomTeams() 171 | InitAllyPriorities() 172 | end 173 | 174 | -------------------------------------------------------------------------------- /maps/map.w3x/war3map.mmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipherxof/wc3-ts-template/e3267f00d9ca950994509b3b1ab53d5ed9ef2372/maps/map.w3x/war3map.mmp -------------------------------------------------------------------------------- /maps/map.w3x/war3map.shd: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maps/map.w3x/war3map.w3c: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maps/map.w3x/war3map.w3e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipherxof/wc3-ts-template/e3267f00d9ca950994509b3b1ab53d5ed9ef2372/maps/map.w3x/war3map.w3e -------------------------------------------------------------------------------- /maps/map.w3x/war3map.w3i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipherxof/wc3-ts-template/e3267f00d9ca950994509b3b1ab53d5ed9ef2372/maps/map.w3x/war3map.w3i -------------------------------------------------------------------------------- /maps/map.w3x/war3map.w3r: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /maps/map.w3x/war3map.wct: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipherxof/wc3-ts-template/e3267f00d9ca950994509b3b1ab53d5ed9ef2372/maps/map.w3x/war3map.wct -------------------------------------------------------------------------------- /maps/map.w3x/war3map.wpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipherxof/wc3-ts-template/e3267f00d9ca950994509b3b1ab53d5ed9ef2372/maps/map.w3x/war3map.wpm -------------------------------------------------------------------------------- /maps/map.w3x/war3map.wtg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipherxof/wc3-ts-template/e3267f00d9ca950994509b3b1ab53d5ed9ef2372/maps/map.w3x/war3map.wtg -------------------------------------------------------------------------------- /maps/map.w3x/war3map.wts: -------------------------------------------------------------------------------- 1 | STRING 1 2 | { 3 | TypeScript Template 4 | } 5 | 6 | STRING 2 7 | { 8 | Any 9 | } 10 | 11 | STRING 3 12 | { 13 | Nondescript 14 | } 15 | 16 | STRING 4 17 | { 18 | TriggerHappy 19 | } 20 | 21 | STRING 5 22 | { 23 | Player 1 24 | } 25 | 26 | STRING 6 27 | { 28 | Player 2 29 | } 30 | 31 | STRING 7 32 | { 33 | Player 3 34 | } 35 | 36 | STRING 8 37 | { 38 | Force 1 39 | } 40 | 41 | STRING 9 42 | { 43 | Force 2 44 | } 45 | 46 | STRING 10 47 | { 48 | Player 4 49 | } 50 | 51 | STRING 11 52 | { 53 | Player 12 54 | } 55 | 56 | -------------------------------------------------------------------------------- /maps/map.w3x/war3mapMap.blp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipherxof/wc3-ts-template/e3267f00d9ca950994509b3b1ab53d5ed9ef2372/maps/map.w3x/war3mapMap.blp -------------------------------------------------------------------------------- /maps/map.w3x/war3mapUnits.doo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipherxof/wc3-ts-template/e3267f00d9ca950994509b3b1ab53d5ed9ef2372/maps/map.w3x/war3mapUnits.doo -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wc3-ts-template", 3 | "version": "1.1.0", 4 | "description": "", 5 | "author": "TriggerHappy", 6 | "license": "MIT", 7 | "main": "src/main.ts", 8 | "scripts": { 9 | "test": "ts-node --transpile-only scripts/test.ts", 10 | "build": "ts-node --transpile-only scripts/build.ts", 11 | "dev": "npm-watch", 12 | "build:defs": "ts-node scripts/dev" 13 | }, 14 | "dependencies": { 15 | "w3ts": "^3.0.2" 16 | }, 17 | "devDependencies": { 18 | "@types/fs-extra": "11.0.4", 19 | "@types/node": "22.15.17", 20 | "@types/pako": "2.0.3", 21 | "fs-extra": "11.3.0", 22 | "lua-types": "^2.13.1", 23 | "luamin": "^1.0.4", 24 | "mdx-m3-viewer-th": "^5.13.3", 25 | "npm-watch": "0.13.0", 26 | "ts-node": "10.9.2", 27 | "tsconfig-paths": "4.2.0", 28 | "tsutils": "3.21.0", 29 | "typescript": "5.8.2", 30 | "typescript-to-lua": "1.31.0", 31 | "war3-objectdata-th": "^0.2.11", 32 | "war3-transformer": "^3.0.10", 33 | "war3tstlhelper": "1.0.1", 34 | "winston": "^3.17.0", 35 | "war3-types-strict": "^0.1.3" 36 | }, 37 | "watch": { 38 | "build:defs": { 39 | "patterns": [ 40 | "./maps/*" 41 | ], 42 | "extensions": "lua", 43 | "legacyWatch": true 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs-extra"; 2 | import * as path from "path"; 3 | import War3Map from "mdx-m3-viewer-th/dist/cjs/parsers/w3x/map" 4 | import { compileMap, getFilesInDirectory, loadJsonFile, logger, toArrayBuffer, IProjectConfig } from "./utils"; 5 | 6 | function main() { 7 | const config: IProjectConfig = loadJsonFile("config.json"); 8 | const minify = process.argv[2] === '-minify' || config.minifyScript 9 | 10 | if(minify !== config.minifyScript){ 11 | logger.warn(`minifyScript has been overridden by command line argument "-minify"`) 12 | config.minifyScript = minify 13 | } 14 | 15 | 16 | const result = compileMap(config); 17 | 18 | if (!result) { 19 | logger.error(`Failed to compile map.`); 20 | return; 21 | } 22 | 23 | logger.info(`Creating w3x archive...`); 24 | if (!fs.existsSync(config.outputFolder)) { 25 | fs.mkdirSync(config.outputFolder); 26 | } 27 | 28 | createMapFromDir(`${config.outputFolder}/${config.mapFolder}`, `./dist/${config.mapFolder}`); 29 | } 30 | 31 | /** 32 | * Creates a w3x archive from a directory 33 | * @param output The output filename 34 | * @param dir The directory to create the archive from 35 | */ 36 | export function createMapFromDir(output: string, dir: string) { 37 | const map = new War3Map(); 38 | const files = getFilesInDirectory(dir); 39 | 40 | map.archive.resizeHashtable(files.length); 41 | 42 | for (const fileName of files) { 43 | const contents = toArrayBuffer(fs.readFileSync(fileName)); 44 | const archivePath = path.relative(dir, fileName); 45 | const imported = map.import(archivePath, contents); 46 | 47 | if (!imported) { 48 | logger.warn("Failed to import " + archivePath); 49 | continue; 50 | } 51 | } 52 | 53 | const result = map.save(); 54 | 55 | if (!result) { 56 | logger.error("Failed to save archive."); 57 | return; 58 | } 59 | 60 | fs.writeFileSync(output, new Uint8Array(result)); 61 | 62 | logger.info("Finished!"); 63 | } 64 | 65 | main(); -------------------------------------------------------------------------------- /scripts/dev.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs-extra"; 2 | import { loadJsonFile, logger } from "./utils"; 3 | const War3TSTLHelper = require("war3tstlhelper"); 4 | 5 | const config = loadJsonFile("config.json"); 6 | 7 | // Create definitions file for generated globals 8 | const luaFile = `./maps/${config.mapFolder}/war3map.lua`; 9 | 10 | try { 11 | const contents = fs.readFileSync(luaFile, "utf8"); 12 | const parser = new War3TSTLHelper(contents); 13 | const result = parser.genTSDefinitions(); 14 | fs.writeFileSync("src/war3map.d.ts", result); 15 | } catch (err: any) { 16 | logger.error(err.toString()); 17 | logger.error(`There was an error generating the definition file for '${luaFile}'`); 18 | } -------------------------------------------------------------------------------- /scripts/test.ts: -------------------------------------------------------------------------------- 1 | import {exec, execFile, execSync} from "child_process"; 2 | import {loadJsonFile, logger, compileMap, IProjectConfig} from "./utils"; 3 | 4 | function main() { 5 | const config: IProjectConfig = loadJsonFile("config.json"); 6 | const result = compileMap(config); 7 | 8 | if (!result) { 9 | logger.error(`Failed to compile map.`); 10 | return; 11 | } 12 | 13 | const cwd = process.cwd(); 14 | const filename = `${cwd}/dist/${config.mapFolder}`; 15 | 16 | logger.info(`Launching map "${filename.replace(/\\/g, "/")}"...`); 17 | 18 | if(config.winePath) { 19 | const wineFilename = `"Z:${filename}"` 20 | const prefix = config.winePrefix ? `WINEPREFIX=${config.winePrefix}` : '' 21 | execSync(`${prefix} ${config.winePath} "${config.gameExecutable}" ${["-loadfile", wineFilename, ...config.launchArgs].join(' ')}`, { stdio: 'ignore' }); 22 | } else { 23 | execFile(config.gameExecutable, ["-loadfile", filename, ...config.launchArgs], (err: any) => { 24 | if (err && err.code === 'ENOENT') { 25 | logger.error(`No such file or directory "${config.gameExecutable}". Make sure gameExecutable is configured properly in config.json.`); 26 | } 27 | }); 28 | } 29 | } 30 | 31 | main(); 32 | -------------------------------------------------------------------------------- /scripts/utils.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import { writeFileSync } from "fs"; 3 | import * as fs from "fs-extra"; 4 | import * as path from "path"; 5 | import { createLogger, format, transports } from "winston"; 6 | const { combine, timestamp, printf } = format; 7 | const luamin = require('luamin'); 8 | 9 | export interface IProjectConfig { 10 | mapFolder: string; 11 | minifyScript: boolean; 12 | gameExecutable: string; 13 | outputFolder: string; 14 | launchArgs: string[]; 15 | winePath?: string; 16 | winePrefix?: string; 17 | } 18 | 19 | /** 20 | * Load an object from a JSON file. 21 | * @param fname The JSON file 22 | */ 23 | export function loadJsonFile(fname: string) { 24 | try { 25 | return JSON.parse(fs.readFileSync(fname).toString()); 26 | } catch (e: any) { 27 | logger.error(e.toString()); 28 | return {}; 29 | } 30 | } 31 | 32 | /** 33 | * Convert a Buffer to ArrayBuffer 34 | * @param b 35 | */ 36 | export function toArrayBuffer(b: Buffer): ArrayBuffer { 37 | var ab = new ArrayBuffer(b.length); 38 | var view = new Uint8Array(ab); 39 | for (var i = 0; i < b.length; ++i) { 40 | view[i] = b[i]; 41 | } 42 | return ab; 43 | } 44 | 45 | /** 46 | * Convert an ArrayBuffer to Buffer 47 | * @param ab 48 | */ 49 | export function toBuffer(ab: ArrayBuffer) { 50 | var buf = Buffer.alloc(ab.byteLength); 51 | var view = new Uint8Array(ab); 52 | for (var i = 0; i < buf.length; ++i) { 53 | buf[i] = view[i]; 54 | } 55 | return buf; 56 | } 57 | 58 | /** 59 | * Recursively retrieve a list of files in a directory. 60 | * @param dir The path of the directory 61 | */ 62 | export function getFilesInDirectory(dir: string) { 63 | const files: string[] = []; 64 | fs.readdirSync(dir).forEach(file => { 65 | let fullPath = path.join(dir, file); 66 | if (fs.lstatSync(fullPath).isDirectory()) { 67 | const d = getFilesInDirectory(fullPath); 68 | for (const n of d) { 69 | files.push(n); 70 | } 71 | } else { 72 | files.push(fullPath); 73 | } 74 | }); 75 | return files; 76 | }; 77 | 78 | function updateTSConfig(mapFolder: string) { 79 | const tsconfig = loadJsonFile('tsconfig.json'); 80 | const plugin = tsconfig.compilerOptions.plugins[0]; 81 | 82 | plugin.mapDir = path.resolve('maps', mapFolder).replace(/\\/g, '/'); 83 | plugin.entryFile = path.resolve(tsconfig.tstl.luaBundleEntry).replace(/\\/g, '/'); 84 | plugin.outputDir = path.resolve('dist', mapFolder).replace(/\\/g, '/'); 85 | 86 | writeFileSync('tsconfig.json', JSON.stringify(tsconfig, undefined, 2)); 87 | } 88 | 89 | /** 90 | * 91 | */ 92 | export function compileMap(config: IProjectConfig) { 93 | if (!config.mapFolder) { 94 | logger.error(`Could not find key "mapFolder" in config.json`); 95 | return false; 96 | } 97 | 98 | logger.info("Cleaning dist directory..."); 99 | fs.removeSync("./dist"); 100 | 101 | logger.info(`Building "${config.mapFolder}"...`); 102 | fs.copySync(`./maps/${config.mapFolder}`, `./dist/${config.mapFolder}`); 103 | 104 | logger.info("Modifying tsconfig.json to work with war3-transformer..."); 105 | updateTSConfig(config.mapFolder); 106 | 107 | logger.info("Transpiling TypeScript to Lua..."); 108 | execSync('tstl -p tsconfig.json', { stdio: 'inherit' }); 109 | 110 | const tsLua = "./dist/tstl_output.lua"; 111 | if (!fs.existsSync(tsLua)) { 112 | logger.error(`Could not find "${tsLua}"`); 113 | return false; 114 | } 115 | 116 | // Merge the TSTL output with war3map.lua 117 | const mapLua = `./dist/${config.mapFolder}/war3map.lua`; 118 | 119 | if (!fs.existsSync(mapLua)) { 120 | logger.error(`Could not find "${mapLua}"`); 121 | return false; 122 | } 123 | 124 | try { 125 | let contents = fs.readFileSync(mapLua).toString() + fs.readFileSync(tsLua).toString(); 126 | 127 | if (config.minifyScript) { 128 | logger.info(`Minifying script...`); 129 | contents = luamin.minify(contents.toString()); 130 | } 131 | 132 | fs.writeFileSync(mapLua, contents); 133 | } catch (err: any) { 134 | logger.error(err.toString()); 135 | return false; 136 | } 137 | 138 | return true; 139 | } 140 | 141 | /** 142 | * Formatter for log messages. 143 | */ 144 | const loggerFormatFunc = printf(({ level, message, timestamp }) => { 145 | return `[${(timestamp as string).replace("T", " ").split(".")[0]}] ${level}: ${message}`; 146 | }); 147 | 148 | /** 149 | * The logger object. 150 | */ 151 | export const logger = createLogger({ 152 | transports: [ 153 | new transports.Console({ 154 | format: combine( 155 | format.colorize(), 156 | timestamp(), 157 | loggerFormatFunc 158 | ), 159 | }), 160 | new transports.File({ 161 | filename: "project.log", 162 | format: combine( 163 | timestamp(), 164 | loggerFormatFunc 165 | ), 166 | }), 167 | ] 168 | }); 169 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Timer, Unit } from "w3ts"; 2 | import { Players } from "w3ts/globals"; 3 | import { W3TS_HOOK, addScriptHook } from "w3ts/hooks"; 4 | import { Units } from "@objectdata/units"; 5 | 6 | const BUILD_DATE = compiletime(() => new Date().toUTCString()); 7 | const TS_VERSION = compiletime(() => require("typescript").version); 8 | const TSTL_VERSION = compiletime(() => require("typescript-to-lua").version); 9 | 10 | compiletime(( { objectData, constants }) => { 11 | const unit = objectData.units.get(constants.units.Footman); 12 | 13 | if (!unit) { 14 | return; 15 | } 16 | 17 | unit.modelFile = "units\\human\\TheCaptain\\TheCaptain.mdl"; 18 | 19 | objectData.save(); 20 | }); 21 | 22 | function tsMain() { 23 | try { 24 | print(`Build: ${BUILD_DATE}`); 25 | print(`Typescript: v${TS_VERSION}`); 26 | print(`Transpiler: v${TSTL_VERSION}`); 27 | print(" "); 28 | print("Welcome to TypeScript!"); 29 | 30 | const unit = Unit.create(Players[0], FourCC(Units.Footman), 0, 0, 270)!; 31 | 32 | Timer.create().start(1.0, true, () => { 33 | unit.color = Players[math.random(0, bj_MAX_PLAYERS)].color; 34 | }); 35 | } catch (e) { 36 | print(e); 37 | } 38 | } 39 | 40 | addScriptHook(W3TS_HOOK.MAIN_AFTER, tsMain); 41 | -------------------------------------------------------------------------------- /src/war3map.d.ts: -------------------------------------------------------------------------------- 1 | declare var gg_trg_Initialization: trigger; 2 | declare var gg_unit_Hblm_0003: unit; 3 | declare var gg_unit_Hpal_0002: unit; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "allowJs": false, 5 | "outDir": ".", 6 | "baseUrl": "./", 7 | "forceConsistentCasingInFileNames": true, 8 | "target": "ESNext", 9 | "lib": [ 10 | "ESNext" 11 | ], 12 | "moduleResolution": "node", 13 | "paths": { 14 | "@objectdata/*": [ 15 | "./node_modules/war3-objectdata-th/dist/cjs/generated/constants/*" 16 | ] 17 | }, 18 | "plugins": [ 19 | { 20 | "transform": "war3-transformer", 21 | "mapDir": "C:/Users/Me/Documents/GitHub/wc3-ts-template/maps/map.w3x", 22 | "entryFile": "C:/Users/Me/Documents/GitHub/wc3-ts-template/src/main.ts", 23 | "outputDir": "C:/Users/Me/Documents/GitHub/wc3-ts-template/dist/map.w3x" 24 | } 25 | ], 26 | "types": [ 27 | "@typescript-to-lua/language-extensions", 28 | "lua-types/core/coroutine", 29 | "lua-types/core/global", 30 | "lua-types/core/math", 31 | "lua-types/core/metatable", 32 | "lua-types/core/modules", 33 | "lua-types/core/string", 34 | "lua-types/core/table", 35 | "lua-types/core/os", 36 | "lua-types/special/5.3", 37 | "war3-types-strict/1.33.0", 38 | "war3-transformer/types", 39 | "war3-objectdata-th/dist/cjs/objectdata" 40 | ] 41 | }, 42 | "include": [ 43 | "src" 44 | ], 45 | "exclude": [], 46 | "tstl": { 47 | "luaTarget": "5.3", 48 | "noHeader": true, 49 | "luaLibImport": "require", 50 | "noImplicitSelf": true, 51 | "luaBundle": "dist/tstl_output.lua", 52 | "luaBundleEntry": "./src/main.ts", 53 | "sourceMapTraceback": false, 54 | "noResolvePaths": [ 55 | "typescript", 56 | "typescript-to-lua" 57 | ] 58 | } 59 | } --------------------------------------------------------------------------------