Advanced Optimisations and Hacks

From The Battle for Wesnoth Wiki
Revision as of 17:43, 29 October 2013 by Dugi (talk | contribs) (Initial entry)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

From time to time, especially if working with some advanced WML, you will come to need things that WML does not support. And you might be in a bad need to do that. This isn't a tricky of using WML, it is actually doing what the developers didn't plan to support. I have mostly used it to write much more effective codes at the cost of low understandability.

Note: It is supposed that you are skilled with WML. It is expected that you know most WML tags, comprehend the contents of save files and have good understanding of variables and variable arrays in WML.


Many common events in scenarios

This trick is useful if your (multiple) scenarios contain a lot of common WML. This happens if your campaign adds some additional functionality, like inventories. It might be also useful if you are using repetitive macros that fire the same events. Or when you need to add event-based weapon specials with [object]s and therefore need to insert these events into all scenarios and not into the unit via macros using unbalanced WML (as it is usually done). It might be even useful if you have a lot of death message events.

The problem that comes from using the same WML events in many scenario is that they are preprocessed and loaded into RAM once for each scenario, and it can be dozens of times, resulting in huge RAM consumption and annoying loading times. This tricks inserts in through a unit so that it is loaded only once. Its downside is that prestart events can't be loaded this way, but start events can set up pretty much anything, they are fired before you see the scenario.

First, create a dummy unit like this:

[unit_type]
    id=Event Loader   #It can be different, but it is assumed in the following code that it will be 'Event Loader',
                      #if you name it differently, you will have to rename it in the rest of the code!
    alignment=neutral
    advances_to=null
    cost=1
    hide_help=true
    do_not_list=yes
    {GLOBAL_EVENTS_LIST}
[/unit_type]

The {GLOBAL_EVENTS_LIST} macro contains all the events you want to insert into all scenarios. This thick is only useful if the list is very long. Example (too short to be worth using this hack, but it is a working example):

#define GLOBAL_EVENTS_LIST
[event]
    # This events gives traits to all ally/enemy leaders (single player is expected)
    name=start
    [store_unit]
          canrecruit=yes
          [not]
                side=1
          [/not]
          variable=leaders
          kill=yes
    [/store_unit]
    {FOREACH leaders i}
          [unit]
                id=$leaders[$i].id
                name=$leaders[$i].name
                gender=$leaders[$i].gender
                x=$leaders[$i].x
                y=$leaders[$i].y
                side=$leaders[$i].side
                random_traits=yes
                canrecruit=yes
           [/unit]
     {NEXT i}
     {CLEAR_VARIABLE leaders}
[/event]
[event]
      # This makes petrification last only for a single turn
      name=turn refresh
      first_time_only=no
      [modify_unit]
             [filter]
                    side=$side_number
                    [filter_wml]
                            [status]
                                    petrified=yes
                            [/status]
                    [/filter_wml]
             [/filter]
             [status]
                     petrified=no
             [/status]
      [/modify_unit]
[/event]
#enddef

Now, we need to add the events in the unit into the scenario. It is trivial. Keep on mind that it means that only start events can be loaded this way, prestart events will be loaded, but will never be fired. It is useful to wrap it in a macro so that only one line makes it load.

#define GLOBAL_EVENTS
[event]
     name=prestart
     [unit]
          type=Event Loader
          side=1
          x,y=1,1
     [/unit]
     [kill]
          type=Event Loader
          animate=no
     [/kill]
[/event]
#GLOBAL_EVENTS

This should be placed in all events. If you need to control your events with some variables it will check, it might be useful to use them as arguments for this macro and set them in the event.

There is also a way to do it without touching the scenarios at all, but its use in multiplayer is severely limited. It is based on lua, which is always executed when the scenario is loaded (even if it is in the middle from a save file, because lua environment isn't saved), and can be later accessed only by defining new WML tags or event hooks. Placing this into your _main (inside the campaign's #ifdef wraps, to avoid hacking other campaigns!).

[lua]
   code = <<
     local helper = wesnoth.require "lua/helper.lua"
     -- We need to check whether the events were loaded in this scenario
     local events_loaded = wesnoth.get_variable( "events_loaded" )
     if events_loaded ~= true then
            wesnoth.set_variable("events_loaded", true)
            -- We don't want this code to believe that the events were loaded because
            -- the variable recording that they are saved remains from previous scenario
            wesnoth.wml_actions.event{ 
                    name="victory" ,
                    { "clear_variable", {
                            name="events_loaded"
                    } }
            }
            -- And now we do WML action itself
            wesnoth.wml_actions.unit{ id = "Event Loader" , side = 1, x = 1, y = 1}
            wesnoth.wml_actions.kill{ type = "Event Loader", animate = false}
     end
>>
[/lua]