-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathcommon.lua
More file actions
1207 lines (1038 loc) · 32.4 KB
/
Copy pathcommon.lua
File metadata and controls
1207 lines (1038 loc) · 32.4 KB
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
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
--
-- General-purpose WML actions library
--
-- codename Naia - Project Ethea phase 1 campaigns shared library
-- Copyright (C) 2006 - 2026 by Iris Morelle <iris@irydacea.me>
--
-- See COPYING for usage terms.
--
---
-- This WML tag does nothing. Literally nothing at all.
--
-- However, because its contents aren't validated in any fashion, you can
-- potentially use it to deliver context information to saved games for easy
-- debugging of code in them.
---
function wesnoth.wml_actions.no_op(cfg)
end
local INVERT_DIRECTION = {
s = "n", sw = "ne", nw = "se",
n = "s", ne = "sw", se = "nw",
}
local DEFAULT_DIRECTION = "sw"
---
-- Assigns a given variable (presumed to be a direction value)
-- the opposite of its current contents. If the variable doesn't
-- seem to be a direction value, SE is used, setting it to NW.
--
-- [invert_direction]
-- variable="direction"
-- [/invert_direction]
---
function wesnoth.wml_actions.invert_direction(cfg)
local variable = cfg.variable or "direction"
local dir = wml.variables[variable]
if INVERT_DIRECTION[dir] then
wml.variables[variable] = INVERT_DIRECTION[dir]
else
wml.variables[variable] = INVERT_DIRECTION[DEFAULT_DIRECTION]
end
end
---
-- Gets the relative direction of a source hex to a target hex.
-- Useful to determine in which direction a unit should be facing
-- (from the source) to look at another unit (at the target).
--
-- [store_direction]
-- from_x,from_y= ...
-- to_x,to_y= ...
-- opposite=false
-- variable="direction"
-- [/store_direction]
--
-- Or:
--
-- [store_direction]
-- [from]
-- ... SLF ...
-- [/from]
-- [to]
-- ... SLF ...
-- [/to]
-- opposite=false
-- variable="direction"
-- [/store_direction]
---
function wesnoth.wml_actions.store_direction(cfg)
local from_slf = wml.get_child(cfg, "from")
local to_slf = wml.get_child(cfg, "to")
local a = { x = cfg.from_x, y = cfg.from_y }
local b = { x = cfg.to_x , y = cfg.to_y }
if from_slf then
a.x = wesnoth.map.find(from_slf)[1].x
a.y = wesnoth.map.find(from_slf)[1].y
end
if to_slf then
b.x = wesnoth.map.find(to_slf)[1].x
b.y = wesnoth.map.find(to_slf)[1].y
end
if not a.x or not a.y or not b.x or not b.y then
wml.error "[store_direction] missing coordinate!"
end
local variable = cfg.variable or "direction"
local opposite = cfg.opposite or false
local dir = wesnoth.map.get_relative_dir(a, b)
if opposite then
dir = INVERT_DIRECTION[dir] or INVERT_DIRECTION[DEFAULT_DIRECTION]
end
wml.variables[variable] = dir
end
---
-- Changes one or more units' facing to follow the specified location, unit,
-- or direction.
--
-- [set_facing]
-- [filter]
-- ... SUF ...
-- [/filter]
-- [filter_location]
-- ... SLF ...
-- [/filter_location]
-- opposite=false
-- [/set_facing]
--
-- Or:
--
-- [set_facing]
-- [filter]
-- ... SUF ...
-- [/filter]
-- [filter_second]
-- ... SUF ...
-- [/filter_second]
-- opposite=false
-- [/set_facing]
--
-- Or:
--
-- [set_facing]
-- [filter]
-- ... SUF ...
-- [/filter]
-- facing= ... direction ...
-- opposite=false
-- [/set_facing]
---
function wesnoth.wml_actions.set_facing(cfg)
local suf = wml.get_child(cfg, "filter") or
wml.error("[set_facing] Missing unit filter")
local facing = cfg.facing
local target_suf = wml.get_child(cfg, "filter_second")
local target_slf = wml.get_child(cfg, "filter_location")
local opposite = cfg.opposite or false
local target_loc, target_u
if not facing then
if target_suf then
target_u = wesnoth.units.find_on_map(target_suf)[1] or
wml.error("[set_facing] Could not match the specified [filter_second] unit")
elseif target_slf then
target_loc = wesnoth.map.find(target_slf)[1] or
wml.error("[set_facing] Could not match the specified [filter_location] location")
end
end
local units = wesnoth.units.find_on_map(suf) or
wml.error("[set_facing] Could not match any on-map units with [filter]")
for i, u in ipairs(units) do
local new_facing
if facing then
new_facing = facing
elseif target_u then
new_facing = wesnoth.map.get_relative_dir(
{ x = u.x, y = u.y },
{ x = target_u.x, y = target_u.y }
)
elseif target_loc then
new_facing = wesnoth.map.get_relative_dir(
{ x = u.x, y = u.y },
{ x = target_loc[1], y = target_loc[2] }
)
else
wml.error("[set_facing] Missing facing or [filter_second] or [filter_location]")
end
if opposite then
new_facing = INVERT_DIRECTION[new_facing] or INVERT_DIRECTION[DEFAULT_DIRECTION]
end
if new_facing ~= u.facing then
u.facing = new_facing
-- HACK:
-- Force Wesnoth to re-read the unit's current facing and update the game
-- display accordingly. Against what one would normally expect, calling
-- [redraw] does *not* work as an alternative.
u:extract()
u:to_map()
end
end
end
---
-- Iterates over a comma-separated list of items.
--
-- The syntax is the same as [foreach], except that list= is used instead of
-- array=, and there is no readonly= option (modifying the list in the middle
-- of the loop IS possible but causes no change in behavior).
--
-- [list_foreach]
-- list=(variable name)
-- variable=this_item
-- index_var=i
-- [do]
-- ... actions ...
-- [/do]
-- [/list_foreach]
---
function wesnoth.wml_actions.list_foreach(cfg)
if wml.child_count(cfg, "do") == 0 then
wml.error("[list_foreach] does not contain any [do] tags")
end
local item_name = cfg.variable or "this_item"
local i_name = cfg.index_var or "i"
local list_name = cfg.list or wml.error("[list_foreach] missing required list= attribute")
if wml.variables[list_name] == nil then
-- Consider a missing variable an empty list as per WML convention
return
end
-- Make sure the list variable is a scalar
local legal_types <const> = { number = 1, string = 1, boolean = 1 }
if legal_types[type(wml.variables[list_name])] == nil then
wml.error("[list_foreach] List variable must be a scalar")
end
local list = stringx.split(wml.variables[list_name])
if #list == 0 then
return
end
do
local this_item <close> = utils.scoped_var(item_name)
local i <close> = utils.scoped_var(i_name)
for index, value in ipairs(list) do
wml.variables[item_name] = value
wml.variables[i_name] = index - 1
-- Perform actions
for do_child in wml.child_range(cfg, "do") do
local action = utils.handle_event_commands(do_child, "loop")
if action == "break" then
utils.set_exiting("none")
goto exit
elseif action == "continue" then
utils.set_exiting("none")
break
elseif action ~= "none" then
goto exit
end
end
end
end
::exit::
end
---
-- Spawns mechanical "Door" units on gate terrain hexes.
--
-- [setup_doors]
-- side=(side number)
-- terrain=(optional terrain filter string, default "*^Z\,*^Z/")
-- ... optional SLF ...
-- [/setup_doors]
---
function wesnoth.wml_actions.setup_doors(cfg)
local owner_side = cfg.side or
wml.error("[setup_doors] No owner side= specified")
cfg = wml.parsed(cfg)
if cfg.terrain == nil then
cfg["terrain"] = "*^P*/,*^P*\\,*^P*|,*^Z\\,*^Z/"
end
cfg.side = nil
local locs = wesnoth.map.find(cfg)
for k, loc in ipairs(locs) do
-- Is this an open door?
local is_closed = not loc.overlay_terrain:match("o$")
local is_vacant = not wesnoth.units.get(loc.x, loc.y)
if is_closed and is_vacant then
wesnoth.units.to_map({
type = "Door",
side = owner_side,
id = ("__door_X%dY%d"):format(loc.x, loc.y),
}, loc.x, loc.y)
end
end
end
---
-- Opens door tiles. Takes a SLF and nothing else.
--
-- Note that it only matches actual gate tiles (^P*, ^Zz*). The terrain= key
-- in the SLF is ignored as a result.
---
function wesnoth.wml_actions.open_doors(cfg)
cfg = wml.parsed(cfg)
cfg["terrain"] = "*^P*,*^Zz*"
local locs = wesnoth.map.find(cfg)
for k, loc in ipairs(locs) do
if loc.overlay_terrain:match("o$") then
-- It's open, ignore
goto continue
end
local newtile = wesnoth.current.map[{loc.x, loc.y}] .. "o"
wesnoth.current.map[{loc.x, loc.y}] = wesnoth.map.replace_overlay(newtile)
::continue::
end
end
---
-- Stores a list of unit ids matching a certain filter.
--
-- To store ids from recall lists, x and y must be either absent
-- or set to "recall" in the base filter (not subfilters!).
--
-- [store_unit_ids]
-- [filter]
-- ...
-- [/filter]
-- variable=ids_store
-- [/store_unit_ids]
---
function wesnoth.wml_actions.store_unit_ids(cfg)
local filter = wml.get_child(cfg, "filter") or
wml.error "[store_unit_ids] missing required [filter] tag"
local var = cfg.variable or "units"
local idx = 0
if cfg.mode == "append" then
idx = wml.variables[var .. ".length"]
else
wml.variables[var] = nil
end
for i, u in ipairs(wesnoth.units.find_on_map(filter)) do
wml.variables[("%s[%d].id"):format(var, idx)] = u.id
idx = idx + 1
end
if (not filter.x or filter.x == "recall") and (not filter.y or filter.y == "recall") then
for i, u in ipairs(wesnoth.units.find_on_recall(filter)) do
wml.variables[("%s[%d].id"):format(var, idx)] = u.id
idx = idx + 1
end
end
end
---
-- Places an item in the map. Just like [item], but without the implicit [redraw].
-- NOTE: items placed this way are lost upon loading from a saved game.
--
-- [item_fast]
-- ... SLF ...
-- image=foo/bar.png
-- halo=baz/bat.png
-- [/item_fast]
---
function wesnoth.wml_actions.item_fast(cfg)
local locs = wesnoth.map.find(cfg)
cfg = wml.parsed(cfg)
if not cfg.image and not cfg.halo then
--Nothing to do
return
end
for i, loc in ipairs(locs) do
-- FIXME: these items aren't going to be removed, so I'm
-- not bothering with state tracking right now.
wesnoth.interface.add_hex_overlay(loc.x, loc.y, cfg)
end
end
---
-- Removes the terrain overlay from every hex matching a given SLF.
--
-- [remove_terrain_overlays]
-- ... SLF ...
-- [/remove_terrain_overlays]
---
function wesnoth.wml_actions.remove_terrain_overlays(cfg)
local locs = wesnoth.map.find(cfg)
for i, loc in ipairs(locs) do
local locstr = wesnoth.current.map[{loc.x, loc.y}]
local newstr = string.gsub(locstr, "%^.*$", "")
wesnoth.current.map[{loc.x, loc.y}] = newstr
end
end
---
-- Matches a standard location filter and stores the resultant coordinates
-- list in a container with two attributes that are comma-separated lists, .x and .y.
--
-- [simplify_location_filter]
-- ... SLF ...
-- variable="location"
-- [/simplify_location_filter]
---
function wesnoth.wml_actions.simplify_location_filter(cfg)
local var = cfg.variable or "location"
local locs = wesnoth.map.find(cfg)
local xstr, ystr = "", ""
wml.variables[var] = nil
for i, loc in ipairs(locs) do
if i > 1 then
xstr = ("%s,%d"):format(xstr, loc.x)
ystr = ("%s,%d"):format(ystr, loc.y)
else
xstr = ("%d"):format(loc.x)
ystr = ("%d"):format(loc.y)
end
end
wml.variables[var .. ".x"] = xstr
wml.variables[var .. ".y"] = ystr
end
---
-- Simpler alternative to the harm_unit action with less options, which
-- uses animate_unit.animate for concurrent animations. It can only work with
-- single attackers and defenders.
---
function wesnoth.wml_actions.animate_attack(cfg)
local attacker_filter = wml.get_child(cfg, "filter_second") or wml.error("[animate_attack] missing required [filter_second] tag")
local defender_filter = wml.get_child(cfg, "filter") or wml.error("[animate_attack] missing required [filter] tag")
local weapon_filter = wml.get_child(cfg, "filter_attack") or wml.error("[animate_attack] missing required [filter_attack] tag")
-- we need to use shallow_literal field, to avoid raising an error if $this_unit (not yet assigned) is used
if not cfg.__shallow_literal.amount then wml.error("[animate_attack] has missing required amount= attribute") end
local attacker = wesnoth.units.find_on_map(attacker_filter)[1]
if not attacker then
wml.error("[animate_attack]: Could not match any attackers")
end
local defender = wesnoth.units.find_on_map(defender_filter)[1]
if not defender then
wml.error("[animate_attack]: Could not match any defenders")
end
local _ = wesnoth.textdomain "wesnoth"
-- #textdomain wesnoth
local this_unit <close> = utils.scoped_var("this_unit")
wml.variables.this_unit = nil -- clearing this_unit
wml.variables.this_unit = defender.__cfg -- cfg field needed
local animate = cfg.animate
local fire_event = cfg.fire_event
local amount = tonumber(cfg.amount)
if cfg.amount == "miss" then
amount = -1
end
local kill = cfg.kill
-- NOTE: excluded from this implementation
-- local experience = cfg.experience
-- NOTE: taken from data/lua/wml-tags.lua:
-- "the two functions below are taken straight from the C++ engine, util.cpp and actions.cpp, with a few unuseful parts removed
-- may be moved in helper.lua in 1.11"
local function round_damage( base_damage, bonus, divisor )
local rounding
if base_damage == 0 then return 0
else
if bonus < divisor or divisor == 1 then
rounding = divisor / 2 - 0
else
rounding = divisor / 2 - 1
end
return math.max( 1, math.floor( ( base_damage * bonus + rounding ) / divisor ) )
end
end
local function calculate_damage( base_damage, alignment, tod_bonus, resistance )
local damage_multiplier = 100
if alignment == "lawful" then
damage_multiplier = damage_multiplier + tod_bonus
elseif alignment == "chaotic" then
damage_multiplier = damage_multiplier - tod_bonus
elseif alignment == "liminal" then
damage_multiplier = damage_multiplier - math.abs( tod_bonus )
else -- neutral, do nothing
end
damage_multiplier = damage_multiplier * resistance -- at this point, a resistance_modifier can be added, as asked by fendrin
local damage = round_damage( base_damage, damage_multiplier, 10000 ) -- if harmer.status.slowed, this may be 20000 ?
return damage
end
-- Calculate damage first to determine if the defender dies or not
local damage = 0
if amount > 0 then
damage = calculate_damage(
amount, (cfg.alignment or "neutral"),
wesnoth.schedule.get_illumination({ defender.x, defender.y }).lawful_bonus,
100 - defender:resistance_against(cfg.damage_type or "dummy")
)
end
local hit_animation_type = true
if amount < 0 then
hit_animation_type = "miss"
elseif defender.hitpoints <= damage then
if kill == false then
damage = defender.hitpoints - 1
else
damage = defender.hitpoints
hit_animation_type = "kill"
end
end
local text = ("%d%s"):format(damage, "\n")
local add_tab = false
local gender = defender.__cfg.gender
-- NOTE: also from data/lua/wml-tags.lua
local function set_status(name, male_string, female_string, sound)
if not cfg[name] or defender.status[name] then return end
if gender == "female" then
text = ("%s%s%s"):format(text, tostring(female_string), "\n")
else
text = ("%s%s%s"):format(text, tostring(male_string), "\n")
end
defender.status[name] = true
add_tab = true
if animate and sound then -- for unhealable, that has no sound
wesnoth.audio.play(sound)
end
end
if not defender.status.not_living then
set_status("poisoned", _"poisoned", _"female^poisoned", "poison.ogg")
end
set_status("slowed", _"slowed", _"female^slowed", "slowed.wav")
set_status("petrified", _"petrified", _"female^petrified", "petrified.ogg")
set_status("unhealable", _"unhealable", _"female^unhealable")
if add_tab then
text = ("%s%s"):format("\t", text)
end
-- HACK: do not display floating label when
-- the inflicted damage is zero or one
if damage <= 1 then
text = ""
end
if animate then
wesnoth.interface.scroll_to_hex(attacker.x, attacker.y, true)
wesnoth.wml_actions.animate_unit( {
flag = "attack",
hits = hit_animation_type,
with_bars = true,
{ "filter", { id = attacker.id } },
{ "primary_attack", weapon_filter },
{ "facing", { x = defender.x, y = defender.y } },
{ "animate", {
flag = "defend",
{ "filter", { id = defender.id } },
{ "primary_attack", weapon_filter },
text = text,
red = 255,
with_bars = true,
hits = hit_animation_type,
} }
} )
else
wesnoth.interface.float_label( defender.x, defender.y, ("<span foreground='red'>%s</span>"):format(text) )
end
defender.hitpoints = defender.hitpoints - damage
if kill ~= false and defender.hitpoints <= 0 then
wesnoth.wml_actions.kill({ id = defender.id, animate = animate, fire_event = fire_event })
end
wesnoth.wml_actions.redraw {}
end
---
-- Creates a unit that's initially hidden from view as if [hide_unit]
-- was used on it.
--
-- This is necessary since [unit] followed by [hide_unit] allows the unit
-- to be displayed for an instant.
--
-- The syntax is identical to [unit].
---
function wesnoth.wml_actions.hidden_unit(cfg)
local u = wesnoth.units.create(cfg)
-- Don't clobber existing units. We don't check for passability
-- because we occasionally use this with units that have infinite
-- movement costs on all terrains, and there's no need to make
-- this smarter than [unit].
u.x, u.y = wesnoth.paths.find_vacant_hex(u.x, u.y)
u.hidden = true
u:to_map()
end
---
-- Counts the amount of matching units.
--
-- [count_units]
-- ... SUF ...
-- variable=unit_count
-- [/count_units]
---
function wesnoth.wml_actions.count_units(cfg)
local units = wesnoth.units.find_on_map(cfg)
local varname = cfg.variable or "unit_count"
if units == nil then
wml.variables[varname] = 0
else
wml.variables[varname] = #units
end
end
---
-- Retrieves the given unit's portrait file path, or the
-- fallback image with TC applied on it if there's no portrait
-- defined.
--
-- [store_unit_portrait]
-- ... SUF ...
-- variable=unit_portrait
-- [/store_unit_portrait]
---
function wesnoth.wml_actions.store_unit_portrait(cfg)
local u = wesnoth.units.find_on_map(cfg)[1] or wml.error("[store_unit_portrait]: Could not match any units")
local varname = cfg.variable or "unit_portrait"
local img = u.__cfg.profile
if (not img) or img ~= "unit_image" then
local mods = u.image_mods
if mods then
img = ("%s~%s~TC(%d,%s)"):format(u.__cfg.image, mods, u.side, u.__cfg.flag_rgb)
else
img = ("%s~TC(%d,%s)"):format(u.__cfg.image, u.side, u.__cfg.flag_rgb)
end
end
wml.variables[varname] = img
end
---
-- Sets a variable to the specified value only if it hasn't been set yet
-- (doesn't exist in wml.variables).
---
function wesnoth.wml_actions.variable_default(cfg)
local name = cfg.name
if name == nil then
wml.error("[variable_default]: No variable name= specified")
end
local value = cfg.value
if value == nil then
wml.error("[variable_default]: No variable value= specified")
end
if wml.variables[name] == nil then
wml.variables[name] = value
end
end
---
-- Sets the given variable to a boolean value depending
-- on whether the given conditional statements pass or not.
--
-- [set_conditional_variable]
-- name= (required string: variable name)
-- [condition]
-- ...
-- [/condition]
-- [/set_conditional_variable]
---
function wesnoth.wml_actions.set_conditional_variable(cfg)
local varname = cfg.name
local condition = wml.get_child(cfg, "condition")
if varname == nil then
wml.error("[set_conditional_variable]: Required 'name' attribute missing")
end
if condition == nil then
wml.error("[set_conditional_variable]: Required '[condition]' tag missing")
end
wml.variables[varname] = wml.eval_conditional(condition)
end
---
-- Fades out the currently playing music and replaces
-- it with silence afterwards.
--
-- It is not possible at this time to know whether music is enabled in
-- the first place, so the fade out delay will always occur regardless
-- of the user's preferences.
--
-- [fade_out_music]
-- duration= (optional int, defaults to 1000 ms)
-- [/fade_out_music]
---
function wesnoth.wml_actions.fade_out_music(cfg)
local duration = cfg.duration
if duration == nil then
duration = 1000
end
-- HACK: reserve last 10 milliseconds for the music switch workaround.
duration = duration - 10
local delay_granularity = 10
duration = math.max(delay_granularity, duration)
local rem = duration % delay_granularity
if rem ~= 0 then
duration = duration - rem
end
local steps = duration / delay_granularity
--wesnoth.message(("%d steps"):format(steps))
for k = 1, steps do
local v = mathx.round(100 - (100*k / steps))
--wesnoth.message(("step %d, volume %d"):format(k, v))
wesnoth.audio.music_list.volume = v
wesnoth.interface.delay(delay_granularity)
end
wesnoth.audio.music_list.clear()
-- Unset existing ms_after to work around Wesnoth issues #4458, #4459, and #4460.
-- This is also done by Naia's version of [music]
if wesnoth.audio.music_list.current then
wesnoth.audio.music_list.current.ms_after = 0
end
wesnoth.audio.music_list.add("silence.ogg", true)
-- HACK: give the new track a chance to start playing silently before
-- resetting to full volume.
wesnoth.interface.delay(10)
wesnoth.audio.music_list.volume = 100.0
end
local function wml_sfx_volume_fade_internal(duration, is_fade_out)
if duration == nil then
duration = 1000
end
local delay_granularity = 10
duration = math.max(delay_granularity, duration)
duration = duration - (duration % delay_granularity)
local steps = duration / delay_granularity
--wesnoth.message(("%d steps"):format(steps))
for k = 1, steps do
local v = 0
if is_fade_out then
v = mathx.round(100 - (100*k / steps))
else
v = mathx.round(100*k / steps)
end
--wesnoth.message(("step %d, volume %d"):format(k, v))
wesnoth.sound_volume(v)
wesnoth.interface.delay(delay_granularity)
end
end
---
-- Simulates fading out all playing sound effects for the given interval of
-- time by gradually decreasing the main sound volume until it reaches zero.
--
-- [fade_out_sound]
-- duration= (optional int, defaults to 1000 ms)
-- [/fade_out_sound]
---
function wesnoth.wml_actions.fade_out_sound_effects(cfg)
wml_sfx_volume_fade_internal(cfg.duration, true)
end
---
-- Simulates fading in all playing sound effects for the given interval of
-- time by gradually increasing the main sound volume until it reaches 100%.
--
-- [fade_in_sound]
-- duration= (optional int, defaults to 1000 ms)
-- [/fade_in_sound]
---
function wesnoth.wml_actions.fade_in_sound_effects(cfg)
wml_sfx_volume_fade_internal(cfg.duration, false)
end
---
-- Sets the sound volume to zero.
---
function wesnoth.wml_actions.mute_sound_effects(cfg)
wesnoth.sound_volume(0.0)
end
---
-- Resets the main sound volume back to normal.
---
function wesnoth.wml_actions.reset_sound_effects(cfg)
wesnoth.sound_volume(100.0)
end
---
-- Checks whether the second unit ([filter_second]) is within the movement
-- range of the first unit ([filter]), and stores the truth value as a
-- boolean in the given variable (or in_range if not specified).
--
-- [check_unit_in_range]
-- [filter]
-- (primary unit SUF)
-- [/filter]
-- [filter_second]
-- (secondary unit SUF)
-- [/filter_second]
-- variable=(optional string, default to "in_range")
-- [/check_unit_in_range]
---
function wesnoth.wml_actions.check_unit_in_range(cfg)
local primary_suf = wml.get_child(cfg, "filter") or
wml.error "[check_unit_in_range] missing required [filter] tag"
local second_suf = wml.get_child(cfg, "filter_second") or
wml.error "[check_unit_in_range] missing required [filter_second] tag"
local variable = cfg.variable
if variable == nil then
variable = "in_range"
end
local u1 = wesnoth.units.find_on_map(primary_suf)[1] or
wml.error "[check_unit_in_range] could not match primary unit"
local u2 = wesnoth.units.find_on_map(second_suf)[1] or
wml.error "[check_unit_in_range] could not match secondary unit"
if wesnoth.map.distance_between(u1.x, u1.y, u2.x, u2.y) <= u1.max_moves then
wml.variables[variable] = true
else
wml.variables[variable] = false
end
end
---
-- Applies a given list of AMLAs to a unit matching the given SUF.
--
-- [apply_amlas]
-- ... SUF ...
-- amla_ids= ... comma-separated list of AMLA ids ...
-- [advancement]
-- ... contents of the [advancement] tag ...
-- [/advancement]
-- [advancement]
-- ... another AMLA ...
-- [/advancement]
-- ...
-- [/apply_amlas]
---
function wesnoth.wml_actions.apply_amlas(cfg)
local u = wesnoth.units.find(cfg)[1] or wml.error("[apply_amlas]: Could not match any units!")
-- Apply unit type AMLAs identified by id
if cfg.amla_ids and cfg.amla_ids ~= "" then
local ut = wesnoth.unit_types[u.type]
if u.variation and u.variation ~= "" then
ut = ut.variations[u.variation]
end
ut = ut.variations[u.gender]
local amla_ids = stringx.split(cfg.amla_ids)
for _, amla_id in ipairs(amla_ids) do
local amla_cfg = wml.find_child(ut.__cfg, "advancement", { id = amla_id })
if amla_cfg then
u:add_modification("advancement", amla_cfg)
else
wprintf(W_ERR, "[apply_amlas] unknown amla id '%s' for unit type '%s'", amla_id, u.type)
end
end
end
-- Apply wholesale AMLAs
for amla_cfg in wml.child_range(cfg, "advancement") do
u:add_modification("advancement", amla_cfg)
end
end
---
-- Applies a given list of [object]s to a unit matching the given SUF.
--
-- [apply_objects]
-- ... SUF ...
-- exclude_internal=yes
-- exclude_temporaries=yes
-- [object]
-- ...
-- [/object]
-- [object]
-- ...
-- [/object]
-- ...
-- [/apply_objects]
function wesnoth.wml_actions.apply_objects(cfg)
local u = wesnoth.units.find_on_map(cfg)[1] or wml.error("[apply_objects]: Could not match any units!")
local no_internals = cfg.exclude_internal
local no_temporaries = cfg.exclude_temporaries
if no_internals == nil then
no_internals = true
end
if no_temporaries == nil then
no_temporaries = true
end
for object_cfg in wml.child_range(cfg, "object") do
if no_temporaries and object_cfg.duration ~= "forever" and object_cfg.duration ~= nil then
wprintf(W_DBG, "Skipping object with duration=%s", object_cfg.duration)
elseif no_internals and object_cfg.description == nil then
wprintf(W_DBG, "Skipping description-less object")
else
u:add_modification("object", object_cfg)
end
end
end
---
-- Highlights a given set of target locations at once as a hint for the
-- player.
--
-- [highlight_goal]
-- ... SLF ...
-- image=(optional path to the goal overlay)
-- [/highlight_goal]
---
function wesnoth.wml_actions.highlight_goal(cfg)
cfg = wml.literal(cfg)
if cfg.image == nil then
cfg.image = "misc/goal-highlight.png"
end
for i = 1, 3 do
wesnoth.wml_actions.item(cfg)
wesnoth.interface.delay(300)
wesnoth.wml_actions.remove_item(cfg)
wesnoth.wml_actions.redraw {}
wesnoth.interface.delay(300)
end
end
---
-- Scatters random images from a list over a number of locations matched by the
-- specified SLF.
--
-- [scatter_images]
-- ... SLF ...