-
Notifications
You must be signed in to change notification settings - Fork 12
/
D0S.DungeonMap
140 lines (122 loc) · 4.99 KB
/
D0S.DungeonMap
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
key.p()
isopen("arcade")
:local vector room
:local vector scale
:local vector offset
:local double incr
:local int state
:budget_cap max
#name D0S.DungeonMap 1.2
:name {name}
; Make it easy to customize the colors
#complete "#008000"
#black "#000000"
#player "#FFFFFF"
; This is abstracted out to make it easier to test.
#isCompleted(room) adventure.isCompleted({room})
; Swap the names of these macros to draw a big inverted circle instead.
; This doesn't require any adventure progress to test the drawing parts of the script.
#isCompletedTest(room) (x({room}) - 127.) ^ 2. + (y({room}) - 127.) ^ 2. > 10000.
; The overall strategy here is to recursively break down the problem:
; First, draw 1 square for the entire map, then divide that into 4 subsquares,
; then divide each of those into 4 subsquares, etc. This works out nicely since
; the map is a nicely-divisible 256x256.
;
; We use the lower-lefthand corner (the smallest coordinates) to represent the
; color of the entire box. This has efficiency benefits: When we go to draw the
; four subsquares, we already know that they are already painted the same color
; as the lower-left square, so we use that to optimize and avoid drawing when we
; don't have to. This is a win, since the draw calls (as opposed to the number
; of pixels filled) are slow.
;
; The recursive strategy means we can use the identical optimization logic at
; every scale, from the topmost down to the 2x2 room level, which is the last
; level that needs to be drawn. It also means that a rough map gets drawn in
; very quickly, since only 25% of the time is spent on *all* the higher layers
; and 75% is spent on the final 2x2 pass.
#varname "<size=0>^adv#"
goto(if(\
contains(impulse(), "{name}") ||\
utcnow() - s2d(sub(gsg({varname}), 0, index(gsg({varname}), "<", 0)), 0.) < 5000000.,\
execloop,\
if(contains(gsg({varname}), "<color"), done, normal)\
))
; Infinite loop of spawning scripts, to crash the AI
execloop:
execute("{name}")
waitframe()
goto(execloop)
normal:
; This adjustment value breaks ties when rounding. Computer arithmetic does
; round-to-even in the case of exact X.5 values; this causes issues for some
; of the mathematical optimizations we've done inside the canvas.draw() calls.
; We could properly round all parts of the expression, but it's (slightly)
; cheaper to add this adjustment in the appropriate place to ensure there will
; never be ties.
#adj (1. / (2048. * 3.))
scale = vec(min(height.d(), width.d()) / 256., min(height.d(), width.d()) / 256.)
offset = vec(\
round(max(0., width.d() - height.d()) * 0.5) + {adj},\
round(max(0., height.d() - width.d()) * 0.5) + {adj}\
)
gss({varname}, utcnow() . "</size><color=#FFFFFF>Dungeon Map drawing...</color>")
canvas.rect(\
offset,\
scale * vec(256., 256.),\
if({isCompleted(room)}, {complete}, {black})\
)
top:
incr = if(incr == 0., 128., incr * 0.5)
room = vec(0., 0.)
loop:
; Store the 2x2 state of rooms bitwise in "state". We will use this to index
; various lookup tables when it comes time to draw the results.
state = if({isCompleted(room)}, 1, 0) +\
if({isCompleted(room + vec(incr, 0.))}, 2, 0) +\
if({isCompleted(room + vec(0., incr))}, 4, 0) +\
if({isCompleted(room + vec(incr, incr))}, 8, 0)
; Depending on the pattern, we need to draw 2, 1, or even 0 rects.
; These tables are hand-generated because they're small and I CBA to write
; code to autogenerate them.
goto(s2i(sub("2011110110111102", state, 1), 99) + draws)
draws:
; The 2 rects case. This one is optimized so that it is *always* the upper-left corner.
canvas.rect(\
offset + (room + vec(0., incr)) * scale,\
(room + vec(incr, 2. * incr)) * scale + vec({adj}, {adj}) - vec(\
round(x(room * scale) + {adj}),\
round((y(room) + incr) * y(scale) + {adj})\
),\
if({isCompleted(room)}, {black}, {complete})\
)
; The 1 rect case (and 2nd part of 2 rects). This is much more complicated
; because we need lookup tables for the positions and sizes.
#lut(table, choice1, choice2) if(contains(sub("{table}", state, 1), "0"),{choice1},{choice2})
canvas.rect(\
offset + (room + vec(\
{lut(0110011111100110, 0., incr)},\
{lut(0001100110011000, 0., incr)}\
)) * scale,\
(room + vec(\
{lut(0111011111101110, incr, 2. * incr)},\
{lut(0101110110111010, incr, 2. * incr)}\
)) * scale + vec({adj}, {adj}) - vec(\
round((x(room) + {lut(0110011111100110, 0., incr)}) * x(scale) + {adj}),\
round((y(room) + {lut(0001100110011000, 0., incr)}) * y(scale) + {adj})\
),\
if({isCompleted(room)}, {black}, {complete})\
)
room = vec((x(room) + 2. * incr) % 256., y(room) + if(x(room) + 2. * incr >= 256., 2. * incr, 0.))
goto(if(y(room) < 256., loop, if(incr > 1., top, show_player)))
show_player:
canvas.rect(\
offset + adventure.roomCoords() * scale,\
(adventure.roomCoords() + vec(1., 1.)) * scale - vec(\
round(x(adventure.roomCoords() * scale)),\
round(y(adventure.roomCoords() * scale))\
),\
{player}\
)
done:
; Don't unset the var if we're not the master script
gss({varname}, if(incr == 0., gsg({varname}), "</size>"))