Difference between revisions of "Advanced WML"

From The Battle for Wesnoth Wiki
m
m (Branch on Village Type: link macro)
Line 4: Line 4:
 
Suppose you capture a village and want the logic of the resulting event to branch on the basis of of the village type. At present there is no way to directly query for the village type, so here's a crude workaround. It just sets a variable on the basis of what type of village it is, and then you can branch on the basis of that variable's value. Here I use village_type for the variable to be set:
 
Suppose you capture a village and want the logic of the resulting event to branch on the basis of of the village type. At present there is no way to directly query for the village type, so here's a crude workaround. It just sets a variable on the basis of what type of village it is, and then you can branch on the basis of that variable's value. Here I use village_type for the variable to be set:
  
The logic is hacked from the predefined {STARTING_VILLAGES SIDE RADIUS}.
+
The logic is hacked from the predefined {{LinkMacroP|STARTING_VILLAGES|SIDE RADIUS}}.
  
 
Note: this example  has been hand-translated to terrain codes '''but not yet tested'''. If you test it and it works, please delete  this warning.  If you test it and it fails, please fix it and reinsert it here and then delete this warning.
 
Note: this example  has been hand-translated to terrain codes '''but not yet tested'''. If you test it and it works, please delete  this warning.  If you test it and it fails, please fix it and reinsert it here and then delete this warning.

Revision as of 19:43, 23 July 2008

Advanced WML

Branch on Village Type

Suppose you capture a village and want the logic of the resulting event to branch on the basis of of the village type. At present there is no way to directly query for the village type, so here's a crude workaround. It just sets a variable on the basis of what type of village it is, and then you can branch on the basis of that variable's value. Here I use village_type for the variable to be set:

The logic is hacked from the predefined Template:LinkMacroP.

Note: this example has been hand-translated to terrain codes but not yet tested. If you test it and it works, please delete this warning. If you test it and it fails, please fix it and reinsert it here and then delete this warning.

This macro assigns a value to village_type. Unfortunately we have to probe all the types to set it:

# Set a default value for safety (e.g., if you forget to probe for one of the possible types)
{VARIABLE village_type ordinary}
# Now step through the known types, telling the macro what value you want stored for each type:
{SET_VILLAGE_TYPE (Gg^Vh,Gg^Vht,Gs^Vht)  ordinary}
{SET_VILLAGE_TYPE (Gs^Vh)  woods}
{SET_VILLAGE_TYPE (Hh^Vhh,Ha^Vhh) hill}
{SET_VILLAGE_TYPE (Mm^Vhh)  mountain}
{SET_VILLAGE_TYPE (Dd^Vda,DD^Vdt) desert}
{SET_VILLAGE_TYPE (Aa^Vea)  snowy}
{SET_VILLAGE_TYPE (Uu^Vu,Uu^Vud)  underground}
{SET_VILLAGE_TYPE (Ss^Vhs,Ss^Vm)  swamp}
{SET_VILLAGE_TYPE (Ww^Vm)  water}

Now you have one of the values 'ordinary', 'woods', 'hill', etc. in the variable $village_type.

# Set the variable $village_type to VARIABLE_VALUE iff the village at $x1,$y1
# matches one of the types in VILLAGE_CODES.
#define SET_VILLAGE_TYPE TERRAIN VARIABLE_VALUE
   [store_locations]
      x,y=$x1,$y1 # x1 and y1 were defined by the event triggered when you captured the village
      radius=0    # don't want to catch any other villages
      variable=probe_village  # temporary where [store_locations] stores the village
      terrain={TERRAIN} # define village_type if-and-only-if the village is one of these types
   [/store_locations]
   {FOREACH probe_village i}
      # This won't execute if there are no villages in the list:
      {VARIABLE village_type {VARIABLE_VALUE}}
      {NEXT i}
   {CLEAR_VARIABLE probe_village}
   {CLEAR_VARIABLE i}
#enddef

Alternatively, you could put your own logic directly into SET_VILLAGE_TYPE, where the variable is defined, and save the extra case-based branching logic. The tradeoff is that you could then use the macro only for that one thing.

Recruit from a Ship

An example scenario where you can recruit units directly from a ship (acting as both your leader and keep) to the castles on the shores. No more need to have ships mysteriously be able to move to castles and keeps in order to recruit. You can also return units to the recall list by moving them next to the ship, which pops up a dialog asking you for confirmation.

You probably want to move the terrain stuff to the appropriate locations for campaign use (terrain.cfg and terrain-graphics.cfg). Note also that several macros from the WML Utilities page are used here.

The mechanism is simple: a custom terrain that looks and acts just like Shallow Water, with the exception that it also acts as a keep (recruit_from=true), is defined and placed under the ship whenever it moves next to a castle hex. Track is kept of old locations and they are removed when the ship moves, so the map doesn't become littered with the "ship keeps". Note, however, that only a "shallow water keep" is defined, and because of that the map has only shallow water next to the castles (otherwise, if the ship moved over deep water next to a castle, the deep water tile would suddenly appear as shallow water). So if you need to have both deep and shallow water next to castles, you need to define two types of water keeps and modify the code to handle both of them.

[terrain]
    id=ship_keep
    name= _ "Shallow Water"
    image=coast.png
    string=Kw
    aliasof=Ww
    submerge=0.4
    unit_height_adjust=-4
    recruit_from=true
[/terrain]

{TERRAIN_BASE_PROB            Kw coast3        30}
{TERRAIN_BASE_PROB            Kw coast2        30}
{TERRAIN_BASE                 Kw coast}
{TERRAIN_ADJACENT -202        Kw !Ww,Kw coast}
 
[scenario]
    id=Recruit_from_ship
    name= _ "Recruiting from a ship"
    map_data=" border_size=1
usage=map

Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Wo,  Ww,  Wo,  Ww,  Ww,  Ww,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Ww,  Ww,  Ww,1 Kh,  Ww,  Ww,  Ww,  Ww,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Ww,  Ww,  Ww,  Ww,  Ww,  Ww,  Ww,  Ww,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Ww,  Ww,  Ww,  Ww,  Ww, Chw, Chw, Chw,  Ww,  Ww,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Ww,  Ch,  Ch,  Gg,  Gg,  Gg,  Gg,  Gg,  Ch,  Ch,  Ww,  Wo,  Wo,  Wo
Wo,  Wo,  Ww,  Ww, Chr,  Gg,  Gg,  Gg,  Gg,  Gg,  Gg,  Gg,  Ww,  Ww,  Wo,  Wo,  Wo
Wo,  Wo,  Ww,  Ww,  Ww, Chw, Chs,  Gg,  Gg,  Gg,  Gg,  Gg,  Ww,  Ww,  Ww,  Wo,  Wo
Wo,  Wo,  Ww,  Ww,  Ww, Chw,  Gg,  Gg,  Gg,  Gg,  Gg,  Ww,  Ww,  Ww,  Wo,  Wo,  Wo
Wo,  Wo,  Ww,  Ww, Chr,  Gg,  Gg,  Gg,  Gg,  Gg,  Ch, Chw,  Ww,  Ww,  Wo,  Wo,  Wo
Wo,  Wo,  Ww,  Ww,  Ww, Chr,  Gg,  Gg,  Gg,  Ch,  Ww,  Ww,  Ww,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Ww,  Ww,  Ww, Chw,  Ww,  Ww,  Ww,  Ch,  Ww,  Ww,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Ww,  Ww,  Ww,  Ww,  Ww,  Ww,  Ww,  Ww,  Ww,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Wo,  Wo,  Ww,  Wo,  Ww,  Ww,  Ww,  Ww,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo,  Wo
"
    next_scenario=null
    turns=-1
    victory_when_enemies_defeated=no

    {MORNING}
    {AFTERNOON}
    {DUSK}
    {FIRST_WATCH}
    {SECOND_WATCH}
    {DAWN}

    [side]
        side=1
        controller=human
        type=Transport Galleon
        description=Agamemnon
        user_description= _ "The Agamemnon"

        canrecruit=yes
        gold=100
        recruit=Elvish Fighter,Elvish Archer
    [/side]

    [event]
        name=start

        [terrain]
            x,y=14,8
            letter=Ww
        [/terrain]

        {VARIABLE Agamemnon_next_to_castle no}

        [objectives]
            note= _ "Move the Agamemnon next to a castle in order to recruit. Units can be returned to the ship by moving them next to it."
        [/objectives]
    [/event]

    [event]
        name=moveto
        first_time_only=no

        [filter]
            description=Agamemnon
        [/filter]

        {IF_VAR Agamemnon_next_to_castle equals yes (
            [then]
                [terrain]
                    x=$Agamemnon_previous_x
                    y=$Agamemnon_previous_y
                    terrain=Ww
                [/terrain]
            [/then]
        )}

        [store_locations]
            x,y=$x1,$y1
            radius=1
            terrain=Ch,Kh,Chr,Ce,Cud,Chw,Chs
            variable=Agamemnon_adjacent_castles
        [/store_locations]

        {IF_VAR Agamemnon_adjacent_castles.length greater_than 0 (
            [then]
                {IF_TERRAIN $x1 $y1 Ww (
                    [then]
                        {VARIABLE Agamemnon_next_to_castle yes}
                        {VARIABLE Agamemnon_previous_x $x1}
                        {VARIABLE Agamemnon_previous_y $y1}

                        [terrain]
                            x=$x1
                            y=$y1
                            terrain=Kw
                        [/terrain]
                    [/then]
                )}
            [/then]

            [else]
                {VARIABLE Agamemnon_next_to_castle no}

                [allow_undo][/allow_undo]
            [/else]
        )}

    [/event]

    [event]
        name=moveto
        first_time_only=no

        [filter]
            side=1

            [not]
                description=Agamemnon
            [/not]
        [/filter]

        [store_locations]
            [filter]
                description=Agamemnon
            [/filter]

            x,y=$x1,$y1
            radius=1
            variable=adjacent_Agamemnon
        [/store_locations]

        {IF_VAR adjacent_Agamemnon.length greater_than 0 (
            [then]
                {STORE_UNIT_VAR x,y=$x1,$y1 user_description boarding_unit_name}

                [message]
                    speaker=Agamemnon
                    message= _ "Return $boarding_unit_name to the Agamemnon?"
                    caption= _ "Board the Agamemnon"

                    [option]
                        message= _ "Yes"

                        [command]
                            {PUT_TO_RECALL_LIST x,y=$x1,$y1}
                        [/command]
                    [/option]

                    [option]
                        message= _ "No"
                    [/option]
                [/message]

                {CLEAR_VARIABLE boarding_unit_name}
            [/then]
        )}

        [allow_undo][/allow_undo]

        {CLEAR_VARIABLE adjacent_Agamemnon}
    [/event]
[/scenario]

Point Rotation Scheme

Use ROTATE_POINT_(COUNTER)CLOCKWISE_AROUND to rotate the point. You take a focus (made up of X and Y numbers), and a point (made up of X and Y variables) and the point will move as if attached to the focus by string and shoved sideways. (bad analogy, I know :P)

A simple use is: store a unit, then {ROTATE_POINT_CLOCKWISE_AROUND unit.x unit.y 11 11}, then unstore it.

WARNING: results are undefined if you move the unit off the map. I don't know what would happen, but I think it would stay on the map... and land somewhere... maybe. It depends on which side of the map it goes off.

#POINT_X and Y have to be variable names; FOCUS_X and Y have to be numbers (which can be e.g. $q)
#this makes POINT equal to POINT rotated 60 degrees around FOCUS.
#define ROTATE_POINT_COUNTERCLOCKWISE_AROUND POINT_X POINT_Y FOCUS_X FOCUS_Y
	{_ROTATE_POINT_AROUND {POINT_X} {POINT_Y} {FOCUS_X} {FOCUS_Y} 5 1 0 6 -1}
#enddef
#define ROTATE_POINT_CLOCKWISE_AROUND POINT_X POINT_Y FOCUS_X FOCUS_Y
	{_ROTATE_POINT_AROUND {POINT_X} {POINT_Y} {FOCUS_X} {FOCUS_Y} 6 0 5 1 1}
#enddef
#define _ROTATE_POINT_AROUND POINT_X POINT_Y FOCUS_X FOCUS_Y V1 V2 V3 V4 O1
	{VAR_OP x_diff to_variable {POINT_X}}
	{VAR_OP negative_focus_x format {FOCUS_X}}
	{VAR_OP negative_focus_x multiply -1}
	{VAR_OP x_diff add $negative_focus_x}
	
	{VAR_OP x_counter to_variable x_diff}
	{VAR_OP x_loc format {FOCUS_X}}
	{VAR_OP y_loc format {FOCUS_Y}}
	[while]
	{VAR_IF x_counter != 0}
		[do]
		[store_locations]
		x=$x_loc
		y=$y_loc
		radius=1
		variable=next_locations
		[/store_locations]
		[if]
		{VAR_IF x_counter > 0}
			[then]
			{VAR_OP x_loc to_variable next_locations[{V1}].x}
			{VAR_OP y_loc to_variable next_locations[{V1}].y}
			{VAR_OP x_counter add -1}
			[/then]
			[else]
			{VAR_OP x_loc to_variable next_locations[{V2}].x}
			{VAR_OP y_loc to_variable next_locations[{V2}].y}
			{VAR_OP x_counter add 1}
			[/else]
		[/if]
		[/do]
	[/while]
	
	{VAR_OP y_diff to_variable y_loc}
	{VAR_OP negative_point_y to_variable {POINT_Y}}
	{VAR_OP negative_point_y multiply -1}
	{VAR_OP y_diff add $negative_point_y}
	
	{VAR_OP y_counter to_variable y_diff}
	{VAR_OP x_loc2 format {FOCUS_X}}
	{VAR_OP y_loc2 format {FOCUS_Y}}
	{VAR_OP modifier to_variable x_diff}
	{VAR_OP modifier multiply {O1}}
	{VAR_OP y_loc2 add $modifier}
	
	[while]
	{VAR_IF y_counter != 0}
		[do]
		[store_locations]
		x=$x_loc2
		y=$y_loc2
		radius=1
		variable=next_locations
		[/store_locations]
		[if]
		{VAR_IF y_counter > 0}
			[then]
			{VAR_OP x_loc2 to_variable next_locations[{V3}].x}
			{VAR_OP y_loc2 to_variable next_locations[{V3}].y}
			{VAR_OP y_counter add -1}
			[/then]
			[else]
			{VAR_OP x_loc2 to_variable next_locations[{V4}].x}
			{VAR_OP y_loc2 to_variable next_locations[{V4}].y}
			{VAR_OP y_counter add 1}
			[/else]
		[/if]
		[/do]
	[/while]
	
	{VAR_OP {POINT_X} to_variable x_loc2}
	{VAR_OP {POINT_Y} to_variable y_loc2}
#enddef

See Also