• 🏆 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!

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

Status
Not open for further replies.
Level 23
Joined
Jan 1, 2009
Messages
1,608

WurstScript

by peq & Frotty
with contributions from Crigges & muzzel


Wurst.jpg

wurstmall.jpg


WurstScript is a Scripting language, which can, similar to vJass, cJass and zinc, be translated to Jass.


Why a new Scripting language?



The only alternative to Wurst is vJass (or cJass, zinc, ..., which are basically all the same).

If you are interested in what we did not like about vJass click the button:


1. Editor Support


vJass is mostly based on text-replacements and generation (vJass modules, textmacros). This makes it hard to create a good editor for vJass, including features like autocomplete.

Wurst instead goes for simple, structured code, that can be analyzed in a single compiler-phase. vJass however needs more phases.

2. Type-safety


vJass isn't typesafe. Look at this example:

*vJass Code*

JASS:
struct A
	endstruct

	struct B
	endstruct

	private function init takes nothing returns nothing
		local A a = A.create()
		local B b = A.create() // no error ...
		set a = 42 // no error ...
		set a = "bla" // pjass error
	endfunction

vJass doesn't check if a variable of type B only stores values of type B. This leads to frequent errors, which can be discovered late or you receive a pJass Error, which isn't easy to understand.

The Wurst counterpart (Screenshot from the Eclipse Plugin):

error_sample1.png


3. Verbose syntax


vJass features several redundant Syntax-Elements. For example "set", "call" or "takes nothing returns nothing". Additionally vJass doesn't have many features that allow for writing readable, clear code. Particularly there only exists one type of loop.

WurstScript features less verbosity, but without losing similarity to (v)Jass so switching is easier, and rapid code production for faster map creation.


Syntax




So how does it look like? Here is a simple spell to give you a first idea. The spell fires a missile to the target location, where it damages and knocks back all units in a certain range.

JASS:
package WarStomp

import ClosureEvents
import ClosureTimers
import Fx
import Knockback
import TempGroups

constant spellId = 'A001'
constant impactAoe = 600.
constant missileType = "AbilitiesWeaponsAncientProtectorMissileAncientProtectorMissile.mdl"
constant missileSpeed = 1000.
constant gravity = 981.
constant effectType = "AbilitiesSpellsHumanThunderclapThunderClapCaster.mdl"
constant effectType2 = "ObjectsSpawnmodelsUndeadImpaleTargetDustImpaleTargetDust.mdl"
constant maxSpellDamage = 100.
constant lvlDamageBonus = 25.
constant knockbackStrength = 700.
constant knockbackTime = 1.5

class WarStomb
	unit caster
	int spellLvl
	Fx missile
	vec3 missileVelocity
	timer t = getTimer()
	
	construct(unit caster, int spellLvl, vec2 targetPos)
		this.caster = caster
		this.spellLvl = spellLvl
		vec2 casterPos = caster.getPos()
		angle castDirection = casterPos.angleTo(targetPos)
		real distToTarget = casterPos.distToVec(targetPos)
		
		// the initial z-speed is calculated, so that the z-speed will be 0 after half the flytime
		let flyTime = distToTarget / missileSpeed
		let speedZ = (flyTime/2) * gravity
		
		let missileVelocity2D = missileSpeed * castDirection.direction()
		missileVelocity = missileVelocity2D.withZ(speedZ)
		
		missile = new Fx(casterPos, castDirection, missileType)
		missile.setScale(1.5)
		
		t.setData(this castTo int)
		t.startPeriodic(ANIMATION_PERIOD, function callMoveMissile)
		
	static function callMoveMissile()
		(GetExpiredTimer().getData() castTo WarStomb).moveMissile()
		
	function moveMissile()
		vec3 missilePos = missile.getPos3d()
		missilePos += missileVelocity * ANIMATION_PERIOD
		missile.setPos(missilePos)
		missileVelocity.z -= gravity * ANIMATION_PERIOD
		if missilePos.z <= 0
			onImpact()
		
	function onImpact()
		let missilePos = missile.getPos2()
		//create a big effect at location location of the missile
		Fx fx = new Fx(missilePos, angle(0), effectType)
			..setScale(2)
			..flash(effectType2)
		//destroy the effect after 2 seconds
		doAfter(2, () -> destroy fx)
		
		for unit u from ENUM_GROUP..enumUnitsInRange(missilePos, impactAoe)
			let distToImpact = missilePos.distToVec(u.getPos())
			let distanceFactor = 1 - (distToImpact / impactAoe)
			//damage the unit relative to it's damage distance to the caster
			let damage = (maxSpellDamage + spellLvl * lvlDamageBonus)*distanceFactor
			caster.damageTarget(u,  damage)
			
			//knockback the target unit away from the impact location
			new Knockback(caster, u, knockbackStrength * distanceFactor, 
							knockbackTime, missilePos.angleTo(u.getPos()))
		destroy this
		
	ondestroy
		t.release()
		destroy missile
		
init
	//Creates a new instance of the class WarStomb if the spell is casted
	onPointCast(spellId, (unit caster, int spellLvl, vec2 targetPos) -> 
		new WarStomb(caster, spellLvl, targetPos))


As you can see, Wurst has an indentation based syntax, and therefore you do not have to write words like endif or endfunction. The example also shows some of the syntactic sugar in Wurst, for example the for-from loop, update-assignments with +=, or the dot-dot-expression. You can also see some of the features like packages, classes, closures and tuples.

Features


The full list of features is documented in the Wurst Manual. Here we present only some selected features.



Local Type Inference + Local Variable Freedom


The type of local variables can automatically be derived from the initial value. Furthermore variables can be declared at any position in a function, not just the beginning.

JASS:
	// "let" defines a local constant
	let harald = CreateUnit(Player(0), 'hfoo', x, y, 0)
	// "var" defines a local variable
	var otto = CreateUnit(Player(0), 'hfoo', x, y, 0)
	// traditional way works too
	unit heinz = CreateUnit(Player(0), 'hfoo', x, y, 0)

We encourage you to use let whenever possible, because code is easier read when you know which variables are changed later and which variables keep their initial value.

Extension functions


Extension functions allow to add specific functions to an already existing type, which can be used via dot-syntax.

Declaration:
JASS:
	public function unit.getOwner() returns player
		return GetOwningPlayer(this)
		
	function player.getName() returns string
		return GetPlayerName(this)

Usage:

JASS:
	print(GetKillingUnit().getOwner().getName() + " killed " +
		GetTriggerUnit().getOwner().getName() + "!")


Extension functions have two pros in comparison to normal functions:
  • Functions are easier to find using auto-complete in Eclipse
  • In many cases the readability is improved, as chained calls are often easier to read than nested calls. With chained calls the order of execution is from left to right, while with nested calls the execution is from the inner to the outer:
    JASS:
    	// nested function calls:
    	name = GetPlayerName(GetOwningPlayer(u))
    	// chained function calls
    	name = u.getOwner().getName()


Tuples


A Tuple is a very simple Datatype. It allows to group several values into one. This allows treating the group of values as a group. Other than classes, tuples don't create any overhead. They don't have to be created or destroyed and can be used for primitive data types (int, string, etc.). A good example are vectors(from the stdlib):

Definition:

JASS:
	// A 2d vector with the components x and y
	public tuple vec2( real x, real y )
	
	// Operator overloading functions:
	public function vec2.op_plus( vec2 v )	returns vec2
		return vec2(this.x + v.x, this.y + v.y)
	
	public function vec2.op_minus( vec2 v )	returns vec2
		return vec2(this.x - v.x, this.y - v.y)
		
	public function vec2.op_mult(real factor) returns vec2
		return vec2(this.x*factor, this.y*factor) 
	
	// dot product:
	public function vec2.dot( vec2 v ) returns real
		return this.x*v.x+this.y*v.y
	
	// length:
	public function vec2.length() returns real
		return SquareRoot(this.x*this.x+this.y*this.y)
	
	// normalized Vector:
	public function vec2.norm() returns vec2
        real len = this.length()
        real x = 0 
        real y = 0
        if (len != 0.0)
            x = (this.x / len)
            y = (this.y / len)
        return vec2(x,y)
	
	public function vec2.polarOffset(real angle, real dist) returns vec2
		return vec2(this.x + Cos(angle)*dist, this.y + Sin(angle)*dist)

Usage:

JASS:
	// A projectile homes the target unit(variable following)
	function followHero()
		// Increase angle
		angle += TURN_SPEED*DT
		// calculate new position
		let newPos = following.getPos().polarOffset(angle, RANGE)
		// current velocity is calculated from the difference between old and new position
		vel = (newPos - pos)*(1/DT)
		pos = newPos
		fx.setPos(pos.x, pos.y)
		SetUnitFacing(fx, (angle + bj_PI/2)*bj_RADTODEG)
		checkCollisions()
		
	// projectile moving forward
	function moveForward()
		// Add velocity to position
		pos = pos + vel*DT
		if not pos.inBounds()
			destroyed = true
		else
			fx.setPos(pos.x, pos.y)
			checkCollisions()

Anonymous functions / closures


With anonymous you can define a new function where it is needed. This is useful for timers, as shown in the following example, where the timer t is started with an anonymous function:

JASS:
class Fireball
	timer t
	//...
	
	construct(unit caster, vec2 target)
		// ...
		t = getTimer()
		t.setData(this castTo int)
		t.start(0.05, () -> begin
			let fireball = GetExpiredTimer().getData() castTo Fireball
			fireball.move()
		end) 
	
	function move()
		// do stuff

Closures are more than just anonymous functions. They also capture variables. This allows to write some very concise code. For example it is easily possible to destroy an Fx after some time:

JASS:
	let fx = new Fx(pos, facing, model)
	fx.setScale(2.0)
	// destroy fx after 2 seconds:
	doAfter(2.0, () -> destroy fx)

Here the closure captured the local variable fx without the need to get a timer, attach stuff to the timer, and all that boilerplate code. The doAfter function is defined in the standard library and it can not only destroy Fx objects. You can write any code inside the closure and it will run after the given time.

Of course you can do more crazy stuff with closures. Here is the spell from the introduction, but implemented with closures instead of a class:

JASS:
package WarStompCl

import ClosureEvents
import ClosureTimers
import ClosureForGroups
import Fx
import Knockback

constant spellId = 'A001'
constant impactAoe = 600.
constant missileType = "AbilitiesWeaponsAncientProtectorMissileAncientProtectorMissile.mdl"
constant missileSpeed = 1000.
constant gravity = 981.
constant effectType = "AbilitiesSpellsHumanThunderclapThunderClapCaster.mdl"
constant effectType2 = "ObjectsSpawnmodelsUndeadImpaleTargetDustImpaleTargetDust.mdl"
constant maxSpellDamage = 100.
constant lvlDamageBonus = 25.
constant knockbackStrength = 700.
constant knockbackTime = 1.5

init
	onPointCast(spellId, (unit caster, int spellLvl, vec2 targetPos) -> begin
		let casterPos = caster.getPos()
		let castDirection = casterPos.angleTo(targetPos)
		let distToTarget = casterPos.distToVec(targetPos)
		
		// the initial z-speed is calculated, so that the z-speed will be 0 after half the flytime
		let flyTime = distToTarget / missileSpeed
		let speedZ = (flyTime/2) * gravity
		
		let missileVelocity2D = missileSpeed * castDirection.direction()
		var missileVelocity = missileVelocity2D.withZ(speedZ)
		
		let missile = new Fx(casterPos, castDirection, missileType)
			..setScale(1.5)
		
		doPeriodically(ANIMATION_PERIOD, (CallbackPeriodic cb) -> begin
			var missilePos = missile.getPos3d()
			missilePos += missileVelocity * ANIMATION_PERIOD
			missile.setPos(missilePos)
			missileVelocity.z -= gravity * ANIMATION_PERIOD
			if missilePos.z <= 0
				let impactPos = missilePos.toVec2()
				
				//create a big effect at location location of the missile
				let fx = new Fx(missilePos, angle(0), effectType)
					..setScale(2)
					..flash(effectType2)
				//destroy the effect after 2 seconds
				doAfter(2, () -> destroy fx)

				forUnitsInRange(impactPos, impactAoe, (unit u) -> begin
					let distToImpact = impactPos.distToVec(u.getPos())
					let distanceFactor = 1 - (distToImpact / impactAoe)
					//damage the unit relative to it's damage distance to the caster
					let damage = (maxSpellDamage + spellLvl * lvlDamageBonus)*distanceFactor
					caster.damageTarget(u,  damage)
					
					//knockback the target unit away from the impact location
					new Knockback(caster, u, knockbackStrength * distanceFactor, 
									knockbackTime, impactPos.angleTo(u.getPos()))
				end)
				
				destroy missile
				// stop periodic call:
				destroy cb
				
		end)
	end)

Please note, that this coding style is not recommended. Dividing code into several named functions is much more readable. Also the performance is worse, when using closures.

Compiletime functions


You can execute Wurst code at compile-time. Wurst includes some pseudo-natives which can be executed at compile-time to generate object-editor entries. This is similar to what can be done with the ObjectMerger tool, but it also comes with nice readable method names. Here is an example which creates a spell based on Channel and a tower unit for each level:

JASS:
@compiletime function generateSpell()
	// based on shadow hunter serpent wards
	int levelCount = 4
	let def = new ChannelSpellPreset(GUN_TURRET_SPELL_ID, levelCount)
		..setName("Gun Turret")
		..setIcon("ReplaceableTexturesCommandButtonsBTNElvenGuardTower.blp")
		..setIconNormal("ReplaceableTexturesCommandButtonsBTNElvenGuardTower.blp")
		..setTargetType(Targettype.PTARGET)
		..setOption(Option.VISIBLE, true)
		..setFollowThroughTime(0.4)
		..setDisablesOther(false)
	for lvl = 1 to levelCount
		createTower(lvl)
		def.setCastingTime(lvl,0)
		def.setCastRange(lvl, 300)

function createTower(int lvl)
	// based on high elven guard tower:
	let def = new BuildingDefinition(GUN_TURRET_TOWER_ID + lvl, 'negt')
		..setName("Gun Turret Level " + lvl.toString())
		..setHitPointsMaximumBase(100 + lvl*100)
	// no projectile
	def.setAttack1ProjectileArt("")
	// 0 damage
	def..setAttack1DamageBase(-1)
		..setAttack1DamageNumberofDice(1)
		..setAttack1DamageSidesperDie(1)
		..setAttack1CooldownTime(attackCooldown(lvl))
		..setAttack1Range(attackRange)
		..setScalingValue(0.8)
		..setGroundTexture("")

It is even possible to share code between compile-time functions and normal in-game functions. For example the constant GUN_TURRET_SPELL_ID or the function attackCooldown can be used to configure the generated objects and they can be used in the code for the spell.

Optimizer


Wurst has an integrated optimizer, with several optimizations:
  • inline functions (can inline most functions, exception are multiple return statements in a function)
  • inline constants
  • simple local optmizations (e.g. optimize 3*4 to 12 or remove if false ...)
  • name-compression
  • tree-shaking (remove unused variables and functions)
  • automatic null-setting of handles


Eclipse Plugin




(Click to enlarge)

Run your map from eclipse


You can compile all scripts and run your map directly from eclipse. This can be much faster than saving the map in WorldEditor first, because only the script is updated and eclipse already has the script in memory.

Live errors and warnings


errors.png


Errors and warnings are directly shown in your code without the need for pressing compile or saving the whole map.



Context-sensitive auto-complete


autocomplete.png


If you have an expression with a dot, then auto-complete will show you all members available for the given type. This is especially useful when used with extension methods, because it is very easy to find the function you are looking for.

Hotdoc


As already seen in the auto-complete screenshot, Wurst has support for documenting functions with so called Hotdoc comments. These comments are shown in auto-complete and when you hover your mouse above a function call.

Jump to declaration


Hold down the Ctrl key and click on a variable or function and you will directly jump to the place where it is declared.

Console and REPL


console.png


You can use the console to evaluate expressions and test your code in eclipse, without even starting WarCraft. Of course this is limited, as not all natives are implemented in the Interpreter, but it is nice for testing data-structures or mathematical calculations.


Other benefits of using eclipse


Many features are already available in Eclipse and not specific to the Wurst-Plugin:
  • Press Ctrl+Shift+R to quickly jump to a file. It even supports using wildcards for searching files.
  • Powerful search (and replace) in all files.
  • Integration with version control (Git & co).
  • ...

Manual


You can read about all features in the Wurst Manual.

Installation / Download


Please find the installation instructions on GitHub.

Other links:

Youtube Tutorials:



Download, Installation & Setup
Eclipse & WorldEditor workflow

Projects using Wurst




FAQ (Frequently asked questions):



Can I use vJass together with Wurst?

Yes, you can use both. However it is not possible to call Wurst-functions from vJass and calling vJass from Wurst is not very convenient.


I have further problems, suggestions or questions. Where can I get help?


 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
It really is too bad that this doesn't play nice with vJASS.

As a result, and due to lack of control over class behaviors (like allocation etc), and a few other things, this'll likely never be adopted by more than a small portion of the community :\

Furthermore, does this work directly with the mpq archives? If so, does it maintain the JASS code already in the archive? Does it allow one to have the map open and compile it in eclipse?
 
Level 23
Joined
Jan 1, 2009
Messages
1,608
It really is too bad that this doesn't play nice with vJASS.

As a result, and due to lack of control over class behaviors (like allocation etc), and a few other things, this'll likely never be adopted by more than a small portion of the community :\

Furthermore, does this work directly with the mpq archives? If so, does it maintain the JASS code already in the archive? Does it allow one to have the map open and compile it in eclipse?

The videos/manual showcase most of what you ask.
Of course it's an alternative to vJass - control over class behavior doesn't make much sense if it's handled correctly by the compiler/language (Wurst is pretty restrictive).

It handles mpqs directly, you can however have normal Jass code outside of Wurst Packages.

Eclipse is not linked to the editor at all(also all scriptfiles are external). You can compile your project (with "compile" in console and through reconciling) in eclipse without even having wc3 installed (so it works on any OS, too).
Just the map "build process" has to be done by the WurstWe.
 
Last edited:
This is really cool. Plus the syntax definitely fits wc3--it is exactly how I would envision a solid alternative to JASS (apart from vJASS).

Any plan on implementing a feature similar to hooks? I know it is not incredibly useful for personal mapping, but it would open a lot of doors system-wise (ex: hooking RemoveUnit() or UnitDamageTarget()... just examples). It would be a pretty nice leg-up over vJASS since its hooks aren't implemented well.

By hooking, I mean rewriting the function completely. All instances of the function would call the hook-replacement instead. This would allow for full control over what happens.

Anyway, congrats on releasing it. I saw the documentation a while back and I liked how thorough it was in its explanations. Great job.

EDIT: lol'd at the license text. How lovely.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Crigges, I don't know if Frotty meant custom JASS script is not maintained (Frotty needs to maintain some of the script, like the map settings, or the map would go boom) or if all JASS Script wasn't maintained (meaning that Frotty is either maintaining the bare minimum, those being the map settings, or reading the map, or expecting a user to input the map setting script, which would be nasty).

It is very easy to maintain all script generated by warcraft 3, and there is really no excuse not to do this, unless Frotty is planning a new GUI editor and is going to read the map's settings out of the mpq archive ;).
 
Level 23
Joined
Jan 1, 2009
Messages
1,608
Crigges, I don't know if Frotty meant custom JASS script is not maintained (Frotty needs to maintain some of the script, like the map settings, or the map would go boom) or if all JASS Script wasn't maintained (meaning that Frotty is either maintaining the bare minimum, those being the map settings, or reading the map, or expecting a user to input the map setting script, which would be nasty).

It is very easy to maintain all script generated by warcraft 3, and there is really no excuse not to do this, unless Frotty is planning a new GUI editor and is going to read the map's settings out of the mpq archive ;).

Maybe you got this wrong.
Jass code inside the war3map.j is not maintained. Stuff in the other files of course is.

Any plan on implementing a feature similar to hooks? I know it is not incredibly useful for personal mapping, but it would open a lot of doors system-wise (ex: hooking RemoveUnit() or UnitDamageTarget()... just examples). It would be a pretty nice leg-up over vJASS since its hooks aren't implemented well.

By hooking, I mean rewriting the function completely. All instances of the function would call the hook-replacement instead. This would allow for full control over what happens.

Honestly we don't see any purpose in hooks, since almost all natives are rewritten by extension functions anyways.
(RemoveUnit(u) gets u.remove())
Therefore you could just modify that or write your own extension function to use instead of the native.
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
This is really cool. Plus the syntax definitely fits wc3--it is exactly how I would envision a solid alternative to JASS (apart from vJASS).

Any plan on implementing a feature similar to hooks? I know it is not incredibly useful for personal mapping, but it would open a lot of doors system-wise (ex: hooking RemoveUnit() or UnitDamageTarget()... just examples). It would be a pretty nice leg-up over vJASS since its hooks aren't implemented well.

By hooking, I mean rewriting the function completely. All instances of the function would call the hook-replacement instead. This would allow for full control over what happens.

Anyway, congrats on releasing it. I saw the documentation a while back and I liked how thorough it was in its explanations. Great job.

EDIT: lol'd at the license text. How lovely.

I think hooks just insert a function call of a function name inside the hooked function. That's why it still works if you call a function that you want to be hooked inside the hooking function.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
Thats how hooker functions work, yes. However i think hooks (especially those without a defned scope) are very bad style since they make your code intransparent.
If you need hooks to write well structured code then ur doing something wrong anyway.
 

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
Maybe you got this wrong.
Jass code inside the war3map.j is not maintained. Stuff in the other files of course is.

I think you and Nestharus have a different understanding of "maintained".

Jass code is not maintained in the sense that it will just be copied to the output script. Instead it will be parsed, optimized and then printed to the output. So in practice you can use a subset of what is possible in Jass and it will still be there (in some similar form) after Wurst has processed your map.
 
Level 23
Joined
Jan 1, 2009
Messages
1,608
Ok, so you are parsing war3map.wct and the map setting stuff then?


I just want to know if GUI in a map + that map's settings (the generated main etc) is compatible with Wurst or not >.<.

Read what peq wrote.
Wurst is compatible with GUI and Jass - the code inside the map "will be parsed, optimized and then printed to the output.".
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Ok, so you are parsing war3map.j then?

Ok

The next question is, given this scenario, what would happen

1. Save the map with WE (done doing stuff)
2. Write some Wurst
3. Save the map with Wurst compiler
4. Write some more Wurst
5. Save the map with Wurst compiler

Do you clean out your generated Wurst stuff from the map? Or will you end up having double the Wurst code in there? Or do you somehow recompile the map with the compiler in WE before going to Wurst? Or do you actually compile the map yourself and then go to Wurst? Or do you save the map under a different name (that way you can keep the map open with WE and won't run into issues of double Wurst).

In any case, am I right to assume then that people will still be able to use vJASS and GUI, just that they won't be able to communicate with Wurst or use Wurst resources, like 2 different zones.

Wurst Zone | vJASS/GUI zone (can't see each other)
 
Honestly we don't see any purpose in hooks, since almost all natives are rewritten by extension functions anyways.
(RemoveUnit(u) gets u.remove())
Therefore you could just modify that or write your own extension function to use instead of the native.

After looking into the project a bit more I also realize that hooks would be completely useless/impractical (for other reasons).

Thats how hooker functions work, yes. However i think hooks (especially those without a defned scope) are very bad style since they make your code intransparent.
If you need hooks to write well structured code then ur doing something wrong anyway.

I didn't ever intend to use it to write "well-structured-code". Rather, hooks would be something useful in creating public systems (particularly ones aimed at being GUI-friendly). However, it isn't really practical for Wurst after I read in on it, so I agree with your guys' perspectives.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I think hooks just insert a function call of a function name inside the hooked function. That's why it still works if you call a function that you want to be hooked inside the hooking function.

hook always generates trigger evaluation because when you hook native you may have code that calls that native above your function, and you cant call functions defined below the caller so it must do it the way it is

proof of my claiming:

JASS:
    function myFunction takes unit getKiller returns nothing
    call TriggerSleepAction(0.00)
    call BJDebugMsg("myFunction hooked")
endfunction

hook KillUnit myFunction
->
JASS:
globals
trigger array st___prototype2
unit f__arg_unit1

endglobals

function sc___prototype2_execute takes integer i,unit a1 returns nothing
    set f__arg_unit1=a1

    call TriggerExecute(st___prototype2[i])
endfunction
function sc___prototype2_evaluate takes integer i,unit a1 returns nothing
    set f__arg_unit1=a1

    call TriggerEvaluate(st___prototype2[i])

endfunction
function h__KillUnit takes unit a0 returns nothing
    //hook: myFunction
    call sc___prototype2_evaluate(1,a0)
call KillUnit(a0)
endfunction

function myFunction takes unit getKiller returns nothing
    call TriggerSleepAction(0.00)
    call BJDebugMsg("myFunction hooked")
endfunction

//some shit in main function

//Struct method generated initializers/callers:
function sa___prototype2_myFunction takes nothing returns boolean
    call myFunction(f__arg_unit1)
    return true
endfunction

function jasshelper__initstructs41037420 takes nothing returns nothing
    set st___prototype2[1]=CreateTrigger()
    call TriggerAddAction(st___prototype2[1],function sa___prototype2_myFunction)
    call TriggerAddCondition(st___prototype2[1],Condition(function sa___prototype2_myFunction))

endfunction
 
Level 23
Joined
Jan 1, 2009
Messages
1,608
The next question is, given this scenario, what would happen

1. Save the map with WE (done doing stuff)
2. Write some Wurst
3. Save the map with Wurst compiler
4. Write some more Wurst
5. Save the map with Wurst compiler

Do you clean out your generated Wurst stuff from the map? Or will you end up having double the Wurst code in there? Or do you somehow recompile the map with the compiler in WE before going to Wurst? Or do you actually compile the map yourself and then go to Wurst? Or do you save the map under a different name (that way you can keep the map open with WE and won't run into issues of double Wurst).

In any case, am I right to assume then that people will still be able to use vJASS and GUI, just that they won't be able to communicate with Wurst or use Wurst resources, like 2 different zones.

Wurst Zone | vJASS/GUI zone (can't see each other)

Wurst has been in use for about a year. Obviously the Wurst Code does not duplicate.
Your Wurst code is only in eclipse -> in external scriptfiles. The saving/outputting process works similar to vjass(freehand list).

1. normal editor save (so only code from the trigger-editor is inside the war3map.j)
2. make backup of map
3. pull war3map.j from mpq and add to runArgs.files
4. add all .wurst files from dependencies (stdlib, /wurst, ...)
5. Parse all Files
6. Print Jass to output.j
7. add output.j to map as war3map.j

Therefore all GUI/JASS Code that you have _in the trigger editor_ will be "maintained". So vjass could _theoretically_ run before Wurst (I guess).
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
hook always generates trigger evaluation because when you hook native you may have code that calls that native above your function, and you cant call functions defined below the caller so it must do it the way it is

proof of my claiming:

JASS:
    function myFunction takes unit getKiller returns nothing
    call TriggerSleepAction(0.00)
    call BJDebugMsg("myFunction hooked")
endfunction

hook KillUnit myFunction
->
JASS:
globals
trigger array st___prototype2
unit f__arg_unit1

endglobals

function sc___prototype2_execute takes integer i,unit a1 returns nothing
    set f__arg_unit1=a1

    call TriggerExecute(st___prototype2[i])
endfunction
function sc___prototype2_evaluate takes integer i,unit a1 returns nothing
    set f__arg_unit1=a1

    call TriggerEvaluate(st___prototype2[i])

endfunction
function h__KillUnit takes unit a0 returns nothing
    //hook: myFunction
    call sc___prototype2_evaluate(1,a0)
call KillUnit(a0)
endfunction

function myFunction takes unit getKiller returns nothing
    call TriggerSleepAction(0.00)
    call BJDebugMsg("myFunction hooked")
endfunction

//some shit in main function

//Struct method generated initializers/callers:
function sa___prototype2_myFunction takes nothing returns boolean
    call myFunction(f__arg_unit1)
    return true
endfunction

function jasshelper__initstructs41037420 takes nothing returns nothing
    set st___prototype2[1]=CreateTrigger()
    call TriggerAddAction(st___prototype2[1],function sa___prototype2_myFunction)
    call TriggerAddCondition(st___prototype2[1],Condition(function sa___prototype2_myFunction))

endfunction

that's what I said bro, the hooking function is executed inside the hooked function.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
btw, everytime i read "function myFunction takes unit getKiller returns nothing" i wanna slap you in the face.
I know there are no code conventions specific for jass, but it is common to use something similar to C/C#/Java.

NEVER use "get********" as a property- or argument-name, it implies a getterfunction!
(Only exception is the getter for boolean-valued variables: "is******".)
 
As soon as I see alternatives for all the core systems people would often use (That is, a Unit Indexer, a decent Event handler, an Event Registry system for the sake of handle-saving (like RegisterPlayerUnitEvent), and a timer system specialized for 0.03125-period timers), I'll see what I can do to get this language accepted here ;)
 
Level 23
Joined
Jan 1, 2009
Messages
1,608
As soon as I see alternatives for all the core systems people would often use (That is, a Unit Indexer, a decent Event handler, an Event Registry system for the sake of handle-saving (like RegisterPlayerUnitEvent), and a timer system specialized for 0.03125-period timers), I'll see what I can do to get this language accepted here ;)

UnitIndexer isn't much use in my opinion, there exist Event libs but they are not in the std-library afaik but there are onCast modules and such. TimedLoop(0.03125 stuff) modules (wurst modules are different) and a simple TimerUtils exist.
Feel free to commentate on the stdlib and write Pull Requests at Github.
I will make a youtube tut for all of these things once I get the time & will to do them :)
StdLib: https://github.com/peq/WurstScript/tree/master/Wurstpack/wurstscript/lib
 
Oh cool, everything is on GitHub.

By the way, I have one suggestion concerning init.
You stated that users must keep whatever is executed in init minimal so they can avoid hitting the op limit. I presume you're just taking all the code in these init blocks and performing simple concatenation.

I have a solution:

The op limit is 300000 operations. Each line of JASS typically takes between 1 and 6 operations (set i = i + 1 takes 6 operations exactly.
I was talking to Yixx while he was doing some tests about a year ago~). You can make a decent assumption that any loop will run X times. This can
allow you to estimate the number of operations a function will use up from the operation limit. It's easier to do this to the output JASS script. To
calculate the op count for a function:

Code:
// I'm assuming that all loops will run 32
// times exactly. This will work similar to how a Fermi problem 
// works. All the overestimations cancel out the underestimations.
// You can tweak this value appropriately and maybe allow the user 
// to tweak it to his liking. Higher number = A bit less init speed, more 
// safety. They can tweak it as they like before the release of a map.

let multiplier = 1
let operations = 0

for each line
    if line == "loop"
        multiplier *= 32
    elseif line == "endloop"
        multiplier /= 32
    else
        operations += (6 * multiplier)

This allows you to effectively group consecutive functions that take less than 300000 operations into single functions so you can add them as
conditions to a trigger and evaluate them in one go~
If there's synchronous code however, everything will be fucked up. If someone puts synchronous code in an init block, he's asking for trouble.
In such cases, if the user wants to run synchronous code for whatever reasons he can bullshit, he can use a 0-second timer.
 

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
Hm, that code to estimate the operations could be useful to warn the user whenever he does too much work at once. But I would not like to have the generated code depend on that. I guess it would be hard to debug the cases where it goes wrong.
 
I hope this could be integrated into WE... I have this habit of continuous testing while writing scripts, so I prefer code editing done directly in WE... because even with a completely no-syntax problem code, there are still lots of ways that your script can cause errors or problems in-game... and it's such a hassle to do the whole process whenever you want to change anything in the wurst codes...
 
Level 23
Joined
Jan 1, 2009
Messages
1,608
I hope this could be integrated into WE... I have this habit of continuous testing while writing scripts, so I prefer code editing done directly in WE... because even with a completely no-syntax problem code, there are still lots of ways that your script can cause errors or problems in-game... and it's such a hassle to do the whole process whenever you want to change anything in the wurst codes...

"the whole process" ?
The World Editor stays open - as you can see in the second video.
I think you guys have a wrong view on how this works.

So:
-Have Eclipse and WorldEditor open
-Create Package in Eclipse, modify, save file
-Save Map in Editor, test

Both can be open at the same time, there is no delay between saving/testing (saving in eclipse takes no time, you can immediately switch to WorldEditor).


So I really don't see your problem?
 

peq

peq

Level 6
Joined
May 13, 2007
Messages
171
Are function overloads implemented? This is very useful thing.
For those people who don't know what it is:
They are functions with same name but different argument count.

Yes, you can overload functions: http://peq.github.io/WurstScript/manual.html#function_overloading

If not, it is not hard to implement them, compiler can check number of arguments in function and add suffix "_N" for example.

Well, it was not as easy as that, because overloading also has to consider other features like overriding (in subclasses and modules) and generics.
 
Level 13
Joined
Mar 6, 2008
Messages
852
I just want to point out one big benefit of scripting with wurstscript.
It enhances teamwork.
One condition is that you know your team mate but if there is trust you can work efficiently on a map if you add dropbox into the whole process.

For example Crigges and me share a folder on dropbox. Whenever I save my process of the terrain work, the latest code gets compiled automatically and Crigges can still work on the code while I am testing the map.
 
Level 8
Joined
Nov 20, 2011
Messages
202
I just want to point out one big benefit of scripting with wurstscript.
It enhances teamwork.
One condition is that you know your team mate but if there is trust you can work efficiently on a map if you add dropbox into the whole process.

For example Crigges and me share a folder on dropbox. Whenever I save my process of the terrain work, the latest code gets compiled automatically and Crigges can still work on the code while I am testing the map.

This is one example how to share ur workspace, u can also do this via a GitHub and an eclispe plugin.
 
Level 23
Joined
Jan 1, 2009
Messages
1,608
I followed the installation till I Cnp the path to the wurst.dependencies and worked fine...bad news is, I cant see my w3x and wurst folder in the project explorer drop down...any idea why?...

If you select the wurst/ folder as project folder you will not see the map and the wurst/ folder.
If you select the projectfolder you will.
Just create your custom folders/scripts inside the folder and save-it wshould be working.
 
Status
Not open for further replies.
Top