1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
|
= WadC tutorial
Jonathan Dowland <jon@dow.land>
:homepage: https://jmtd.net/wadc/
This tutorial hopes to walk you through WadC. We start with the very basics
and drawing some very simple, boring square rooms. By the end I hope we are
generating some intricate dungeons in the style of an old board game I used
to love called "Warhammer Quest", complete with some randomness to make the
resulting map different every time you build it.
== Set up
Make sure you have the WadC JAR somewhere and have Java installed. You need
at least version 8. To test, try double-clicking on the JAR: If WadC appears,
success! If not, you probably haven't got Java on properly.
You will also need a Doom engine, a Doom IWAD (game data), and a nodes
builder. Use your favourite, otherwise I recommend:
Java JRE:: link:http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html[via Oracle]
Engine:: link:https://www.chocolate-doom.org/wiki/index.php/Crispy_Doom#Download[Crispy Doom]
IWAD:: link:https://www.gog.com/game/doom_ii_final_doom[Doom 2 via GOG.com]
Node builder:: link:http://glbsp.sourceforge.net[glbsp]
Put them all somewhere and let WadC know where they are by either editing
`wadc.cfg` in any editor of your choice, or in WadC itself, or simply write
something like the following into the open WadC window and Run the result
(adjust the paths to reflect where you put the stuff):
doomexe("/Users/jon/git/crispy-doom/src/crispy-doom")
doomargs("-iwad /Users/jon/games/doom2.wad -warp 1 -file")
bspcmd("/Users/jon/Downloads/glbsp-2.24-source/glbsp")
iwad("/Users/jon/games/doom2/doom2.wad")
Evaluating the above updates WadC's in-memory configuration, which is saved
out to `wadc.cfg` when you close the program.
== High-level overview
Imagine a big piece of graph paper and a pen. There's a robotic arm holding the
pen over the graph paper. You can tell the arm to move up or down, to the left
or to the right. You can tell the arm to raise or lower the pen from the paper.
You can tell the arm to swap the pen for one of another colour. You can tell
the arm to rotate by 90 degrees either to the left or to the right; such that
the new forwards/upwards direction is what was formerly left/right.
This is a little bit like the old educational tool LOGO: where the robotic arm
was called a "Turtle". For WadC, we'll call it the cursor.
In WadC, you can move the cursor up, down, to the left or right; you can rotate
by 90 degrees in either direction; you can move the "pen" up or down,
controlling whether you are drawing lines or just moving about. The WadC
equivalent of "pen colour" is a set of properties about the objects that will
be created by your instructions, including what floor and ceiling texture to
use when creating sectors; what textures to use when creating lines; what type
of things to create when creating things; what properties for those things, or
lines, including tags and special types such as doors and buttons.
We can also group together sets of these instructions into bundles called
functions, you can refer to those functions to execute all the steps within,
letting you repeat things easily. You can also leave placeholders within the
functions that are filled in when you refer to them. WadC comes with lots of
existing functions that help you to work at a higher conceptual level than
"pen up, pen down".
== Hello world
The very first thing we will try is perhaps the simplest possible functional
WadC level: a box with the player in it. Consider this the WadC equivalent of
"Hello world":
.tutorial/1.wl
----
include::tutorial/1.wl[]
----
image::tutorial/1.png[title="1.wad"]
The very first line tells WadC to include the library file "standard.h". If you
have ever programmed in a mainstream language before, you may have seen things
like this elsewhere, such as C's `#include` or `import` in Python, etc.
We have defined a function called "main". This is the entry point into your
WadC program. We call three functions within it: box, which draws a box room;
movestep, which moves the "pen", and thing, which creates a thing wherever the
cursor is.
Try it out. You can probably figure out what the arguments to box and movestep
are, especially if you run the resulting WAD.
`box` and `movestep` are all defined within `standard.h`, by the way.
Notice that we haven't had to think about whether the cursor is up or down, the
details of that are hidden away within the definitions for `box` and `movestep`.
We also haven't had to decide on textures for walls or floors, or specifically
ask for a player 1 start thing. We're relying on WadC's default values.
== User-defined functions, pushpop
Where do we go from here? Let's make a few tweaks to this boring level.
.tutorial/2.wl
----
include::tutorial/2.wl[]
----
Here we've defined a second function called `boring`. We've moved the call to
`box` into it, and we're now calling boring from within main.
If you try building this, you will end up with exactly the same map as before.
Not too exciting yet!
What happens if we try calling `boring` twice?
.tutorial/3.wl
----
include::tutorial/3.wll[]
----
It didn't work: the second box is trying to overdraw on the first. We can fix
this by adding a `move`, to move the cursor onwards after the `box`:
.tutorial/4.wl
----
include::tutorial/4.wl[]
----
image::tutorial/4.png[title="4.wad"]
Et voila, two boring rooms instead of one! However, where is the player start?
It's a bit weird to be putting the player start in last. Instead, let's place
it first
.tutorial/5.wl
----
include::tutorial/5.wl[]
----
The player is now stuck in the corner of the first room. We're moving the
cursor, placing the player, but then drawing onwards from that same point. We
need to temporarily move into the space the room will occupy, place the player,
and move back.
.tutorial/6.wl
----
include::tutorial/6.wl[]
----
Great, that works. This is something that has to be done so frequently,
there's a function in `standard.h` to help you do it:
.tutorial/7.wl
----
include::tutorial/7.wl[]
----
If you've ever programmed in a C or C-like language, this may look a bit
strange to you. What argument are we passing `pushpop`? Where did the second
`movestep` go? This demonstrates two things
* After it has been evaluated, `pushpop` returns the cursor to the position
it was in when it started. So the second `movestep` isn't necessary. We
could move the cursor wherever we liked and it would be back at the
beginning after `pushpop` was done.
* Any function argument in WadC can contain multiple expressions. Any sequence
of expressions next to each other are concatenated into one.
== Things to do
Let's liven up the box a bit: let's put a pedestal in the middle and add
something to do (or shoot).
.tutorial/8.wl
----
include::tutorial/8.wl[]
----
image::tutorial/8.png[title="8.wad"]
We introduce a new function `ibox`, which works just like `box`, except it
creates an _inner_ box, with the outer edges of the linedefs belonging to
the outer sector.
We see `thing` again, but this time preceded by `formerhuman`. The first
time we used `thing`, a player 1 start was created: that's the default
thing if nothing else is specified. `formerhuman` changes the thing type
to a zombieman.
== Different pedestals
Let's make each pedestal have a different thing on it.
.tutorial/9.wl
----
include::tutorial/9.wl[]
----
Here we've introduced arguments for our `boring` function. So far, we've
written two functions (`main` counts) but none of them accepted any
arguments. We've used plenty of other functions that accepted arguments.
Now we know how to write them.
We've had to add `pickups.h` to the list of includes at the top in order
to get access to the `shotgun` definition.
== Some randomness
Let's make things more unpredictable by introducing the choice operator.
.tutorial/10.wl
----
include::tutorial/10.wl[]
----
Now we get either a shotgun or a chaingun, and must face either a
zombieman or a shotgun guy.
The braces ({}) are not strictly necessary here but they might be needed
in other places where you might want to use the choice operator; until
you understand WadC better it's best to just use them all the time.
== Turning corners
We can use these buildings blocks to assemble a ring of rooms quite easily.
.tutorial/11.wl
----
include::tutorial/11.wl[]
----
image::tutorial/11.png[title="11.wad"]
Here we introduce `rotright`. It and its sibling `rotleft` simply rotate
the cursor 90 degrees one way or the other. To think, we've come this far
without covering turning around yet!
We also use a third function to encapsulate our choice of random monsters.
The script is starting to look a bit ugly, with lots of repetition. Let's
tidy it up.
.tutorial/12.wl
----
include::tutorial/12.wl[]
----
What have we done here? Firstly, we made the decision to move your random
weapon off a pedestal and just put it at your feet at the start of the map.
Once we had done that, we started to have a repeating pattern of functions
boring2
boring2
rotright
move(256)
The `standard.h` header has a number of simple functions to aid with managing
repeating code like this. Here we use `twice`, er, twice, and `triple` once.
The use of the `twice` within the `triple` is possibly a bit over the top.
Sometimes you have to make a judgement call as to whether it helps or hinders
legibility. Which do you think is easier to read?
triple(boring2 boring2 rotright move(256))
triple(twice(boring2) rotright move(256))
We've also re-arranged the order of the boring rooms slightly. You may notice
that the starting room is now in the "West" position rather than the "South
West".
== Now for a corridor
Let's try another routine. First of all, can you remember where the cursor is?
I can't. If you are using the WadC GUI, a quick way to find out is to put a
temporary thing at the end of the script, or to add a short wall that goes
nowhere. Try adding `thing` and/or `straight(64)` right at the end of the
`main` function and look at the result in the GUI. Is it clearer?
Let's try adding a new type of room to the map. Considering that the existing
rooms are 256x256 squares, and that the cursor is generally positioned in the
top-leftmost point of each room after we've drawn it using `boring`. Let's
define a corridor that doesn't extend from that corner but from a way into
the square room.
.tutorial/13.wl
----
include::tutorial/13.wl[]
----
image::tutorial/13.png[title="13.wad"]
Before drawing the corridor, we first move inwards a bit, because we want the
corridor inset. Just as with the `boring` routine, we then move outwards again,
so we're positioned ready to call one of our routines straight away.
I've tacked a final `boring2` on the end just so you can see that things all
line up.
== Older tutorial stuff to integrate into the above narrative
Works now. Another room type, a bend
leftturn {
movestep(96,96)
turnaround
box(0,128,128,96,64)
movestep(0,64)
box(0,128,128,64,32)
rotright
movestep(32,-96)
}
dev tip: when developing something like this, if you get lost,
try putting straight(64) in places to see where the cursor is
at that point in time
time for randomroom!
randomroom {
boring | corridor | leftturn
}
try chaining it!
main {
for(1, 5, randomroom)
}
refresh a load of times. eventually, you will hit a situation where one
of the rooms tries to draw into the space occupied by another.
let's review our "contract" for these rooms:
* each room draws from bottom-left and puts cursor in place for next block
to be run immediately after
* each room self contained in 128x128
(we also need to agree on where the join points are. this is a bit loose.)
we need to implement a blockmap:
2d coordinate of 128x128 blocks, (x,y) starting 0,0 and growing vertically/right
each room will check to see whether the 'block' it's going into is occupied
or not, and either bail out, or draw and then mark that block as occupied
(at this point look at lisp.wl in examples; this will be a building block for
our blockmap)
|