• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Release] WurstScript - A Wurst to Jass Compiler & IDE

Status
Not open for further replies.

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
Hiho,
...
Wouldn't it be nice to introduce the legendary C++ RAII into the language where you can create those variables on the stack and which are automatically deleted by an automated call to their destructor at the end of the scope.

Hey Robbepop, I am happy that you are considering using Wurst.

I am also happy that someone suggest the RAII principle, as I thought about this quite often. So there are a few reasons why it is not in the language already:

  1. Cokemonkey already mentioned tuple-types, which cover many important cases. I did not see many cases of short-lived objects yet.
  2. RAII cannot be as powerful as in C++, since Jass does not have references to locations on the stack. So basically that would mean that method calls would have to be inlined or use some form of copying similar to what tuple-types are using at the moment.
  3. As another example consider an ArrayList (or Vector in C++). An ArrayList on the Heap has to share the same Array for all instances, because Arrays cannot be created dynamically in Jass. However local arrays are created dynamically, so you would ideally use a different kind of ArrayList for RAII-uses.
  4. Another aspect to consider is the complexity of the language. Adding another feature on how to create local variables can be confusing I think. At least in C++ the Syntax for RAII is really bad. I much prefer the scope keyword that was in D. But to really make it safe, you would need a better type-system like in Rust. And I don't think I want a complex type-system in Wurst. On the other hand we already allow use-after-free bugs when using manual memory management, so it would only be a little bit worse than now. The problem is that this has to be done right in the first try, because it is not possible to make the type-rules more strict, when the feature is already being used.

So:

  • I would really like to have some notion of ownership/RAII in Wurst, together with automatic clean-up. Not only for local variables, but also for fields.
  • I think the optimization to put variables on the Stack is only worth it in very few cases. So I would keep this as an additional feature for the optimizer.
  • I don't know yet how to integrate this into Wurst, without making the language more complicated, and without creating a feature that no one understands or wants to use.
 
Level 11
Joined
Mar 6, 2008
Messages
898
Hiho,

thank you very much for your fast and interesting response!

I think it is amazing that you already thought about implementing such a feature (or similar) into WurstScript before.

The main use cases where the RAII idiom would make sense in WurstScript with the underlying model of JASS would be only for smaller types such as vec2/3, a point, maybe smart_pointers which automatically perform the desturction at the end of scope for heap-objects etc.

One could also apply a similar system like in the D language where there are classes for ref-based and polymorphism constructs and structs for value based non-inheritance object types.

I think it would be awesome if WurstScript could support this in an efficient way.
Besides that the manual memory management isn't something a modern programming language should default to. (delete xy at the end of a scope)

So if it isn't possible to create things on the stack without too much work on the compiler it would at least be nice to introduce so-called smart pointers which could serve as a building bridge.
However, as far as I imagine their implementation, the way they would work would make them an exception in the language and exceptions should always be avoided.

I can see that this isn't an easy task but I really think that it is worth some thoughts and iterations as the performance of local variables (when doing the mentioned optimizations) should really beat the "heap simulated" array-based reference versions.
The compiler should also be able to enforce that no programmer will ever take the reference of a temporary object which is based on local variables due to this optimization.

Regards,
Robbepop
 
Level 8
Joined
Nov 20, 2011
Messages
202
New Wurstscript all in one pack:

New version features:
-Better minimal eclipse, with proper icons and less size
-Upgraded from eclipse Luna to Eclipse Mars
-New optional batchfiles to start Wurstpack and eclipse as administrator
-Decreased compressed size by 19MB from 156MB to 137MB
-Deleted sample project and workspace since the wurst.dependenices file does not support relative pathes
-Added missing checksums.md5 file to the Wurstpack folder which could result in unwanted behavior

DOWNLOAD
 
Level 11
Joined
Mar 6, 2008
Messages
898
Awesome that you are still updating this tool!
In about three weeks I am having holidays and can't wait to finally create my first Wurstscript featured map!

Regards,
Robbepop

edit:

I have a question related to WurstScript.
Just started to learn the language and wanted to create a small object oriented observer pattern.
After a short time I realized that code-parameters dont take closures and I couldnt call a method with a lamda expression in it, so I had to go a static way which is kind of ugly.

JASS:
package Init

import ArrayList
import Trigger

interface ChatEventListener
	function onChatEvent(string content)

class HelloWorld implements ChatEventListener
	function onChatEvent(string content)
		print("Hello, World! -> " + content)

class DoublePrinter implements ChatEventListener
	function onChatEvent(string content)
		print(content + content)

var chatEvent = CreateTrigger()
var chatEventListeners = new ArrayList<ChatEventListener>()

function notifyChatEventListeners()
	let iterator = chatEventListeners.iterator()
	for listener from iterator
		listener.onChatEvent(GetEventPlayerChatString())
	iterator.close()

init
	chatEvent.registerPlayerChatEvent(Player(0), "", false)
	chatEvent.addAction(() -> notifyChatEventListeners())
	chatEventListeners.add(new HelloWorld())
	chatEventListeners.add(new DoublePrinter())
	print("init done")

Can you tell me how to do that properly in wurst?


edit:
some utility functions which I am missing in the standards librbary which is apart from that truely awesome:
- sort function for the built-in containers based on a good sorting algorithm, for example quick sort or merge sort.
- functional utility functions for functional programming like map, reduce, etc.
- (binary) search for all built-in container types
- more string manipulation functions like string-split
 
Last edited:
Level 5
Joined
Dec 1, 2008
Messages
120
Downloaded the "all-in-one-pack" above, trying to save a map:

attachment.php


I do have Java installed. No other application has problems finding it.
 

Attachments

  • nojava.jpg
    nojava.jpg
    49.2 KB · Views: 379
Level 8
Joined
Nov 20, 2011
Messages
202
Downloaded the "all-in-one-pack" above, trying to save a map:

attachment.php


I do have Java installed. No other application has problems finding it.

Have you enabled the compilation server? If yes try it without.

Would you mind to send me the content of the wehack.lua in your wurstpack folder?


@Robbepop

This should work:

JASS:
package Init

import ArrayList
import Trigger

interface ChatEventListener
    function onChatEvent(string content

var chatEvent = CreateTrigger()
var chatEventListeners = new ArrayList<ChatEventListener>()

function notifyChatEventListeners()
    let iterator = chatEventListeners.iterator()
    for listener from iterator
        listener.onChatEvent(GetEventPlayerChatString())
    iterator.close()

init
    chatEvent.registerPlayerChatEvent(Player(0), "", false)
    chatEvent.addAction(() -> notifyChatEventListeners())
    chatEventListeners.add((string content) ->  print("Hello, World! -> " + content))
    chatEventListeners.add((string content) ->  print(content + content))
    print("init done")

For custom events take a look at: http://www.hiveworkshop.com/forums/lab-715/custom-closure-events-242901/
 
Level 5
Joined
Dec 1, 2008
Messages
120
I didn't have the server enabled.

I figured out what happened. I downloaded the pack, tried saving a map and it gave me error:
"Error in File line -1: Could not find lib-package Wurst".

So in order to fix that, I ran the updater. It said I have changed wehack.lua and asked if I wanted overwrite it. So I did.

Here's the wehack.lua after overwriting with updater:
Code:
-- This file is executed once on we start up.  The state perseveres
-- through callbacks
--
-- wehack.runprocess:  Wait for exit code, report errors (grimext)
-- wehack.runprocess2:  Wait for exit code, don't report errors (jasshelper)
-- wehack.execprocess:  Don't wait for exit code (War3)
--
grimregpath = "Software\\Grimoire\\"
--warcraftdir = grim.getregpair(grimregpath,"War3InstallPath")
--if warcraftdir == 0 then
--	wehack.messagebox("Error, could not find warcraft install path in wehack.lua")
--end

isstartup = true
grimdir = grim.getcwd()
dofile("wehacklib.lua")
dofile("findpath.lua")
if path==0 or path=="" then
	path = "."
end
mapvalid = true
cmdargs = "" -- used to execute external tools on save

confregpath = "HKEY_CURRENT_USER\\Software\\Grimoire\\"

haveext = grim.exists("grimext\\grimex.dll")
if haveext then
	utils = wehack.addmenu("Extensions")
end

whmenu = wehack.addmenu("Grimoire")
wh_window = TogMenuEntry:New(whmenu,"Start War3 with -window",nil,true)
wh_opengl = TogMenuEntry:New(whmenu,"Start War3 with -opengl",nil,false)
if not grim.isnewcompiler(path.."\\war3.exe") then
  wh_grimoire = TogMenuEntry:New(whmenu,"Start War3 with Grimoire",nil,true)
  wh_enablewar3err = TogMenuEntry:New(whmenu,"Enable war3err",nil,true)
  wh_enablejapi = TogMenuEntry:New(whmenu,"Enable japi",nil,false)
  --wh_machine = TogMenuEntry:New(whmenu,"Enable warmachine",nil,false)
end
wehack.addmenuseparator(whmenu)
wh_tesh = TogMenuEntry:New(whmenu,"Enable TESH",nil,true)
if grim.isdotnetinstalled() then
	wh_colorizer = TogMenuEntry:New(whmenu,"Enable Colorizer",nil,true)
end
wh_nolimits = TogMenuEntry:New(whmenu,"Enable no limits",
	function(self) grim.nolimits(self.checked) end,false)
wh_oehack = TogMenuEntry:New(whmenu,"Enable object editor hack",
	function(self) grim.objecteditorhack(self.checked) end,true)
wh_syndisable = TogMenuEntry:New(whmenu,"Disable WE syntax checker",
	function(self) grim.syndisable(self.checked) end,true)
wh_descpopup = TogMenuEntry:New(whmenu,"Disable default description nag",
	function(self) grim.descpopup(self.checked) end,true)
wh_autodisable = TogMenuEntry:New(whmenu,"Don't let WE disable triggers",
	function(self) grim.autodisable(self.checked) end,true)
wh_alwaysenable = TogMenuEntry:New(whmenu,"Always allow trigger enable",
	function(self) grim.alwaysenable(self.checked) end,true)
wh_disablesound = TogMenuEntry:New(whmenu,"Mute editor sounds",nil,true)
wh_firstsavenag = TogMenuEntry:New(whmenu,"Disable first save warning",nil,true)

wehack.addmenuseparator(whmenu)

usetestmapconf = (grim.getregpair(confregpath,"Use custom test map settings") == "on")
function testmapconfig()
	usetestmapconf = wehack.testmapconfig(path,usetestmapconf)
	if usetestmapconf then
		grim.setregstring(confregpath,"Use custom test map settings","on")
	else
		grim.setregstring(confregpath,"Use custom test map settings","off")
	end
end
wh_configtest = MenuEntry:New(whmenu,"Customize test map settings",testmapconfig);

function attachdebugger()
	wehack.execprocess("w3jdebug\\pyw3jdebug.exe")
end
havedebugger = grim.exists("w3jdebug\\pyw3jdebug.exe")
if havedebugger then
    wh_debug = MenuEntry:New(whmenu,"Attach debugger",attachdebugger)
end

function aboutpopup()
	wehack.showaboutdialog("Grimoire 1.5")
end
wh_about = MenuEntry:New(whmenu,"About Grimoire ...",aboutpopup)

function wurst_compilationserver_start()
	wehack.execprocess("wurstscript\\wurstscript.exe --startServer")
end

function wurst_compilationserver_stop()
	wehack.execprocess("wurstscript\\wurstscript_b.exe -stopServer")
end

-- ##WurstScript##
havewurst = grim.exists("wurstscript\\wurstscript.exe")
if havewurst then
	wurstmenu = wehack.addmenu("WurstScript")
	
	function wurst_command()
		if wurst_b_enable.checked then
			return "wurstscript\\wurstscript_b.exe"
		else
			return "wurstscript\\wurstscript.exe"
		end
	end
	
	
	wurst_enable = TogMenuEntry:New(wurstmenu,"Enable WurstScript",nil,true)
	wurst_b_enable = TogMenuEntry:New(wurstmenu,"Use Wurst compilation server",nil,false)
	
	
	
	
	-- TODO
	-- MenuEntry:New(wurstmenu,"Start Wurst compilation server ",wurst_compilationserver_start)
	MenuEntry:New(wurstmenu,"Stop Wurst compilation server ",wurst_compilationserver_stop)
	
	wehack.addmenuseparator(wurstmenu)
	-- optimizer options
	wurst_optenable = TogMenuEntry:New(wurstmenu,"Enable Froptimizer",nil,false)
	wurst_localoptenable = TogMenuEntry:New(wurstmenu,"Enable (experimental) local optimizations",nil,false)
	wurst_inliner = TogMenuEntry:New(wurstmenu, "Enable Inliner",nil,false)
	
	wehack.addmenuseparator(wurstmenu)
	
	-- debug options
	wurst_stacktraces = TogMenuEntry:New(wurstmenu, "Enable stack-traces",nil,false)
	wurst_uncheckedDispatch = TogMenuEntry:New(wurstmenu, "Enable unchecked dispatch",nil,false)
	wurst_nodebug = TogMenuEntry:New(wurstmenu, "Disable debug messages",nil,false)
	wurst_debug = TogMenuEntry:New(wurstmenu,"Debug Mode",nil,false)
	
	wehack.addmenuseparator(wurstmenu)
	
	-- compiletime options
	wurst_compiletimefunctions  = TogMenuEntry:New(wurstmenu, "Run compiletime functions",nil,false)
	wurst_injectObjects  = TogMenuEntry:New(wurstmenu, "Inject compiletime objects",nil,false)
	
	wehack.addmenuseparator(wurstmenu)
	
	-- other tools
	wurst_useJmpq2 = TogMenuEntry:New(wurstmenu, "Use JMpq-v2 (deprecated)",nil,false)
	
	
	function wurst_runfileexporter()
		curmap = wehack.findmappath()
		if curmap ~= "" then
			wehack.execprocess(wurst_command() .. " --extractImports \"" .. curmap .. "\"")
		else
			wehack.messagebox("No map loaded. Try saving the map first.","Wurst",false)
		end
	end
	
	MenuEntry:New(wurstmenu,"Extract all imported files",wurst_runfileexporter)

	wehack.addmenuseparator(wurstmenu)
	
	function wurstshowerr()
	  	wehack.execprocess(wurst_command() .. " --showerrors")
	end
	
	function wurstabout()
	  	wehack.execprocess(wurst_command() .. " --about")
	end
	
  -- TODO wurstshowerrm = MenuEntry:New(wurstmenu,"Show previous errors",wurstshowerr)
  wurstaboutm = MenuEntry:New(wurstmenu,"About WurstScript ...",wurstabout)
end





-- ##EndWurstScript##

-- ## Jasshelper ##
--Here I'll add the custom menu to jasshelper. moyack
jh_path = "vexorian"
havejh = grim.exists("vexorianjasshelper\\jasshelper.exe")
if havejh then
	jhmenu = wehack.addmenu("JassHelper")
	jh_enable = TogMenuEntry:New(jhmenu,"Enable JassHelper",nil,true)

	
	
	wehack.addmenuseparator(jhmenu)
	jh_debug = TogMenuEntry:New(jhmenu,"Debug Mode",nil,false)
	jh_disable = TogMenuEntry:New(jhmenu,"Disable vJass syntax",nil,false)
    jh_disableopt = TogMenuEntry:New(jhmenu,"Disable script optimization",nil,false)

	wehack.addmenuseparator(jhmenu)
	
	function jhshowerr()
	  	wehack.execprocess(jh_path.."jasshelper\\jasshelper.exe --showerrors")
	end
	
	function jhabout()
	  	wehack.execprocess(jh_path.."jasshelper\\jasshelper.exe --about")
	end
	
	jhshowerrm = MenuEntry:New(jhmenu,"Show previous errors",jhshowerr)
	jhaboutm = MenuEntry:New(jhmenu,"About JassHelper ...",jhabout)
	
	
	function jhshowhelp()
		jhsetpath()
		wehack.execprocess("starter.bat ./"..jh_path.."jasshelper\\jasshelpermanual.html")
	end
	
	jhhelp = MenuEntry:New(jhmenu, "JassHelper Documentation...", jhshowhelp)
end
-- # end jasshelper #

-- # begin sharpcraft #
haveSharpCraft = grim.exists("SharpCraft\\SharpCraft.exe")
if haveSharpCraft then
	sharpCraftMenu = wehack.addmenu("SharpCraft")
	sharpCraftEnable = TogMenuEntry:New(sharpCraftMenu,"Run with ShapCraft",nil,true)
end
-- # end sharpcraft #



function initshellext()
    local first, last = string.find(grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit.Scenario\\shell\\open\\command\\", ""),"NewGen",1)
    if first then
        wehack.checkmenuentry(shellext.menu,shellext.id,1)
    else
    		local second, third = string.find(grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit.Scenario\\shell\\open\\command\\", ""),".bat",1)
    		if second then
    			wehack.checkmenuentry(shellext.menu,shellext.id,1)
    		else
        	wehack.checkmenuentry(shellext.menu,shellext.id,0)
        end
    end
end

function fixopencommand(disable,warpath,grimpath,filetype)
    --local curval = grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","")
    --if curval ~= 0 then
    --    if disable then
    --        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","",string.gsub(curval, "%%L", "%%1"))
    --    else
    --        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","",string.gsub(curval, "%%1", "%%L"))
    --    end
    --end
    
    local wepath = "\""..grimpath.."\\NewGen WE.exe\""
    if not grim.exists(grimpath.."\\NewGen WE.exe") then
      wepath = "\""..grimpath.."\\we.bat\""
    end
    if disable then
    	grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","","\""..warpath.."\\World Editor.exe\" -loadfile \"%L\"")
    else
    	grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","",wepath.." -loadfile \"%L\"")
    end
end

function registerextension(disable,warpath,grimpath,filetype,istft)
    if disable then
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\command\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\command\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\opengl\\command\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\opengl\\");
    else
        --if istft then
        --    gamepath = "\""..warpath.."\\Frozen Throne.exe\""
        --else
        --    gamepath = "\""..warpath.."\\Warcraft III.exe\""
        --end
        --grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\","","Play Fullscreen")
        --grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\command\\","",gamepath.." -loadfile \"%L\"")
        --grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\","","Play Windowed")
        --grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\command\\","",gamepath.." -window -loadfile \"%L\"")

        local gamepath = "\""..grimpath.."\\NewGen Warcraft.exe\""
        if not grim.exists(grimpath.."\\NewGen Warcraft.exe") then
	        gamepath = "\""..grimpath.."\\startwar3.bat\""
	      end
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\","","Play Fullscreen")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\command\\","",gamepath.." -loadfile \"%L\"")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\","","Play Windowed")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\command\\","",gamepath.." -window -loadfile \"%L\"")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\opengl\\","","Play With OpenGL")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\opengl\\command\\","",gamepath.." -window -opengl -loadfile \"%L\"")
    end
end

function toggleshellext()
    local istft = (grim.getregpair("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "InstallPathX") ~= 0)
    local first, last = string.find(grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit.Scenario\\shell\\open\\command\\", ""),"NewGen",1)
    local found = false
    if first then
    	found = true
    else
    		local second, third = string.find(grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit.Scenario\\shell\\open\\command\\", ""),".bat",1)
    		if second then
    			found = true
    		end
    end

    if path ~= 0 and grimdir ~= 0 then
        fixopencommand(found,path,grimdir,"Scenario")
        registerextension(found,path,grimdir,"Scenario",istft)
        fixopencommand(found,path,grimdir,"ScenarioEx")
        registerextension(found,path,grimdir,"ScenarioEx",istft)
        fixopencommand(found,path,grimdir,"Campaign")
        registerextension(found,path,grimdir,"Campaign",istft)
        fixopencommand(found,path,grimdir,"AIData")
        if found then
            wehack.checkmenuentry(shellext.menu,shellext.id,0)
        else
            wehack.checkmenuentry(shellext.menu,shellext.id,1)
        end
    end
end

function initlocalfiles()
    if grim.getregpair("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "Allow Local Files") == 0 then
        wehack.checkmenuentry(localfiles.menu,localfiles.id,0)
    else
        wehack.checkmenuentry(localfiles.menu,localfiles.id,1)
    end
end

function togglelocalfiles()
    if grim.getregpair("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "Allow Local Files") == 0 then
        grim.setregdword("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "Allow Local Files", 1)
        wehack.checkmenuentry(localfiles.menu,localfiles.id,1)
    else
        grim.setregdword("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "Allow Local Files", 0)
        wehack.checkmenuentry(localfiles.menu,localfiles.id,0)
    end
end

function runobjectmerger(mode)
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.openfiledialog("Unit files (*.w3u)|*.w3u|Item files (*.w3t)|*w3t|Doodad files (*.w3d)|*.w3d|Destructable files (*.w3b)|*.w3b|Ability files (*.w3a)|*.w3a|Buff files (*.w3h)|*.w3h|Upgrade files (*.w3q)|*.w3q|", "w3a", "Select files to import ...", true)
grim.log("got in lua: " .. source)
        if source ~= "" then
            list = strsplit("|", source);
--            cmdargs = "ObjectMerger \""..curmap.."\" "..wehack.getlookupfolders().." "..mode..fileargsjoin(list)        
            cmdargs = "grimext\\ObjectMerger.exe \""..curmap.."\" "..wehack.getlookupfolders().." "..mode..fileargsjoin(list)
grim.log("assembled cmdline: " .. cmdargs)
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
grim.log("called saved map")
        end
    else
    	showfirstsavewarning()
    end
end

function runconstantmerger()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.openfiledialog("Text files (*.txt)|*.txt|", "txt", "Select files to import ...", true)
        if source ~= "" then
            list = strsplit("|", source);
--            cmdargs = "ConstantMerger \""..curmap.."\" "..wehack.getlookupfolders()..fileargsjoin(list)
            cmdargs = "grimext\\ConstantMerger.exe \""..curmap.."\" "..wehack.getlookupfolders()..fileargsjoin(list)
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
        end
    else
    	showfirstsavewarning()
    end
end

function runtriggermerger()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.openfiledialog("GUI Trigger files (*.wtg)|*.wtg|Custom Text Trigger files (*.wct)|*wct|", "wtg", "Select trigger data to import ...", true)
        if source ~= "" then
            list = strsplit("|", source);
--            cmdargs = "TriggerMerger \""..curmap.."\" "..wehack.getlookupfolders()..fileargsjoin(list)
            cmdargs = "grimext\\TriggerMerger.exe \""..curmap.."\" "..wehack.getlookupfolders()..fileargsjoin(list)
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
        end
    else
    	showfirstsavewarning()
    end
end

function runfileimporterfiles()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.openfiledialog("All files (*.*)|*.*|", "*", "Select files to import ...", true)
        if source ~= "" then
            list = strsplit("|", source);
            inmpqpath = wehack.inputbox("Specify the target path ...","FileImporter","Units\\")
--            cmdargs = "FileImporter \""..curmap.."\" "..wehack.getlookupfolders()..argsjoin(inmpqpath,list)
            cmdargs = "grimext\\FileImporter.exe \""..curmap.."\" "..wehack.getlookupfolders()..argsjoin(inmpqpath,list)
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
        end
    else
    	showfirstsavewarning()
    end
end

function runfileimporterdir()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.browseforfolder("Select the source directory ...")
        if source ~= "" then
--            cmdargs = "FileImporter \""..curmap.."\" "..wehack.getlookupfolders().." \""..source.."\""
            cmdargs = "grimext\\FileImporter.exe \""..curmap.."\" "..wehack.getlookupfolders().." \""..source.."\""
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
        end
    else
    	showfirstsavewarning()
    end
end

function runfileexporter()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        target = wehack.browseforfolder("Select the target directory ...")
        if target ~= "" then
--        		wehack.rungrimextool("FileExporter", curmap, removequotes(wehack.getlookupfolders()), target)
            wehack.runprocess("grimext\\FileExporter.exe \""..curmap.."\" "..wehack.getlookupfolders().." \""..target.."\"")
        end
    else
    	showfirstsavewarning()
    end
end

function runtilesetter()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        map = wehack.openarchive(curmap,15)
        oldtiles = wehack.getcurrenttiles()
        wehack.closearchive(map)
        if oldtiles ~= "" then
        		newtiles = wehack.tilesetconfig(string.sub(oldtiles,1,1), string.sub(oldtiles,2))
        		if newtiles ~= "" then
        			tileset = string.sub(newtiles,1,1)
        			tiles = string.sub(newtiles,2)
							if tileset ~= "" and tiles ~= "" then
--								cmdargs = "TileSetter \""..curmap.."\" "..wehack.getlookupfolders().." "..tileset.." "..tiles
								cmdargs = "grimext\\TileSetter.exe \""..curmap.."\" "..wehack.getlookupfolders().." "..tileset.." "..tiles
								wehack.savemap()
        			end
        		end
        		
--            tileset = wehack.inputbox("Specify the tileset ...","TileSetter",string.sub(oldtiles,1,1))
--            if tileset ~= "" then
--                tiles = wehack.inputbox("Specify the tile list ...","TileSetter",string.sub(oldtiles,2))
--                if tiles ~= "" then
--                    cmdargs = "grimext\\TileSetter.exe \""..curmap.."\" "..wehack.getlookupfolders().." "..tileset.." "..tiles
--                    wehack.savemap()
--                end
--            end
        end
    else
    	showfirstsavewarning()
    end
end

function showfirstsavewarning()
	if wh_firstsavenag.checked then
		return
	else
		local msg = "Could not find path to map, please try saving again"
		if wurst_b_enable.checked then
			msg = msg .. "\nMake sure that the compilation server is running or try disabling the server!"
		end
		wehack.messagebox(msg,"Grimoire",false)
	end
end

function testmap(cmdline)
	--if havewurst and wurst_enable.checked and not mapvalid then
	--	return
	--end
	--wehack.messagebox(cmdline)
	--mappath = strsplit(" ",cmdline)[2]
	--compilemap_path(mappath)
	
	if haveSharpCraft and sharpCraftEnable.checked then
		-- remove default .exe
		local pos = string.find(cmdline, ".exe")
		cmdline = string.sub(cmdline, 4 + pos)
		-- replace with SharpCraft exe
		cmdline = "SharpCraft\\SharpCraft.exe -game " .. cmdline
	end
	
	if wh_opengl.checked then
		cmdline = cmdline .. " -opengl"
	end
	if wh_window.checked then
		cmdline = cmdline .. " -window"
	end
	wehack.execprocess(cmdline)
end

function compilemap_path(mappath,tries)
	if mappath == "" then
		showfirstsavewarning()
		return
	end
	map = wehack.openarchive(mappath,15)
	wehack.extractfile("wurstscript\\common.j","scripts\\common.j")
	wehack.extractfile("wurstscript\\Blizzard.j","scripts\\Blizzard.j")
	wehack.extractfile(jh_path.."jasshelper\\common.j","scripts\\common.j")
	wehack.extractfile(jh_path.."jasshelper\\Blizzard.j","scripts\\Blizzard.j")
	wehack.extractfile("war3map.j","war3map.j")
	wehack.closearchive(map)
	if cmdargs ~= "" then
		local cmdtable = argsplit(cmdargs)
--		local len = table.getn(cmdtable)
--		for i = 1, len do
--			cmdtable[i] = removequotes(cmdtable[i])
--		end
--		wehack.rungrimextool(cmdtable)
grim.log("running tool on save: "..cmdargs)
		wehack.runprocess(cmdargs)
		cmdargs = ""
	end
	
	if havejh and jh_enable.checked then
		cmdline = jh_path .. "jasshelper\\jasshelper.exe"
		if jh_debug.checked then
			cmdline = cmdline .. " --debug"
		end
		if jh_disable.checked then
			cmdline = cmdline .. " --nopreprocessor"
		end
		if jh_disableopt.checked then
			cmdline = cmdline .. " --nooptimize"
		end
		cmdline = cmdline .. " "..jh_path.."jasshelper\\common.j "..jh_path.."jasshelper\\blizzard.j \"" .. mappath .."\""
		toolresult = 0
		toolresult = wehack.runprocess(cmdline)
		if toolresult == 0 then 
			mapvalid = true
		else
			mapvalid = false
		end
	end
	
	if havewurst and wurst_enable.checked then
		cmdline = wurst_command()
		cmdline = cmdline .. " -gui"
		if wurst_debug.checked then
			--cmdline = cmdline .. " --debug"
		end
		--if wurst_disable.checked then
			--cmdline = cmdline .. " --nopreprocessor"
		--end
		if wurst_optenable.checked then
			cmdline = cmdline .. " -opt"
		end
		if wurst_localoptenable.checked then
			cmdline = cmdline .. " -localOptimizations"
		end
		if wurst_inliner.checked then
			cmdline = cmdline .. " -inline"
		end
		if wurst_stacktraces.checked then
			cmdline = cmdline  .. " -stacktraces"
		end
		if wurst_uncheckedDispatch.checked then
			cmdline = cmdline .. " -uncheckedDispatch"
		end
		if wurst_nodebug.checked then
			cmdline = cmdline  .. " -nodebug"
		end
		if wurst_compiletimefunctions.checked then
			cmdline = cmdline .. " -runcompiletimefunctions"
		end
		if wurst_injectObjects.checked then
			cmdline = cmdline .. " -injectobjects"
		end
		if wurst_useJmpq2.checked then
			cmdline = cmdline .. " --jmpq2"
		end
		
		-- cmdline = cmdline .. " -lib ./wurstscript/lib/"
		cmdline = cmdline .. " wurstscript\\common.j wurstscript\\Blizzard.j \"" .. mappath .."\""
		
		toolresult = 0
--		if wurst_fast ~= nil and wurst_fast.checked then
--			toolresult = wehack.runjasshelper(wurst_debug.checked, wurst_disable.checked, "jasshelper\\common.j", "jasshelper\\blizzard.j", mappath, "")
--		else
			toolresult = wehack.runprocess2(cmdline)
--		end
		if toolresult == 0 then 
			mapvalid = true
		else 
			mapvalid = false
			if wurst_b_enable.checked then
				if tries == 0 then
					-- try starting compilation server
					wurst_compilationserver_start()
					-- try again
					compilemap_path(mappath,tries+1)
				else
					wehack.messagebox("Could not run Wurst with compilation server.","Wurst",false)
				end
			else
				wehack.messagebox("Could not run Wurst.","Wurst",false)
			end
		end
	end
end

function compilemap()
	mappath = wehack.findmappath()
	compilemap_path(mappath,0)
end

if haveext then
    localfiles = MenuEntry:New(utils,"Enable Local Files",togglelocalfiles)
    shellext = MenuEntry:New(utils,"Register Shell Extensions",toggleshellext)
    initlocalfiles()
    initshellext()
    wehack.addmenuseparator(utils)
end
if haveext and grim.exists("grimext\\tilesetter.exe") then
    tilesetter = MenuEntry:New(utils,"Edit Tileset",runtilesetter)
end
if haveext and grim.exists("grimext\\fileexporter.exe") then
    fileexporter = MenuEntry:New(utils,"Export Files",runfileexporter)
end
if haveext and grim.exists("grimext\\fileimporter.exe") then
    fileimporterdir = MenuEntry:New(utils,"Import Directory",runfileimporterdir)
    fileimporterfiles = MenuEntry:New(utils,"Import Files",runfileimporterfiles)
end
if haveext and grim.exists("grimext\\objectmerger.exe") then
    objectmerger = MenuEntry:New(utils,"Merge Object Editor Data",function(self) runobjectmerger("m") end)
    objectreplacer = MenuEntry:New(utils,"Replace Object Editor Data",function(self) runobjectmerger("r") end)
    objectimporter = MenuEntry:New(utils,"Import Object Editor Data",function(self) runobjectmerger("i") end)
end
if haveext and grim.exists("grimext\\constantmerger.exe") then
    constantmerger = MenuEntry:New(utils,"Merge Constants Data",runconstantmerger)
end
if haveext and grim.exists("grimext\\triggermerger.exe") then
    triggermerger = MenuEntry:New(utils,"Merge Trigger Data",runtriggermerger)
end

function extabout()
    grim.openlink("http://www.wc3campaigns.net")
end
if haveext then
	wehack.addmenuseparator(utils)
  aboutextensions = MenuEntry:New(utils,"About Grimex ...",extabout)
end



isstartup = false

EDIT: Batch script to start Eclipse has typo in it's name.
 
Last edited:
Level 8
Joined
Nov 20, 2011
Messages
202
I didn't have the server enabled.

I figured out what happened. I downloaded the pack, tried saving a map and it gave me error:
"Error in File line -1: Could not find lib-package Wurst".

So in order to fix that, I ran the updater. It said I have changed wehack.lua and asked if I wanted overwrite it. So I did.

Here's the wehack.lua after overwriting with updater:
Code:
-- This file is executed once on we start up.  The state perseveres
-- through callbacks
--
-- wehack.runprocess:  Wait for exit code, report errors (grimext)
-- wehack.runprocess2:  Wait for exit code, don't report errors (jasshelper)
-- wehack.execprocess:  Don't wait for exit code (War3)
--
grimregpath = "Software\\Grimoire\\"
--warcraftdir = grim.getregpair(grimregpath,"War3InstallPath")
--if warcraftdir == 0 then
--	wehack.messagebox("Error, could not find warcraft install path in wehack.lua")
--end

isstartup = true
grimdir = grim.getcwd()
dofile("wehacklib.lua")
dofile("findpath.lua")
if path==0 or path=="" then
	path = "."
end
mapvalid = true
cmdargs = "" -- used to execute external tools on save

confregpath = "HKEY_CURRENT_USER\\Software\\Grimoire\\"

haveext = grim.exists("grimext\\grimex.dll")
if haveext then
	utils = wehack.addmenu("Extensions")
end

whmenu = wehack.addmenu("Grimoire")
wh_window = TogMenuEntry:New(whmenu,"Start War3 with -window",nil,true)
wh_opengl = TogMenuEntry:New(whmenu,"Start War3 with -opengl",nil,false)
if not grim.isnewcompiler(path.."\\war3.exe") then
  wh_grimoire = TogMenuEntry:New(whmenu,"Start War3 with Grimoire",nil,true)
  wh_enablewar3err = TogMenuEntry:New(whmenu,"Enable war3err",nil,true)
  wh_enablejapi = TogMenuEntry:New(whmenu,"Enable japi",nil,false)
  --wh_machine = TogMenuEntry:New(whmenu,"Enable warmachine",nil,false)
end
wehack.addmenuseparator(whmenu)
wh_tesh = TogMenuEntry:New(whmenu,"Enable TESH",nil,true)
if grim.isdotnetinstalled() then
	wh_colorizer = TogMenuEntry:New(whmenu,"Enable Colorizer",nil,true)
end
wh_nolimits = TogMenuEntry:New(whmenu,"Enable no limits",
	function(self) grim.nolimits(self.checked) end,false)
wh_oehack = TogMenuEntry:New(whmenu,"Enable object editor hack",
	function(self) grim.objecteditorhack(self.checked) end,true)
wh_syndisable = TogMenuEntry:New(whmenu,"Disable WE syntax checker",
	function(self) grim.syndisable(self.checked) end,true)
wh_descpopup = TogMenuEntry:New(whmenu,"Disable default description nag",
	function(self) grim.descpopup(self.checked) end,true)
wh_autodisable = TogMenuEntry:New(whmenu,"Don't let WE disable triggers",
	function(self) grim.autodisable(self.checked) end,true)
wh_alwaysenable = TogMenuEntry:New(whmenu,"Always allow trigger enable",
	function(self) grim.alwaysenable(self.checked) end,true)
wh_disablesound = TogMenuEntry:New(whmenu,"Mute editor sounds",nil,true)
wh_firstsavenag = TogMenuEntry:New(whmenu,"Disable first save warning",nil,true)

wehack.addmenuseparator(whmenu)

usetestmapconf = (grim.getregpair(confregpath,"Use custom test map settings") == "on")
function testmapconfig()
	usetestmapconf = wehack.testmapconfig(path,usetestmapconf)
	if usetestmapconf then
		grim.setregstring(confregpath,"Use custom test map settings","on")
	else
		grim.setregstring(confregpath,"Use custom test map settings","off")
	end
end
wh_configtest = MenuEntry:New(whmenu,"Customize test map settings",testmapconfig);

function attachdebugger()
	wehack.execprocess("w3jdebug\\pyw3jdebug.exe")
end
havedebugger = grim.exists("w3jdebug\\pyw3jdebug.exe")
if havedebugger then
    wh_debug = MenuEntry:New(whmenu,"Attach debugger",attachdebugger)
end

function aboutpopup()
	wehack.showaboutdialog("Grimoire 1.5")
end
wh_about = MenuEntry:New(whmenu,"About Grimoire ...",aboutpopup)

function wurst_compilationserver_start()
	wehack.execprocess("wurstscript\\wurstscript.exe --startServer")
end

function wurst_compilationserver_stop()
	wehack.execprocess("wurstscript\\wurstscript_b.exe -stopServer")
end

-- ##WurstScript##
havewurst = grim.exists("wurstscript\\wurstscript.exe")
if havewurst then
	wurstmenu = wehack.addmenu("WurstScript")
	
	function wurst_command()
		if wurst_b_enable.checked then
			return "wurstscript\\wurstscript_b.exe"
		else
			return "wurstscript\\wurstscript.exe"
		end
	end
	
	
	wurst_enable = TogMenuEntry:New(wurstmenu,"Enable WurstScript",nil,true)
	wurst_b_enable = TogMenuEntry:New(wurstmenu,"Use Wurst compilation server",nil,false)
	
	
	
	
	-- TODO
	-- MenuEntry:New(wurstmenu,"Start Wurst compilation server ",wurst_compilationserver_start)
	MenuEntry:New(wurstmenu,"Stop Wurst compilation server ",wurst_compilationserver_stop)
	
	wehack.addmenuseparator(wurstmenu)
	-- optimizer options
	wurst_optenable = TogMenuEntry:New(wurstmenu,"Enable Froptimizer",nil,false)
	wurst_localoptenable = TogMenuEntry:New(wurstmenu,"Enable (experimental) local optimizations",nil,false)
	wurst_inliner = TogMenuEntry:New(wurstmenu, "Enable Inliner",nil,false)
	
	wehack.addmenuseparator(wurstmenu)
	
	-- debug options
	wurst_stacktraces = TogMenuEntry:New(wurstmenu, "Enable stack-traces",nil,false)
	wurst_uncheckedDispatch = TogMenuEntry:New(wurstmenu, "Enable unchecked dispatch",nil,false)
	wurst_nodebug = TogMenuEntry:New(wurstmenu, "Disable debug messages",nil,false)
	wurst_debug = TogMenuEntry:New(wurstmenu,"Debug Mode",nil,false)
	
	wehack.addmenuseparator(wurstmenu)
	
	-- compiletime options
	wurst_compiletimefunctions  = TogMenuEntry:New(wurstmenu, "Run compiletime functions",nil,false)
	wurst_injectObjects  = TogMenuEntry:New(wurstmenu, "Inject compiletime objects",nil,false)
	
	wehack.addmenuseparator(wurstmenu)
	
	-- other tools
	wurst_useJmpq2 = TogMenuEntry:New(wurstmenu, "Use JMpq-v2 (deprecated)",nil,false)
	
	
	function wurst_runfileexporter()
		curmap = wehack.findmappath()
		if curmap ~= "" then
			wehack.execprocess(wurst_command() .. " --extractImports \"" .. curmap .. "\"")
		else
			wehack.messagebox("No map loaded. Try saving the map first.","Wurst",false)
		end
	end
	
	MenuEntry:New(wurstmenu,"Extract all imported files",wurst_runfileexporter)

	wehack.addmenuseparator(wurstmenu)
	
	function wurstshowerr()
	  	wehack.execprocess(wurst_command() .. " --showerrors")
	end
	
	function wurstabout()
	  	wehack.execprocess(wurst_command() .. " --about")
	end
	
  -- TODO wurstshowerrm = MenuEntry:New(wurstmenu,"Show previous errors",wurstshowerr)
  wurstaboutm = MenuEntry:New(wurstmenu,"About WurstScript ...",wurstabout)
end





-- ##EndWurstScript##

-- ## Jasshelper ##
--Here I'll add the custom menu to jasshelper. moyack
jh_path = "vexorian"
havejh = grim.exists("vexorianjasshelper\\jasshelper.exe")
if havejh then
	jhmenu = wehack.addmenu("JassHelper")
	jh_enable = TogMenuEntry:New(jhmenu,"Enable JassHelper",nil,true)

	
	
	wehack.addmenuseparator(jhmenu)
	jh_debug = TogMenuEntry:New(jhmenu,"Debug Mode",nil,false)
	jh_disable = TogMenuEntry:New(jhmenu,"Disable vJass syntax",nil,false)
    jh_disableopt = TogMenuEntry:New(jhmenu,"Disable script optimization",nil,false)

	wehack.addmenuseparator(jhmenu)
	
	function jhshowerr()
	  	wehack.execprocess(jh_path.."jasshelper\\jasshelper.exe --showerrors")
	end
	
	function jhabout()
	  	wehack.execprocess(jh_path.."jasshelper\\jasshelper.exe --about")
	end
	
	jhshowerrm = MenuEntry:New(jhmenu,"Show previous errors",jhshowerr)
	jhaboutm = MenuEntry:New(jhmenu,"About JassHelper ...",jhabout)
	
	
	function jhshowhelp()
		jhsetpath()
		wehack.execprocess("starter.bat ./"..jh_path.."jasshelper\\jasshelpermanual.html")
	end
	
	jhhelp = MenuEntry:New(jhmenu, "JassHelper Documentation...", jhshowhelp)
end
-- # end jasshelper #

-- # begin sharpcraft #
haveSharpCraft = grim.exists("SharpCraft\\SharpCraft.exe")
if haveSharpCraft then
	sharpCraftMenu = wehack.addmenu("SharpCraft")
	sharpCraftEnable = TogMenuEntry:New(sharpCraftMenu,"Run with ShapCraft",nil,true)
end
-- # end sharpcraft #



function initshellext()
    local first, last = string.find(grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit.Scenario\\shell\\open\\command\\", ""),"NewGen",1)
    if first then
        wehack.checkmenuentry(shellext.menu,shellext.id,1)
    else
    		local second, third = string.find(grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit.Scenario\\shell\\open\\command\\", ""),".bat",1)
    		if second then
    			wehack.checkmenuentry(shellext.menu,shellext.id,1)
    		else
        	wehack.checkmenuentry(shellext.menu,shellext.id,0)
        end
    end
end

function fixopencommand(disable,warpath,grimpath,filetype)
    --local curval = grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","")
    --if curval ~= 0 then
    --    if disable then
    --        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","",string.gsub(curval, "%%L", "%%1"))
    --    else
    --        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","",string.gsub(curval, "%%1", "%%L"))
    --    end
    --end
    
    local wepath = "\""..grimpath.."\\NewGen WE.exe\""
    if not grim.exists(grimpath.."\\NewGen WE.exe") then
      wepath = "\""..grimpath.."\\we.bat\""
    end
    if disable then
    	grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","","\""..warpath.."\\World Editor.exe\" -loadfile \"%L\"")
    else
    	grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\open\\command\\","",wepath.." -loadfile \"%L\"")
    end
end

function registerextension(disable,warpath,grimpath,filetype,istft)
    if disable then
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\command\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\command\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\opengl\\command\\");
        grim.deleteregkey("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\opengl\\");
    else
        --if istft then
        --    gamepath = "\""..warpath.."\\Frozen Throne.exe\""
        --else
        --    gamepath = "\""..warpath.."\\Warcraft III.exe\""
        --end
        --grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\","","Play Fullscreen")
        --grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\command\\","",gamepath.." -loadfile \"%L\"")
        --grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\","","Play Windowed")
        --grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\command\\","",gamepath.." -window -loadfile \"%L\"")

        local gamepath = "\""..grimpath.."\\NewGen Warcraft.exe\""
        if not grim.exists(grimpath.."\\NewGen Warcraft.exe") then
	        gamepath = "\""..grimpath.."\\startwar3.bat\""
	      end
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\","","Play Fullscreen")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\fullscreen\\command\\","",gamepath.." -loadfile \"%L\"")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\","","Play Windowed")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\windowed\\command\\","",gamepath.." -window -loadfile \"%L\"")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\opengl\\","","Play With OpenGL")
        grim.setregstring("HKEY_CLASSES_ROOT\\WorldEdit."..filetype.."\\shell\\opengl\\command\\","",gamepath.." -window -opengl -loadfile \"%L\"")
    end
end

function toggleshellext()
    local istft = (grim.getregpair("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "InstallPathX") ~= 0)
    local first, last = string.find(grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit.Scenario\\shell\\open\\command\\", ""),"NewGen",1)
    local found = false
    if first then
    	found = true
    else
    		local second, third = string.find(grim.getregpair("HKEY_CLASSES_ROOT\\WorldEdit.Scenario\\shell\\open\\command\\", ""),".bat",1)
    		if second then
    			found = true
    		end
    end

    if path ~= 0 and grimdir ~= 0 then
        fixopencommand(found,path,grimdir,"Scenario")
        registerextension(found,path,grimdir,"Scenario",istft)
        fixopencommand(found,path,grimdir,"ScenarioEx")
        registerextension(found,path,grimdir,"ScenarioEx",istft)
        fixopencommand(found,path,grimdir,"Campaign")
        registerextension(found,path,grimdir,"Campaign",istft)
        fixopencommand(found,path,grimdir,"AIData")
        if found then
            wehack.checkmenuentry(shellext.menu,shellext.id,0)
        else
            wehack.checkmenuentry(shellext.menu,shellext.id,1)
        end
    end
end

function initlocalfiles()
    if grim.getregpair("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "Allow Local Files") == 0 then
        wehack.checkmenuentry(localfiles.menu,localfiles.id,0)
    else
        wehack.checkmenuentry(localfiles.menu,localfiles.id,1)
    end
end

function togglelocalfiles()
    if grim.getregpair("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "Allow Local Files") == 0 then
        grim.setregdword("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "Allow Local Files", 1)
        wehack.checkmenuentry(localfiles.menu,localfiles.id,1)
    else
        grim.setregdword("HKEY_CURRENT_USER\\Software\\Blizzard Entertainment\\Warcraft III\\", "Allow Local Files", 0)
        wehack.checkmenuentry(localfiles.menu,localfiles.id,0)
    end
end

function runobjectmerger(mode)
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.openfiledialog("Unit files (*.w3u)|*.w3u|Item files (*.w3t)|*w3t|Doodad files (*.w3d)|*.w3d|Destructable files (*.w3b)|*.w3b|Ability files (*.w3a)|*.w3a|Buff files (*.w3h)|*.w3h|Upgrade files (*.w3q)|*.w3q|", "w3a", "Select files to import ...", true)
grim.log("got in lua: " .. source)
        if source ~= "" then
            list = strsplit("|", source);
--            cmdargs = "ObjectMerger \""..curmap.."\" "..wehack.getlookupfolders().." "..mode..fileargsjoin(list)        
            cmdargs = "grimext\\ObjectMerger.exe \""..curmap.."\" "..wehack.getlookupfolders().." "..mode..fileargsjoin(list)
grim.log("assembled cmdline: " .. cmdargs)
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
grim.log("called saved map")
        end
    else
    	showfirstsavewarning()
    end
end

function runconstantmerger()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.openfiledialog("Text files (*.txt)|*.txt|", "txt", "Select files to import ...", true)
        if source ~= "" then
            list = strsplit("|", source);
--            cmdargs = "ConstantMerger \""..curmap.."\" "..wehack.getlookupfolders()..fileargsjoin(list)
            cmdargs = "grimext\\ConstantMerger.exe \""..curmap.."\" "..wehack.getlookupfolders()..fileargsjoin(list)
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
        end
    else
    	showfirstsavewarning()
    end
end

function runtriggermerger()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.openfiledialog("GUI Trigger files (*.wtg)|*.wtg|Custom Text Trigger files (*.wct)|*wct|", "wtg", "Select trigger data to import ...", true)
        if source ~= "" then
            list = strsplit("|", source);
--            cmdargs = "TriggerMerger \""..curmap.."\" "..wehack.getlookupfolders()..fileargsjoin(list)
            cmdargs = "grimext\\TriggerMerger.exe \""..curmap.."\" "..wehack.getlookupfolders()..fileargsjoin(list)
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
        end
    else
    	showfirstsavewarning()
    end
end

function runfileimporterfiles()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.openfiledialog("All files (*.*)|*.*|", "*", "Select files to import ...", true)
        if source ~= "" then
            list = strsplit("|", source);
            inmpqpath = wehack.inputbox("Specify the target path ...","FileImporter","Units\\")
--            cmdargs = "FileImporter \""..curmap.."\" "..wehack.getlookupfolders()..argsjoin(inmpqpath,list)
            cmdargs = "grimext\\FileImporter.exe \""..curmap.."\" "..wehack.getlookupfolders()..argsjoin(inmpqpath,list)
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
        end
    else
    	showfirstsavewarning()
    end
end

function runfileimporterdir()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        source = wehack.browseforfolder("Select the source directory ...")
        if source ~= "" then
--            cmdargs = "FileImporter \""..curmap.."\" "..wehack.getlookupfolders().." \""..source.."\""
            cmdargs = "grimext\\FileImporter.exe \""..curmap.."\" "..wehack.getlookupfolders().." \""..source.."\""
--            wehack.messagebox(cmdargs,"Grimoire",false)
            wehack.savemap()
        end
    else
    	showfirstsavewarning()
    end
end

function runfileexporter()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        target = wehack.browseforfolder("Select the target directory ...")
        if target ~= "" then
--        		wehack.rungrimextool("FileExporter", curmap, removequotes(wehack.getlookupfolders()), target)
            wehack.runprocess("grimext\\FileExporter.exe \""..curmap.."\" "..wehack.getlookupfolders().." \""..target.."\"")
        end
    else
    	showfirstsavewarning()
    end
end

function runtilesetter()
    curmap = wehack.findmappath()
    if curmap ~= "" then
        map = wehack.openarchive(curmap,15)
        oldtiles = wehack.getcurrenttiles()
        wehack.closearchive(map)
        if oldtiles ~= "" then
        		newtiles = wehack.tilesetconfig(string.sub(oldtiles,1,1), string.sub(oldtiles,2))
        		if newtiles ~= "" then
        			tileset = string.sub(newtiles,1,1)
        			tiles = string.sub(newtiles,2)
							if tileset ~= "" and tiles ~= "" then
--								cmdargs = "TileSetter \""..curmap.."\" "..wehack.getlookupfolders().." "..tileset.." "..tiles
								cmdargs = "grimext\\TileSetter.exe \""..curmap.."\" "..wehack.getlookupfolders().." "..tileset.." "..tiles
								wehack.savemap()
        			end
        		end
        		
--            tileset = wehack.inputbox("Specify the tileset ...","TileSetter",string.sub(oldtiles,1,1))
--            if tileset ~= "" then
--                tiles = wehack.inputbox("Specify the tile list ...","TileSetter",string.sub(oldtiles,2))
--                if tiles ~= "" then
--                    cmdargs = "grimext\\TileSetter.exe \""..curmap.."\" "..wehack.getlookupfolders().." "..tileset.." "..tiles
--                    wehack.savemap()
--                end
--            end
        end
    else
    	showfirstsavewarning()
    end
end

function showfirstsavewarning()
	if wh_firstsavenag.checked then
		return
	else
		local msg = "Could not find path to map, please try saving again"
		if wurst_b_enable.checked then
			msg = msg .. "\nMake sure that the compilation server is running or try disabling the server!"
		end
		wehack.messagebox(msg,"Grimoire",false)
	end
end

function testmap(cmdline)
	--if havewurst and wurst_enable.checked and not mapvalid then
	--	return
	--end
	--wehack.messagebox(cmdline)
	--mappath = strsplit(" ",cmdline)[2]
	--compilemap_path(mappath)
	
	if haveSharpCraft and sharpCraftEnable.checked then
		-- remove default .exe
		local pos = string.find(cmdline, ".exe")
		cmdline = string.sub(cmdline, 4 + pos)
		-- replace with SharpCraft exe
		cmdline = "SharpCraft\\SharpCraft.exe -game " .. cmdline
	end
	
	if wh_opengl.checked then
		cmdline = cmdline .. " -opengl"
	end
	if wh_window.checked then
		cmdline = cmdline .. " -window"
	end
	wehack.execprocess(cmdline)
end

function compilemap_path(mappath,tries)
	if mappath == "" then
		showfirstsavewarning()
		return
	end
	map = wehack.openarchive(mappath,15)
	wehack.extractfile("wurstscript\\common.j","scripts\\common.j")
	wehack.extractfile("wurstscript\\Blizzard.j","scripts\\Blizzard.j")
	wehack.extractfile(jh_path.."jasshelper\\common.j","scripts\\common.j")
	wehack.extractfile(jh_path.."jasshelper\\Blizzard.j","scripts\\Blizzard.j")
	wehack.extractfile("war3map.j","war3map.j")
	wehack.closearchive(map)
	if cmdargs ~= "" then
		local cmdtable = argsplit(cmdargs)
--		local len = table.getn(cmdtable)
--		for i = 1, len do
--			cmdtable[i] = removequotes(cmdtable[i])
--		end
--		wehack.rungrimextool(cmdtable)
grim.log("running tool on save: "..cmdargs)
		wehack.runprocess(cmdargs)
		cmdargs = ""
	end
	
	if havejh and jh_enable.checked then
		cmdline = jh_path .. "jasshelper\\jasshelper.exe"
		if jh_debug.checked then
			cmdline = cmdline .. " --debug"
		end
		if jh_disable.checked then
			cmdline = cmdline .. " --nopreprocessor"
		end
		if jh_disableopt.checked then
			cmdline = cmdline .. " --nooptimize"
		end
		cmdline = cmdline .. " "..jh_path.."jasshelper\\common.j "..jh_path.."jasshelper\\blizzard.j \"" .. mappath .."\""
		toolresult = 0
		toolresult = wehack.runprocess(cmdline)
		if toolresult == 0 then 
			mapvalid = true
		else
			mapvalid = false
		end
	end
	
	if havewurst and wurst_enable.checked then
		cmdline = wurst_command()
		cmdline = cmdline .. " -gui"
		if wurst_debug.checked then
			--cmdline = cmdline .. " --debug"
		end
		--if wurst_disable.checked then
			--cmdline = cmdline .. " --nopreprocessor"
		--end
		if wurst_optenable.checked then
			cmdline = cmdline .. " -opt"
		end
		if wurst_localoptenable.checked then
			cmdline = cmdline .. " -localOptimizations"
		end
		if wurst_inliner.checked then
			cmdline = cmdline .. " -inline"
		end
		if wurst_stacktraces.checked then
			cmdline = cmdline  .. " -stacktraces"
		end
		if wurst_uncheckedDispatch.checked then
			cmdline = cmdline .. " -uncheckedDispatch"
		end
		if wurst_nodebug.checked then
			cmdline = cmdline  .. " -nodebug"
		end
		if wurst_compiletimefunctions.checked then
			cmdline = cmdline .. " -runcompiletimefunctions"
		end
		if wurst_injectObjects.checked then
			cmdline = cmdline .. " -injectobjects"
		end
		if wurst_useJmpq2.checked then
			cmdline = cmdline .. " --jmpq2"
		end
		
		-- cmdline = cmdline .. " -lib ./wurstscript/lib/"
		cmdline = cmdline .. " wurstscript\\common.j wurstscript\\Blizzard.j \"" .. mappath .."\""
		
		toolresult = 0
--		if wurst_fast ~= nil and wurst_fast.checked then
--			toolresult = wehack.runjasshelper(wurst_debug.checked, wurst_disable.checked, "jasshelper\\common.j", "jasshelper\\blizzard.j", mappath, "")
--		else
			toolresult = wehack.runprocess2(cmdline)
--		end
		if toolresult == 0 then 
			mapvalid = true
		else 
			mapvalid = false
			if wurst_b_enable.checked then
				if tries == 0 then
					-- try starting compilation server
					wurst_compilationserver_start()
					-- try again
					compilemap_path(mappath,tries+1)
				else
					wehack.messagebox("Could not run Wurst with compilation server.","Wurst",false)
				end
			else
				wehack.messagebox("Could not run Wurst.","Wurst",false)
			end
		end
	end
end

function compilemap()
	mappath = wehack.findmappath()
	compilemap_path(mappath,0)
end

if haveext then
    localfiles = MenuEntry:New(utils,"Enable Local Files",togglelocalfiles)
    shellext = MenuEntry:New(utils,"Register Shell Extensions",toggleshellext)
    initlocalfiles()
    initshellext()
    wehack.addmenuseparator(utils)
end
if haveext and grim.exists("grimext\\tilesetter.exe") then
    tilesetter = MenuEntry:New(utils,"Edit Tileset",runtilesetter)
end
if haveext and grim.exists("grimext\\fileexporter.exe") then
    fileexporter = MenuEntry:New(utils,"Export Files",runfileexporter)
end
if haveext and grim.exists("grimext\\fileimporter.exe") then
    fileimporterdir = MenuEntry:New(utils,"Import Directory",runfileimporterdir)
    fileimporterfiles = MenuEntry:New(utils,"Import Files",runfileimporterfiles)
end
if haveext and grim.exists("grimext\\objectmerger.exe") then
    objectmerger = MenuEntry:New(utils,"Merge Object Editor Data",function(self) runobjectmerger("m") end)
    objectreplacer = MenuEntry:New(utils,"Replace Object Editor Data",function(self) runobjectmerger("r") end)
    objectimporter = MenuEntry:New(utils,"Import Object Editor Data",function(self) runobjectmerger("i") end)
end
if haveext and grim.exists("grimext\\constantmerger.exe") then
    constantmerger = MenuEntry:New(utils,"Merge Constants Data",runconstantmerger)
end
if haveext and grim.exists("grimext\\triggermerger.exe") then
    triggermerger = MenuEntry:New(utils,"Merge Trigger Data",runtriggermerger)
end

function extabout()
    grim.openlink("http://www.wc3campaigns.net")
end
if haveext then
	wehack.addmenuseparator(utils)
  aboutextensions = MenuEntry:New(utils,"About Grimex ...",extabout)
end



isstartup = false

EDIT: Batch script to start Eclipse has typo in it's name.


Well yes there was the mistake the wehack.lua is specially modified for the all in one pack, so the updater shouldn't replace it. Replace the wehack.lua with the old one and try it again.

This error: "Error in File line -1: Could not find lib-package Wurst" is caused when you are missing the wurst.dependencies file in your wurst folder next to your map.
 
Level 11
Joined
Mar 6, 2008
Messages
898
Hiho,

first of all: thank you for your response. I will look into the custom event library things.

However, the other questions about the missing library functions are still not answered to me. :(

Sorry, that I wasn't too clear about my question with the shown code.
My real intend to ask was, if it is possible to create such a functionality encapsulated within a class and notifyChatEventListeners as a method of this class.
This requires closures instead of lamdas which I can't give to a function that's taking a "code" argument which is bad. :(

So, is it possible to do that in wurst or must I stick with the global function?

This is very important for me to know since I want to create a map which will be based very heavily on a good and fluent event system.

Regard,
Robbepop
 
Level 8
Joined
Nov 20, 2011
Messages
202
However, the other questions about the missing library functions are still not answered to me. :(

The standard lib is open source. A lot of people missing different features in it. If you want to add something to it just create pull request on github, but i can not promise that peq or frotty will merge everything.

Sorry, that I wasn't too clear about my question with the shown code.
My real intend to ask was, if it is possible to create such a functionality encapsulated within a class and notifyChatEventListeners as a method of this class.

Iam not sure but i guess what you want does my libary. notifyChatEventListeners() would be equaly to fire().

This requires closures instead of lamdas which I can't give to a function that's taking a "code" argument which is bad. :(

This one confuses me the most. A lamba would be:

JASS:
(int i) -> print(i.toString())

In this one the variable "closure" would be the closure.

JASS:
interface Rob
    function notify(int i)

function bla()
    Rob closure = (int i) -> print(i.toString())
    Rob closure2 = (int i) -> print((i*2).toString())
    closure.notify(10) // prints 10

Additionaly following is vaild:

JASS:
function foo(code c)

function bla()
     foo(() -> print("hello"))
 
Level 11
Joined
Mar 6, 2008
Messages
898
Hiho,

yeah but what I basically wanted to do was the following:

JASS:
package Init

import ArrayList
import Trigger

interface ChatEventListener
    function onChatEvent(string content)

class ChatEvent
    var chatEvent = CreateTrigger()
    var chatEventListeners = new ArrayList<ChatEventListener>()

    construct()
        chatEvent.registerPlayerChatEvent(Player(0), "", false)
        chatEvent.addAction(() -> notifyAll()) // this call is not working since notifyAll is a method

    function addListener(ChatEventListener listener)
        chatEventListeners.add(listener)

    function notifyAll()
        let iterator = chatEventListeners.iterator()
        for listener from iterator
            listener.onChatEvent(GetEventPlayerChatString())
        iterator.close()

init
    var event = new ChatEvent()
    event.add((string content) -> print("Hello, World! -> " + content))
    event.add((string content) -> print(content + content))

I will look into your library when I got time to see if it can fix my problem with a nice solution. :)

Regards,
Robbepop
 
Level 8
Joined
Nov 20, 2011
Messages
202
JASS:
package ChatEvent

import CustomClosureEvents
import HashMap

//wrapper class for the primitive type
class ChatData
	string s
	player p
	
	construct(string s, player p)
		this.s = s
		this.p = p

class ChatEvent extends Event<ChatData>
	static HashMap<trigger, ChatEvent> map = new HashMap<trigger, ChatEvent>()
	trigger ev
	
	construct()
		ev = CreateTrigger()
		map.put(ev, this)
		for int i = 0 to 11
			ev.registerPlayerChatEvent(Player(i), "", false)
		ev.addAction(() -> map.get(GetTriggeringTrigger()).fire(GetEventPlayerChatString(), GetTriggerPlayer()))
		
	function fire(string message, player data)
		callActions(new ChatData(message, data))
		

init
	ChatEvent even = new ChatEvent()
	var action = even.addAction((ChatData data) -> print("The player " + GetPlayerName(data.p) + " typed the message " + data.s))
	//turn of notifcations if player 0 writes "-stop"
	even.addAction((ChatData data) -> begin
		if(data.p == Player(0) and data.s == "-stop")
			destroy action
	end)

This is what you want right? Created this using my libary
 

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
I would take the same approach as Crigges.

The reason why Wurst does not allow to use closures for code parameters is that there are a lot of ways to attach data to code. And the way to choose heavily depends on the context. For example with triggers you can use a hashmap as Crigges showed. For Chat messages I would just use the triggering player (or the player-ID). For timers you would rather use timer-utils. For GroupEnums you can usually use a global.

The compiler cannot know how you want to attach the data and where you want to attach it. So the approach we are taking is to provide library functions for the most commonly used cases. For other use cases you have to do your own implementation.
 
Level 11
Joined
Mar 6, 2008
Messages
898
Hiho,

@Crigges:
Thank you for this example.
Yeah that is basically what I wanted but I think it is quite ugly that this approach requires a static HashMap. However, still 100 times less ugly than vJass. xDD

@peq:
Thank you for your explanations!

I want to build the map with WurstScript and rely heavily on an Event driven system and really hope that this approach won't suffer from performance issues as the map rewrite requires high efficiency. ;)

Regards,
Robbepop
 
Level 8
Joined
Nov 20, 2011
Messages
202
Hiho,

@Crigges:
Thank you for this example.
Yeah that is basically what I wanted but I think it is quite ugly that this approach requires a static HashMap. However, still 100 times less ugly than vJass. xDD

I don't like the static HashMap in this context aswell. However it will always be ugly since jass trigger act static. So you always need to get from this static context back to the dynamic.

A slightly better solution would be to extend the standard libary by a function
trigger.setData(int i) and tigger.getData() so there would be one central Table that acts in the background for all triggers. The code would look as following:

p
JASS:
ackage ChatEvent

import CustomClosureEvents
import HashMap

//wrapper class for the primitive type
class ChatData
    string s
    player p
   
    construct(string s, player p)
        this.s = s
        this.p = p

class ChatEvent extends Event<ChatData>
    trigger ev
   
    construct()
        ev = CreateTrigger()
        ev.setData(this castTo int)
        for int i = 0 to 11
            ev.registerPlayerChatEvent(Player(i), "", false)
        ev.addAction((GetTriggeringTrigger().getData() castTo thistype).callActions(new ChatData(GetEventPlayerChatString(), GetTriggerPlayer())))

init
    ChatEvent even = new ChatEvent()
    var action = even.addAction((ChatData data) -> print("The player " + GetPlayerName(data.p) + " typed the message " + data.s))
    //turn of notifcations if player 0 writes "-stop"
    even.addAction((ChatData data) -> begin
        if(data.p == Player(0) and data.s == "-stop")
            destroy action
    end)
 
Level 11
Joined
Mar 6, 2008
Messages
898
I like the idea with the setData(...) and getData(...) functions.
However, I think we could generalize that for any object and any data type.

In this example setData(...) and getData(...) are generic and setData is made method for every primitive or better yet, a global generic function which can accept all types.
For primitive types it is specialized to do the right thing and for all custom types it converts to int first like in your example.
So effectively we would hide the nasty cast with this method and make the whole approach more generic so that we can reuse it for other data without extending our code base.

getData(...) would be similar. However, here WurstScript can't auto infere types so we need to provide it with the converted type like so:
getData<thistype>(trigger) or (if thistype isn't a thing in WurstScript) getData<MyType>(trigger)
This is the approach taken in C++ often, however is this possible to implement in WurstScript?

Example Code:

JASS:
ackage ChatEvent

import CustomClosureEvents
import HashMap

//wrapper class for the primitive type
class ChatData
    string s
    player p
   
    construct(string s, player p)
        this.s = s
        this.p = p

class ChatEvent extends Event<ChatData>
    trigger ev
   
    construct()
        ev = CreateTrigger()
        setData(ev, this) // type auto infered, maybe it is possible to call it like a method with uniform call syntax?
        for int i = 0 to 11
            ev.registerPlayerChatEvent(Player(i), "", false)
        ev.addAction(getData<thistype>(GetTriggeringTrigger()).callActions(
                new ChatData(GetEventPlayerChatString(), GetTriggerPlayer())))

init
    ChatEvent even = new ChatEvent()
    var action = even.addAction((ChatData data) -> print("The player " + GetPlayerName(data.p) + " typed the message " + data.s))
    //turn of notifcations if player 0 writes "-stop"
    even.addAction((ChatData data) -> begin
        if(data.p == Player(0) and data.s == "-stop")
            destroy action
    end)

Regards,
Robbepop
 

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
I don't like the idea of one global hashmap. That is just some hidden global state which will cause problems once you have collisions. For trigger it is probably ok, because you only use setData when you create the trigger. But what about units? What about integers that are equal to the hash code of some trigger?

Also there is no garbage collection for hashmaps, so when the object is removed, the entry in the hashmap is still there (that's one of the reasons why we reuse timers in TimerUtils).
 
Level 11
Joined
Mar 6, 2008
Messages
898
Ah okay, then these are two important aspects which I didn't know:
1. No garbage collection of HashMap entries which is ridiculous.
2. Objects of different types have no distinct object Ids.

Then that's truely not a good idea.
Thank you for the input!

I think we (I) have to stick with the underlying uglyness of the JASS environment. ;)

Regards,
Robbepop
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
No garbage collection of HashMap entries which is ridiculous.

In practice it doesn't really matter in JASS, because hashtable is either statically allocated, or at least allocated quite large.

Don't forget that warcraft 3 is coded in C++. Underneath the thick wrapper, handle ids are probably recycled properly.

None of my public resources, nor wurst code, flushes handle tables, because it isn't necessary or interesting.
 

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
In practice it doesn't really matter in JASS, because hashtable is either statically allocated, or at least allocated quite large.

Don't forget that warcraft 3 is coded in C++. Underneath the thick wrapper, handle ids are probably recycled properly.

None of my public resources, nor wurst code, flushes handle tables, because it isn't necessary or interesting.

You are right, but the reason you don't have to flush tables is that you usually don't use it for short-lived objects. If you add thousands of keys a second I am sure, that even the C++ implementation underneath would run into problems after some time.

So @Robbepop: Hashtables are a good choice for attaching data to a trigger. I just don't like the generic setData method because it hides some of the pitfalls and performance implications, which might bite you in some cases.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
You are right, but the reason you don't have to flush tables is that you usually don't use it for short-lived objects. If you add thousands of keys a second I am sure, that even the C++ implementation underneath would run into problems after some time.

So @Robbepop: Hashtables are a good choice for attaching data to a trigger. I just don't like the generic setData method because it hides some of the pitfalls and performance implications, which might bite you in some cases.

I would be surprised if the cost of hashtable.save(createEffect().destroy(), 0) increased by more than 50% after a billion iterations.
 
Level 1
Joined
Jun 16, 2015
Messages
3
in vJass there are
Structs with more index space
JASS:
struct X[10000]
    integer a
    integer b
endstruct
(from JassHelper manual)

any analog, something like that in WurstScript?

thanks
 

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
Wurst currently does not support structs with more index space.

As a workaround you could also use a tuple containing one integer and back it up with a hashtable, but of course performance would be worse.

Plain tuples might be a good fit for some use-cases where a lot of small objects are needed.
 

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
It's some time since the last update here...

In the last weeks I've been working on a plug-in for VisualStudioCode (vsc).

You can check it out here : https://marketplace.visualstudio.com/items?itemName=peterzeller.wurst

Setup is a bit hairy at the moment (follow the instructions), so let me know if you want to try it, but have problems setting it up.

It already can do most of the things supported by eclipse like autocomplete and jump to declaration.

My long term goal would be to get away from eclipse and to a more modern editor. The editor support should also be less coupled to a specific editor. The new language services could also be used for other editor plug-ins like vim, atom or sublime.

I chose vsc now, because it has a very clean extension api, it's open source, and I generally like it as an editor.

I will probably post some Screenshots or a video soon.
 
Level 25
Joined
Feb 2, 2006
Messages
1,683
Cool stuff. Will there be any kind of converter from vJass to Wurst at some point? You could convert stuff except for textmacros and other things which are not supported or is it too much work? The only reason I am not using this is the huge code base I do already have in vJass. Does Wurst hide .evaluate() and .execute() calls? I thought it was quite useful with the JassHelper to turn of default .evaluate() calls for methods, so I could reduce the number by rearanging the method definitions.

Will hooks be supported some day? And what about Array members? They are listed as not supported.

I think Eclipse is the a good IDE to support. It's free and I could even use it on Linux for the code only.

edit:
Btw. your YouTube videos are private.
 

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
Cool stuff. Will there be any kind of converter from vJass to Wurst at some point? You could convert stuff except for textmacros and other things which are not supported or is it too much work? The only reason I am not using this is the huge code base I do already have in vJass.

There is a dialect of Wurst called Jurst, which has the same features as Wurst but a Syntax similar to vJass. You can use Jurst to adapt vJass code, but there are still a few manual steps involved, because of the difference in supported features.

You can even mix Wurst and Jurst in the same project.
However, Jurst is not really documented at the moment and hardly tested. The only documentation is basically the grammar definition: https://github.com/peq/WurstScript/blob/master/de.peeeq.wurstscript/parserspec/Jurst.g4

Does Wurst hide .evaluate() and .execute() calls? I thought it was quite useful with the JassHelper to turn of default .evaluate() calls for methods, so I could reduce the number by rearanging the method definitions.

Method calls are translated to if-statements and methods are just reordered automatically as long as you have no cycle in the call hierarchy.

Cycles in the call hierarchy are translated by stuffing everything into one big method.

For example ...

Wurst:
function foo(int x) returns int
	return S2I(bar(x-1))
	
function bar(int x) returns string
	if x > 0
		return foo(x-1).toString()
	return "0"

... gets translated to the following code:

JASS:
function cyc_foo takes integer funcChoice, integer x returns nothing
	if funcChoice == 0 then
		call cyc_foo(1, x - 1)
		set tempReturn_integer = S2I(tempReturn_string)
		return
	elseif funcChoice == 1 then
		if x > 0 then
			call cyc_foo(0, x - 1)
			set tempReturn_string = int_toString(tempReturn_integer)
			return
		endif
		set tempReturn_string = "0"
		return
	endif
endfunction



Will hooks be supported some day? And what about Array members? They are listed as not supported.

Hooks are not supported, because they can make the code confusing. However, they would not be too difficult to add, so if there are some good usecases, I could add them.

Array members are partially supported, see http://peq.github.io/WurstScript/manual.html#dynamic-sized-array-members

I think Eclipse is the a good IDE to support. It's free and I could even use it on Linux for the code only.

Visual-Studio Code also works well on Linux. I mostly work on Linux, so this is important :)

I think we have to update the first post...
 
Level 8
Joined
Nov 20, 2011
Messages
202
Cool stuff. Will there be any kind of converter from vJass to Wurst at some point? You could convert stuff except for textmacros and other things which are not supported or is it too much work? The only reason I am not using this is the huge code base I do already have in vJass. Does Wurst hide .evaluate() and .execute() calls? I thought it was quite useful with the JassHelper to turn of default .evaluate() calls for methods, so I could reduce the number by rearanging the method definitions.

Will hooks be supported some day? And what about Array members? They are listed as not supported.

I think Eclipse is the a good IDE to support. It's free and I could even use it on Linux for the code only.

edit:
Btw. your YouTube videos are private.

I don't think there will be a converter from vJass to wurst soon. You shoud take a look at Jurst.

Array members are supported already.

Wurst does not affect TriggerExecute or TriggerEvaluate afaik.
 
Level 25
Joined
Feb 2, 2006
Messages
1,683
ok thx I don't know if I will have the time soon. The reordering is a much better approach than vJass does. I use hook for example for RemoveUnit() or PauseUnit() when I need these events. Maybe for RemoveUnit() one could use the leave event? Basically it is useful when there is no event for the function or you want some debugging output for natives.

Sized array class members seem to be supported, what is missing here? If you support sized structs it would be useful too. I use types like AIntegerVector which are heavily used in and need more instances.

Hm, I still have an ugly parser for vJass in my tool vjassdoc. Maybe I could generate some stuff already.
 
Couple of questions:

Can we finally just call functions that are declared in before?

function doNothing takes nothing returns nothing
call myFunc()
endfunction

function myFunc takes nothing returns nothing
endfunction

Even if the compiler compiles any function to ExecuteFunc("funcname") and global variables to pass?

Are local variables auto-nulled or are we still expected to null them ourselves?


edit:

Wurst:
function foo(int x) returns int
	return S2I(bar(x-1))
	
function bar(int x) returns string
	if x > 0
		return foo(x-1).toString()
	return "0"

... gets translated to the following code:

JASS:
function cyc_foo takes integer funcChoice, integer x returns nothing
	if funcChoice == 0 then
		call cyc_foo(1, x - 1)
		set tempReturn_integer = S2I(tempReturn_string)
		return
	elseif funcChoice == 1 then
		if x > 0 then
			call cyc_foo(0, x - 1)
			set tempReturn_string = int_toString(tempReturn_integer)
			return
		endif
		set tempReturn_string = "0"
		return
	endif
endfunction

Really? Why. Why would they ever be translated this way? Having to call methods using an if/else block just sounds AWEFUL and slow down of algorithms is directly related number of methods in a class.

Was there really no other way to emulate this?

Also, have you guys switched from using arrays to using hashtables for classes so we can have >8190/largest array size instances? That's really (in my opinion) the weakest feature of vjass because it really kinda fucking sucks for designing a unit indexer....
 
Last edited:

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Are local variables auto-nulled or are we still expected to null them ourselves?

afaik wurst auto nulls your variables.

Wurst:
function foo(int x) returns int
	return S2I(bar(x-1))
	
function bar(int x) returns string
	if x > 0
		return foo(x-1).toString()
	return "0"

... gets translated to the following code:

JASS:
function cyc_foo takes integer funcChoice, integer x returns nothing
	if funcChoice == 0 then
		call cyc_foo(1, x - 1)
		set tempReturn_integer = S2I(tempReturn_string)
		return
	elseif funcChoice == 1 then
		if x > 0 then
			call cyc_foo(0, x - 1)
			set tempReturn_string = int_toString(tempReturn_integer)
			return
		endif
		set tempReturn_string = "0"
		return
	endif
endfunction

Really? Why. Why would they ever be translated this way? Having to call methods using an if/else block just sounds AWEFUL and slow down of algorithms is directly related number of methods in a class.

Was there really no other way to emulate this?

These kind of functions are only compiled for every strongly connected component, of which there shouldn't be too many tbh.

Also ifs should be way faster than any other form of dynamically calling code.


Also, have you guys switched from using arrays to using hashtables for classes so we can have >8190/largest array size instances? That's really (in my opinion) the weakest feature of vjass because it really kinda fucking sucks for designing a unit indexer....

How so? 8000 units are quite a lot, are they not?
 
Level 13
Joined
Nov 7, 2014
Messages
571
Also, have you guys switched from using arrays to using hashtables for classes so we can have >8190/largest array size instances? That's really (in my opinion) the weakest feature of vjass because it really kinda fucking sucks for designing a unit indexer....

How so? 8000 units are quite a lot, are they no

This change should probably be as simple as adding, what's the name of those... annotations/attributes to the struct/class:

JASS:
@storage(array)
struct Foo { ... }

@storage(hashtable)
struct Bar { ... }

@storage(gamecache)
struct Bar { ... }

or something, in my opinion.

Would probably require some fogstate return-bugging to bring the gamecache interface to match that of the hashtable, also hashtables can't store some handle types (forgot which).
 
This change should probably be as simple as adding, what's the name of those... annotations/attributes to the struct/class:

JASS:
@storage(array)
struct Foo { ... }

@storage(hashtable)
struct Bar { ... }

@storage(gamecache)
struct Bar { ... }

That. Would be. Fucking. Brilliant. Seriously man, This should be implemented!

Also, no one answered my question,

can we have:

function a;

function b;

and expect them to be able to call one another? (without callExecuteFunc or trigger + addactions)

How so? 8000 units are quite a lot, are they not?

Not always. let's say you have a struct told indexed units, and a struct for each unit instance

that means each player gets 8190/12 units or

682 units.

Ever had a hostile player that requires 2500 units while the rest 1 or so?

Do we really want to have to create a seperate class?

I suppose we could use polymorphism, but still I don't like to have to do this just to achieve different alloc sizes....

Look at this also ->

hashtables would enable structs to have arrays with variable size

ie

struct
int array x

endstruct

wouldn't produce compile error of requiring static size

also; why not make function variable type wrapped around trigger?

I know we can do this ourselves, but it would make the code a little cleaner to have default support.

Also, are map optimizers still safe with wurst to jass?

Ie,

do you ever use call ExecuteFunc? This would cause map optimizers to fail on optimize scripts I believe.
 
Level 8
Joined
Nov 20, 2011
Messages
202
function a;

function b;

and expect them to be able to call one another? (without callExecuteFunc or trigger + addactions)

No! The current way by using if statements is pretty fast and clean. There is no better way to solve cyclic dependencies in jass. Wurst is already trying to sort the functions.


Not always. let's say you have a struct told indexed units, and a struct for each unit instance

that means each player gets 8190/12 units or

682 units.

Ever had a hostile player that requires 2500 units while the rest 1 or so?

Do we really want to have to create a seperate class?

I suppose we could use polymorphism, but still I don't like to have to do this just to achieve different alloc sizes....

How about no? Wurst is a oop language. So you'll have to use polymorphism by desing. And if you reach more than 8190 units in total on the map. The game will be unplayable a long time before!


Look at this also ->

hashtables would enable structs to have arrays with variable size

ie

struct
int array x

endstruct

There are no structs in wurst. You can use tupels instead. Iam not sure if wurst already supports tuple types as generic type or if peq still plans to add it.

Addionaly Hashtables are realy slow compared to arrays. This getter and setter function is around 4 times faster than a nearly empty hashtable: http://peeeq.de/code.php?id=9019
Benchmark it yourself.


Also, are map optimizers still safe with wurst to jass?

Ie,

do you ever use call ExecuteFunc? This would cause map optimizers to fail on optimize scripts I believe.
ExecuteFunc is forbidden in wurst for this reason, however wurst uses different features like lambdas to avoid using ExecuteFunc.
Wurst output code is already optimzied, but you can try to optimize it further.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
Not always. let's say you have a struct told indexed units, and a struct for each unit instance

that means each player gets 8190/12 units or

682 units.

No dynamic indexing here? If you just allocate the structs dynamically, like I'd expect wurst to do, it won't be a problem. Then you could even have 8189 units on one player and 1 on some other.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Addionaly Hashtables are realy slow compared to arrays. This getter and setter function is around 4 times faster than a nearly empty hashtable:

I am pretty sure that's not the case (arrays are faster, but jass function calls are somewhat slow, so it evens out, sort of), although I've only done "FPS" benchmarks not stopwatch, and even if function calls with binary search and array lookups were that much faster, is it really worth all the generated bloat it comes with? And it's probably unlikely systems that need that much space would also need the speed; I am pretty sure even gamecache will suffice in most cases and it's even slower than the hashtable natives.
 
Level 8
Joined
Nov 20, 2011
Messages
202
I am pretty sure that's not the case (arrays are faster, but jass function calls are somewhat slow, so it evens out, sort of), although I've only done "FPS" benchmarks not stopwatch, and even if function calls with binary search and array lookups were that much faster, is it really worth all the generated bloat it comes with? And it's probably unlikely systems that need that much space would also need the speed; I am pretty sure even gamecache will suffice in most cases and it's even slower than the hashtable natives.

Iam pretty sure that it is the case. The linked setter and getter function is way faster than a hashtable, I benchmarked it myself by using preload bug.
The generated bloat is created in the background so you don't have to care about it. Checkout the output code of wurst with stracktraces and inliner enabled, i guess you will loose your mind after that. Addionaly wurst uses if statements with binary search for: Dynamic dispatch, Switch cases and Member Arrays.

Where you need that much space you will most likely need a lot of speed. Let's assume you are using hashtables to store unit data, in my eyes this is bullshit but you bought it up yourself. In that case each custom spell with a periodic effect will lookup unit data each 0.03 sec on a lot of units. Custom damage systems even on each normal attack. Using gamecache there will result in huge fps drops even in low scaled scenarios.
 
Level 1
Joined
Jun 16, 2015
Messages
3
I have a question about Eclipse.
How can I change the encoding of HotDoc comment from Windows-1251 to UTF-8?
Thanks
 

Attachments

  • Screenshot_1.jpg
    Screenshot_1.jpg
    30.5 KB · Views: 119
Iam pretty sure that it is the case. The linked setter and getter function is way faster than a hashtable, I benchmarked it myself by using preload bug.
The generated bloat is created in the background so you don't have to care about it. Checkout the output code of wurst with stracktraces and inliner enabled, i guess you will loose your mind after that. Addionaly wurst uses if statements with binary search for: Dynamic dispatch, Switch cases and Member Arrays.

Where you need that much space you will most likely need a lot of speed. Let's assume you are using hashtables to store unit data, in my eyes this is bullshit but you bought it up yourself. In that case each custom spell with a periodic effect will lookup unit data each 0.03 sec on a lot of units. Custom damage systems even on each normal attack. Using gamecache there will result in huge fps drops even in low scaled scenarios.
Actually, he didn't bring it up, I didn't. I brought up the idea of structs being implemented with hashtable.

That set aside, who the fuck codes spells that require looping through all the units? That's completely fucking stupid.

Also, custom damage by creating triggers for each unit that can take damage then destructing are fast. I don't know what custom damage system you've been using, but this is the one that's recommended. The only ones that'd cause slow downs are the ones that simply add damage events to a single trigger each time a unit is created, because they leak.

But any good dds system linked with unit indexing will clean the data when the unit dies.
 
No! The current way by using if statements is pretty fast and clean. There is no better way to solve cyclic dependencies in jass. Wurst is already trying to sort the functions.


JASS:
function doNothing takes unit u returns unit
  return doNothing00(u)
endfunction

function doNothing00 takes unit u returns unit
  return u
endfunction
would then compile to

JASS:
globals
  trigger t
  unit pass_u0
  unit return_u0
endglobals

function doNothing takes unit u returns unit
  set pass_u0 = u
  call TriggerExecute(t)
  return return_u0
endfunction

function doNothing00 takes unit u returns unit
  return u
endfunction

function resolve takes nothing returns nothing
  set return_u0 = doNothing00(pass_u0)
endfunction

//injected into main or something
function INIT_CYCLIC_DEPENDENCY takes nothing returns nothing
   set t = CreateTrigger()
   call TriggerAddAction(t, function resolve)
endfunction
Only real disadvantage to this method I can think is maybe more complex compilation process?

Probably all functions would have to compile to an API like this for consistency.

And I'm not trying to start any fights here, I'm actually very interested. There are things about WURST that already seem nicer than VJASS, I'm just not sure I'd switch to it at this point.

Also, the ExecuteFunc issue from jass/vjass was easilly solved using

JASS:
struct functor
    private trigger handled
   
    static method create takes code callback returns thistype
        local thistype new = .allocate()
       
        set new.handled = CreateTrigger()
        call TriggerAddAction(new.handled, callback)
       
        return new
    endmethod
   
    method execute takes nothing returns nothing 
        call TriggerExecute(handled)
    endmethod
   
    method destroy takes nothing returns nothing
        call TriggerClearActions(handled)
        call DestroyTrigger(handled)
        call .deallocate()
    endmethod
   
endstruct
 
Last edited:

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
JASS:
function doNothing takes unit u returns unit
  return doNothing00(u)
endfunction

function doNothing00 takes unit u returns unit
  return u
endfunction

or it could compile to

JASS:
function doNothing00 takes unit u returns unit
  return u
endfunction

function doNothing takes unit u returns unit
  return doNothing00(u)
endfunction
 
You are all missing the point.

Maybe this will make it more clear:

JASS:
function doNothing takes unit u returns unit
    return doNothing00(u)
endfunction

function doNothing00 takes unit u returns unit
    return doNothing(u)
endfunction
compiles to

JASS:
globals
  trigger t
  trigger x
  unit pass_u0
  unit return_u0
endglobals

function doNothing takes unit u returns unit
  set pass_u0 = u
  call TriggerExecute(t)
  return return_u0
endfunction

function doNothing00 takes unit u returns unit
  set pass_u0 = u
  call TriggerExecute(x)
  return return_u0
endfunction

function resolve takes nothing returns nothing
  set return_u0 = doNothing00(pass_u0)
endfunction

function resolve0 takes nothing returns nothing
  set return_u0 = doNothing(pass_u0)
endfunction

//injected into main or something
function INIT_CYCLIC_DEPENDENCY takes nothing returns nothing
   set t = CreateTrigger()
   call TriggerAddAction(t, function resolve)
  
   set x = CreateTrigger()
   call TriggerAddAction(x, function resolve0)
endfunction
Do you now see how this compilation method would allow for cyclic dependency?

Yes, this would create an infinite recursion loop, that is not the point here. The point is showing cyclic dependency between functions can be done this way.

Edit: Although I think yours may be more efficient. Mine has more function calls than yours does.... Alright. I stand corrected
 
Status
Not open for further replies.
Top