• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!
  • ✅ The POLL for Hive's Texturing Contest #34 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!
  • ✅ The POLL for Hive's Techtree Contest #20 is OPEN! Vote for the TOP 3 FACTIONS! 🔗Click here to cast your vote!

**JassForge Released – A Modern Compiler/Project Template for Warcraft III JASS Mapping**

Level 3
Joined
Mar 14, 2019
Messages
8
github:GitHub - LuciferFor/JassForge-Project

Hi everyone,

I’d like to share JassForge, a compiler/project template for Warcraft III map scripting.

Repository:

JassForge is built to make JASS map development more modern and more comfortable.
It packages the compiler, map script structure, include/import setup, build scripts, and example code into one workflow, so you can compile war3map.j and write it back into Map.w3x more easily.

Main features:
  • Zinc-style syntax ({}, ;, ->)
  • Strongly typed enums
  • Default parameters and reference parameters
  • Static local variables
  • Function pointer types
  • Lambdas and closures
  • Generic structs
  • new/delete/let lifecycle syntax
  • Struct reflection
  • Standard containers like vector, stack, queue, and map
  • Hashtable double-index syntax sugar
  • Array initialization, raw strings, and interpreter grouping

What’s included:
  • JassForge compiler
  • Example Warcraft III map project structure
  • Include/import files
  • PowerShell build scripts
  • Demo code showing language features

Project goal:
The goal is to provide a smoother and more expressive workflow for Warcraft III scripting, especially for people who want something more modern than traditional JASS/vJASS-style development.

If you are interested, I’d really appreciate feedback on:
  • syntax design
  • workflow/build process
  • usability for real map projects
  • compatibility expectations in the Warcraft III modding ecosystem

Thanks for checking it out.

JASS:
//! zinc

library JassForgeFeatureDemo {

    // -----------------------------------------------------------------
    // Demo 1: Strongly typed enum
    // When using an enum, you must write qualified names like DemoMode.Boss.
    // You cannot write Boss directly.
    // -----------------------------------------------------------------

    enum DemoMode: integer {
        Idle = 0,
        Farming = 1,
        Boss = 2
    }

    // -----------------------------------------------------------------
    // Demo 2: Function pointer types
    // JassForge can declare function pointers with signatures,
    // which are more strongly typed than traditional code.
    // -----------------------------------------------------------------

    type ScoreFormatter extends function(string, integer) -> string;
    type IntTransform extends function(integer) -> integer;

    // -----------------------------------------------------------------
    // Demo 3: Global variables, array initialization, hashtable
    // Zinc global arrays can be initialized directly with initializer lists.
    // -----------------------------------------------------------------

    integer DemoGlobalScore = 0;
    string DemoLabels[] = { "Default parameters", "Reference parameters", "Struct reflection" };
    hashtable DemoTable = InitHashtable();

    // -----------------------------------------------------------------
    // Demo 4: Generic struct
    // It will be monomorphized at compile time.
    // For example, Box<integer> will generate a concrete instance type.
    // -----------------------------------------------------------------

    struct Box<T> {
        T value;

        public method assign(T v) {
            this.value = v;
        }

        public method get() -> T {
            return this.value;
        }
    }

    // -----------------------------------------------------------------
    // Demo 5: Constructor, destructor, default fields, readonly, reflection
    // new/delete/let will all trigger lifecycle logic.
    // -----------------------------------------------------------------

    struct DemoStats {
        integer hp = 100;
        real speed = 1.00;
        string name = "Unnamed";
        readonly integer rawId = 'Hpal';
        DemoMode mode = DemoMode.Idle;

        DemoStats(string heroName, integer startHp) {
            this.name = heroName;
            this.hp = startHp;
        }

        ~DemoStats() {
            BJDebugMsg("[delete/let] Destructor: " + this.name);
        }

        public method describe(string prefix = "Character") -> string {
            return prefix + " " + this.name + " hp=" + I2S(this.hp);
        }

        public static method fieldInfo() -> string {
            return "Field count=" + I2S(DemoStats.size()) + ", type of field #2=" + DemoStats.type(1);
        }
    }

    // -----------------------------------------------------------------
    // Demo 6: interpreter block
    // When generating war3map.j, interpreter-start / interpreter-end
    // comments will be output.
    // -----------------------------------------------------------------

    interpreter "ScaffoldDemo/InterpreterBlock" {
        function InterpreterHello() {
            BJDebugMsg("[interpreter] Called function through interpreter block path");
        }
    }

    function AddScore(integer base, integer bonus = 10) -> integer {
        return base + bonus;
    }

    function BumpScore(integer &score, integer delta) {
        score += delta;
    }

    function FormatScore(string label, integer score) -> string {
        return label + "=" + I2S(score);
    }

    function CountOnCall() -> integer {
        static integer count = 0;
        count += 1;
        return count;
    }

    function BoolText(boolean value) -> string {
        return value ? "true" : "false";
    }

    function DemoEnum(DemoMode mode) {
        if (mode == DemoMode.Boss) {
            BJDebugMsg("[enum] Current mode: Boss");
        } else {
            BJDebugMsg("[enum] Current mode is not Boss");
        }
    }

    function DemoDefaultAndRef() {
        integer score = AddScore(5);
        BumpScore(score, 3);
        DemoGlobalScore = score;
        BJDebugMsg("[Default parameter / & reference parameter] " + I2S(score));
    }

    function DemoStaticLocal() {
        BJDebugMsg("[static local variable] 1st time=" + I2S(CountOnCall()));
        BJDebugMsg("[static local variable] 2nd time=" + I2S(CountOnCall()));
    }

    function DemoFunctionPointerAndLambda() {
        ScoreFormatter formatter = FormatScore;

        IntTransform transform = function(integer value) -> integer {
            return value + 7;
        };

        BJDebugMsg("[function pointer / lambda] " + formatter.evaluate("Score", transform.evaluate(5)));
    }

    function DemoSysFunction() {
        sys::function<FormatScore> exactFormatter = FormatScore;
        BJDebugMsg("[sys::function<function name>] " + exactFormatter("Exact binding", 77));
    }

    function DemoGenericStruct() {
        Box<integer> intBox = Box<integer>.create();
        Box<string> stringBox = Box<string>.create();

        intBox.assign(42);
        stringBox.assign("generic string");

        BJDebugMsg("[generic struct] int=" + I2S(intBox.get()) + " string=" + stringBox.get());

        intBox.destroy();
        stringBox.destroy();
    }

    function DemoNewDeleteAndReflection() {
        DemoStats hero = new DemoStats("Test Hero", 250);

        integer hp = hero.at("hp");
        hero.at("hp") = hp + 50;

        BJDebugMsg("[new/delete] " + hero.describe());
        BJDebugMsg("[struct reflection] " + DemoStats.fieldInfo());
        BJDebugMsg("[struct reflection] name field=" + hero.at("name", string));

        delete hero;
    }

    function DemoLetAutoCleanup() {
        let DemoStats temp("temporary let object", 80);
        BJDebugMsg("[let] " + temp.describe("Auto-cleanup object"));
    }

    function DemoVectorAndRangeFor() {
        sys::vector<integer> values = sys::vector<integer>.create();

        values.push_back(3);
        values.push_back(1);
        values.push_back(2);

        sys::sort(values);

        for (integer value : values) {
            BJDebugMsg("[sys::vector / range-for] value=" + I2S(value));
        }

        values.destroy();
    }

    function DemoStack() {
        sys::stack<string> names = sys::stack<string>.create();

        names.push("First layer");
        names.push("Second layer");
        names.push("Top of stack");

        BJDebugMsg("[sys::stack] size=" + I2S(names.size()) + " top=" + names.top());

        names.pop();

        BJDebugMsg("[sys::stack] top after pop=" + names.top());

        names.clear();

        BJDebugMsg("[sys::stack] empty after clear=" + BoolText(names.empty()));

        names.destroy();
    }

    function DemoQueue() {
        sys::queue<integer> waves = sys::queue<integer>.create();

        waves.push(10);
        waves.push(20);
        waves.push(30);

        BJDebugMsg("[sys::queue] size=" + I2S(waves.size()) + " front=" + I2S(waves.front()) + " back=" + I2S(waves.back()));

        waves.pop();

        BJDebugMsg("[sys::queue] front after pop=" + I2S(waves.front()));

        waves.clear();

        BJDebugMsg("[sys::queue] empty after clear=" + BoolText(waves.empty()));

        waves.destroy();
    }

    function DemoMap() {
        sys::map<string, integer> scores = sys::map<string, integer>.create();

        scores.insert("Warrior", 100);
        scores["Mage"] = 88;
        scores["Hunter"] = 92;

        if (scores.contains("Warrior")) {
            BJDebugMsg("[sys::map] Warrior=" + I2S(scores["Warrior"]) + " count=" + I2S(scores.count("Warrior")));
        }

        for (string key : scores) {
            BJDebugMsg("[sys::map / range-for] " + key + "=" + I2S(scores[key]));
        }

        BJDebugMsg("[sys::map] key #0=" + scores.at(0));

        scores.erase("Mage");

        BJDebugMsg("[sys::map] size after removing Mage=" + I2S(scores.size()));

        scores.clear();

        BJDebugMsg("[sys::map] empty after clear=" + BoolText(scores.empty()));

        scores.destroy();
    }

    function DemoClosureCallbacks() {
        timer delay = CreateTimer();
        group units = CreateGroup();

        string timerText = "TimerStart can directly capture local variables";
        string groupText = "ForGroup can also directly capture outer variables";
        integer reward = 25;

        // No need to manually save values into a hashtable,
        // and no need to pass parameters through C2I/I2C.

        TimerStart(delay, 0.00, false, function() {
            BJDebugMsg("[Timer closure] " + timerText + " reward=" + I2S(reward));
            DestroyTimer(delay);
        });

        GroupEnumUnitsOfPlayer(units, Player(0), null);

        // The callback can still read groupText/reward,
        // and can directly use GetEnumUnit().

        ForGroup(units, function() {
            BJDebugMsg("[ForGroup closure] " + groupText + " unitId=" + I2S(GetHandleId(GetEnumUnit())) + " reward=" + I2S(reward));
        });

        DestroyGroup(units);

        delay = null;
        units = null;
    }

    function DemoHashtableSugar() {
        DemoTable["player-1"]["score"] = DemoGlobalScore;
        BJDebugMsg("[hashtable double-index syntax] score=" + I2S((integer)DemoTable["player-1"]["score"]));
    }

    function DemoArrayAndRawString() {
        string tips[] = {
            DemoLabels[0],
            DemoLabels[1],
            R"(Raw string: path C:\MapDemo\war3map.j does not need double backslashes)"
        };

        BJDebugMsg("[array initialization] " + tips[0] + " / " + tips[1]);
        BJDebugMsg("[raw string] " + tips[2]);
    }

    function DemoInterpreterCall() {
        interpreter.("ScaffoldDemo/InterpreterBlock").InterpreterHello();
    }

    // Companion plugin: search for JassForge in the VSCode Marketplace.

    function onInit() {
        BJDebugMsg("===== JassForge Compiler Feature Demo =====");

        DemoEnum(DemoMode.Boss);
        DemoDefaultAndRef();
        DemoStaticLocal();
        DemoFunctionPointerAndLambda();
        DemoSysFunction();
        DemoGenericStruct();
        DemoNewDeleteAndReflection();
        DemoLetAutoCleanup();
        DemoVectorAndRangeFor();
        DemoStack();
        DemoQueue();
        DemoMap();
        DemoClosureCallbacks();
        DemoHashtableSugar();
        DemoArrayAndRawString();
        DemoInterpreterCall();
    }
}

//! endzinc

Search the VSCode Extension Marketplace for JassForge.

It is compatible with JASS / vJASS / Zinc.
 
compatibility expectations in the Warcraft III modding ecosystem
:thumbs_up:

This is the part where I say my obligatory "I'm hoping someone will make an improved version of a tool like this that'll convert Lua into jass that I can paste into old world editor versions so that I don't have to learn Jass (but can still develop my older maps on 1.27b and support other people who use such more stable legacy versions, for maximum compatibility)"
 
Judging from the Github page, is this meant to be used with YDWE (and I'm assuming that's the recommended way to enable vjass support in a non-Reforged editor nowadays)?

Also, does this support all versions of Jass, like 1.27b, 1.31.1, and 2.04?

Asking because I want to learn wc3 trigger scripting and use something compatible with legacy/classic patches aka jass, but I don't want to have to learn/write actual jass, so maybe vjass or zinc.

P.S., I notice the Github and VScode extension page are all in Chinese. Would be nice if there was some English documentation so that I don't have to rely on Google Translate.
 
Judging from the Github page, is this meant to be used with YDWE (and I'm assuming that's the recommended way to enable vjass support in a non-Reforged editor nowadays)?

Also, does this support all versions of Jass, like 1.27b, 1.31.1, and 2.04?

Asking because I want to learn wc3 trigger scripting and use something compatible with legacy/classic patches aka jass, but I don't want to have to learn/write actual jass, so maybe vjass or zinc.

P.S., I notice the Github and VScode extension page are all in Chinese. Would be nice if there was some English documentation so that I don't have to rely on Google Translate.
Send me an email address, and I’ll send over the English versions of all the documents, the VS Code extension features, and the framework documentation.

Judging from the Github page, is this meant to be used with YDWE (and I'm assuming that's the recommended way to enable vjass support in a non-Reforged editor nowadays)?

Also, does this support all versions of Jass, like 1.27b, 1.31.1, and 2.04?

Asking because I want to learn wc3 trigger scripting and use something compatible with legacy/classic patches aka jass, but I don't want to have to learn/write actual jass, so maybe vjass or zinc.

P.S., I notice the Github and VScode extension page are all in Chinese. Would be nice if there was some English documentation so that I don't have to rely on Google Translate.
YDWE is not really required. It is only used here as one possible testing setup for the map.

The standalone compiler can compile the code directly and then package it into the map. If you have a different way to test your map, feel free to modify the .sh script accordingly.

It should support almost every Warcraft III version. Since Reforged is backward compatible, anything that works on older versions should work on Reforged as well.
 
Back
Top