1local helper = wesnoth.require "helper" 2local location_set = wesnoth.require "location_set" 3local utils = wesnoth.require "wml-utils" 4local wml_actions = wesnoth.wml_actions 5local T = wml.tag 6 7wesnoth.require "wml-conditionals" 8wesnoth.require "wml-flow" 9wesnoth.require "wml" 10 11--[[ 12 13Note: When adding new WML tags, unless they're very simple, it's preferred to 14add a new file in the "data/lua/wml" directory rather than implementing it in this file. 15The file will then automatically be loaded by the above require statement. 16 17Also note: The above on_load event needs to be registered before any other on_load events. 18That means before loading the WML tags via wesnoth.require "wml". 19 20]] 21 22function wml_actions.sync_variable(cfg) 23 local names = cfg.name or helper.wml_error "[sync_variable] missing required name= attribute." 24 local result = wesnoth.synchronize_choice( 25 function() 26 local res = {} 27 for name_raw in utils.split(names) do 28 local name = utils.trim(name_raw) 29 local variable_type = string.sub(name, string.len(name)) == "]" and "indexed" or ( wml.variables[name .. ".length"] > 0 and "array" or "attribute") 30 local variable_info = { name = name, type = variable_type } 31 table.insert(res, { "variable", variable_info }) 32 if variable_type == "indexed" then 33 table.insert(variable_info, { "value", wml.variables[name] } ) 34 elseif variable_type == "array" then 35 for i = 1, wml.variables[name .. ".length"] do 36 table.insert(variable_info, { "value", wml.variables[string.format("%s[%d]", name, i - 1)] } ) 37 end 38 else 39 variable_info.value = wml.variables[name] 40 end 41 end 42 return res 43 end 44 ) 45 for variable in wml.child_range(result, "variable") do 46 local name = variable.name 47 48 if variable.type == "indexed" then 49 wml.variables[name] = variable[1][2] 50 elseif variable.type == "array" then 51 for index, cfg_pair in ipairs(variable) do 52 wml.variables[string.format("%s[%d]", name, index - 1)] = cfg_pair[2] 53 end 54 else 55 wml.variables[name] = variable.value 56 end 57 end 58end 59 60function wml_actions.chat(cfg) 61 local side_list = wesnoth.get_sides(cfg) 62 local speaker = tostring(cfg.speaker or "WML") 63 local message = tostring(cfg.message or 64 helper.wml_error "[chat] missing required message= attribute." 65 ) 66 67 for index, side in ipairs(side_list) do 68 if side.controller == "human" and side.is_local then 69 wesnoth.message(speaker, message) 70 break 71 end 72 end 73 74 local observable = cfg.observable ~= false 75 76 if observable then 77 local all_sides = wesnoth.get_sides() 78 local has_human_side = false 79 for index, side in ipairs(all_sides) do 80 if side.controller == "human" and side.is_local then 81 has_human_side = true 82 break 83 end 84 end 85 86 if not has_human_side then 87 wesnoth.message(speaker, message) 88 end 89 end 90end 91 92function wml_actions.gold(cfg) 93 local amount = tonumber(cfg.amount) or 94 helper.wml_error "[gold] missing required amount= attribute." 95 local sides = wesnoth.get_sides(cfg) 96 for index, team in ipairs(sides) do 97 team.gold = team.gold + amount 98 end 99end 100 101--note: This tag can't easily (without deprecation) be extended to store an array, 102--since the gold is stored in a scalar variable, not a container (there's no key). 103function wml_actions.store_gold(cfg) 104 local team = wesnoth.get_sides(cfg)[1] 105 if team then wml.variables[cfg.variable or "gold"] = team.gold end 106end 107 108function wml_actions.clear_variable(cfg) 109 local names = cfg.name or 110 helper.wml_error "[clear_variable] missing required name= attribute." 111 for w in utils.split(names) do 112 wml.variables[utils.trim(w)] = nil 113 end 114end 115 116function wml_actions.store_unit_type_ids(cfg) 117 local types = {} 118 for k in pairs(wesnoth.unit_types) do 119 table.insert(types, k) 120 end 121 table.sort(types) 122 types = table.concat(types, ',') 123 wml.variables[cfg.variable or "unit_type_ids"] = types 124end 125 126function wml_actions.store_unit_type(cfg) 127 local types = cfg.type or 128 helper.wml_error "[store_unit_type] missing required type= attribute." 129 local writer = utils.vwriter.init(cfg, "unit_type") 130 for w in utils.split(types) do 131 local unit_type = wesnoth.unit_types[w] or 132 helper.wml_error(string.format("Attempt to store nonexistent unit type '%s'.", w)) 133 utils.vwriter.write(writer, unit_type.__cfg) 134 end 135end 136 137function wml_actions.fire_event(cfg) 138 local u1 = wml.get_child(cfg, "primary_unit") 139 u1 = u1 and wesnoth.get_units(u1)[1] 140 local x1, y1 = 0, 0 141 if u1 then x1, y1 = u1.x, u1.y end 142 143 local u2 = wml.get_child(cfg, "secondary_unit") 144 u2 = u2 and wesnoth.get_units(u2)[1] 145 local x2, y2 = 0, 0 146 if u2 then x2, y2 = u2.x, u2.y end 147 148 local w1 = wml.get_child(cfg, "primary_attack") 149 local w2 = wml.get_child(cfg, "secondary_attack") 150 if w2 then w1 = w1 or {} end 151 152 if cfg.id and cfg.id ~= "" then wesnoth.fire_event_by_id(cfg.id, x1, y1, x2, y2, w1, w2) 153 elseif cfg.name and cfg.name ~= "" then wesnoth.fire_event(cfg.name, x1, y1, x2, y2, w1, w2) 154 end 155end 156 157function wml_actions.allow_recruit(cfg) 158 local unit_types = cfg.type or helper.wml_error("[allow_recruit] missing required type= attribute") 159 for index, team in ipairs(wesnoth.get_sides(cfg)) do 160 local v = team.recruit 161 for type in utils.split(unit_types) do 162 table.insert(v, type) 163 wesnoth.add_known_unit(type) 164 end 165 team.recruit = v 166 end 167end 168 169function wml_actions.allow_extra_recruit(cfg) 170 local recruits = cfg.extra_recruit or helper.wml_error("[allow_extra_recruit] missing required extra_recruit= attribute") 171 for index, unit in ipairs(wesnoth.get_units(cfg)) do 172 local v = unit.extra_recruit 173 for recruit in utils.split(recruits) do 174 table.insert(v, recruit) 175 wesnoth.add_known_unit(recruit) 176 end 177 unit.extra_recruit = v 178 end 179end 180 181function wml_actions.disallow_recruit(cfg) 182 local unit_types = cfg.type 183 for index, team in ipairs(wesnoth.get_sides(cfg)) do 184 if unit_types then 185 local v = team.recruit 186 for w in utils.split(unit_types) do 187 for i, r in ipairs(v) do 188 if r == w then 189 table.remove(v, i) 190 break 191 end 192 end 193 end 194 team.recruit = v 195 else 196 team.recruit = nil 197 end 198 end 199end 200 201function wml_actions.disallow_extra_recruit(cfg) 202 local recruits = cfg.extra_recruit or helper.wml_error("[disallow_extra_recruit] missing required extra_recruit= attribute") 203 for index, unit in ipairs(wesnoth.get_units(cfg)) do 204 local v = unit.extra_recruit 205 for w in utils.split(recruits) do 206 for i, r in ipairs(v) do 207 if r == w then 208 table.remove(v, i) 209 break 210 end 211 end 212 end 213 unit.extra_recruit = v 214 end 215end 216 217function wml_actions.set_recruit(cfg) 218 local recruit = cfg.recruit or helper.wml_error("[set_recruit] missing required recruit= attribute") 219 for index, team in ipairs(wesnoth.get_sides(cfg)) do 220 local v = {} 221 for w in utils.split(recruit) do 222 table.insert(v, w) 223 end 224 team.recruit = v 225 end 226end 227 228function wml_actions.set_extra_recruit(cfg) 229 local recruits = cfg.extra_recruit or helper.wml_error("[set_extra_recruit] missing required extra_recruit= attribute") 230 local v = {} 231 232 for w in utils.split(recruits) do 233 table.insert(v, w) 234 end 235 236 for index, unit in ipairs(wesnoth.get_units(cfg)) do 237 unit.extra_recruit = v 238 end 239end 240 241function wml_actions.store_map_dimensions(cfg) 242 local var = cfg.variable or "map_size" 243 local w, h, b = wesnoth.get_map_size() 244 wml.variables[var .. ".width"] = w 245 wml.variables[var .. ".height"] = h 246 wml.variables[var .. ".border_size"] = b 247end 248 249function wml_actions.unit_worth(cfg) 250 local u = wesnoth.get_units(cfg)[1] or 251 helper.wml_error "[unit_worth]'s filter didn't match any unit" 252 local ut = wesnoth.unit_types[u.type] 253 local hp = u.hitpoints / u.max_hitpoints 254 local xp = u.experience / u.max_experience 255 local best_adv = ut.cost 256 for w in utils.split(ut.__cfg.advances_to) do 257 local uta = wesnoth.unit_types[w] 258 if uta and uta.cost > best_adv then best_adv = uta.cost end 259 end 260 wml.variables["cost"] = ut.cost 261 wml.variables["next_cost"] = best_adv 262 wml.variables["health"] = math.floor(hp * 100) 263 wml.variables["experience"] = math.floor(xp * 100) 264 wml.variables["recall_cost"] = ut.recall_cost 265 wml.variables["unit_worth"] = math.floor(math.max(ut.cost * hp, best_adv * xp)) 266end 267 268function wml_actions.lua(cfg) 269 cfg = wml.shallow_literal(cfg) 270 local bytecode, message = load(cfg.code or "") 271 if not bytecode then error("~lua:" .. message, 0) end 272 bytecode(wml.get_child(cfg, "args")) 273end 274 275function wml_actions.music(cfg) 276 if cfg.play_once then 277 wesnoth.music_list.play(cfg.name) 278 else 279 if not cfg.append then 280 if cfg.immediate and wesnoth.music_list.current_i then 281 wesnoth.music_list.current.once = true 282 end 283 wesnoth.music_list.clear() 284 end 285 local m = #wesnoth.music_list 286 wesnoth.music_list.add(cfg.name, not not cfg.immediate, cfg.ms_before or 0, cfg.ms_after or 0) 287 local n = #wesnoth.music_list 288 if n == 0 then 289 return 290 end 291 if cfg.shuffle == false then 292 wesnoth.music_list[n].shuffle = false 293 end 294 -- Always overwrite shuffle even if the new track couldn't be added, 295 -- but title shouldn't be overwritten. 296 if cfg.title ~= nil and m ~= n then 297 wesnoth.music_list[n].title = cfg.title 298 end 299 end 300end 301 302function wml_actions.volume(cfg) 303 if cfg.music then 304 local rel = tonumber(cfg.music) or 100.0 305 wesnoth.music_list.volume = rel 306 end 307 if cfg.sound then 308 local rel = tonumber(cfg.sound) or 100.0 309 wesnoth.sound_volume(rel) 310 end 311end 312 313function wml_actions.scroll_to(cfg) 314 local loc = wesnoth.get_locations( cfg )[1] 315 if not loc then return end 316 if not utils.optional_side_filter(cfg) then return end 317 wesnoth.scroll_to_tile(loc[1], loc[2], cfg.check_fogged, cfg.immediate) 318 if cfg.highlight then 319 wesnoth.highlight_hex(loc[1], loc[2]) 320 wml_actions.redraw{} 321 end 322end 323 324function wml_actions.scroll_to_unit(cfg) 325 local u = wesnoth.get_units(cfg)[1] 326 if not u then return end 327 if not utils.optional_side_filter(cfg, "for_side", "for_side") then return end 328 wesnoth.scroll_to_tile(u.x, u.y, cfg.check_fogged, cfg.immediate) 329 if cfg.highlight then 330 wesnoth.highlight_hex(u.x, u.y) 331 wml_actions.redraw{} 332 end 333end 334 335function wml_actions.lock_view(cfg) 336 wesnoth.lock_view(true) 337end 338 339function wml_actions.unlock_view(cfg) 340 wesnoth.lock_view(false) 341end 342 343function wml_actions.select_unit(cfg) 344 local u = wesnoth.get_units(cfg)[1] 345 if not u then return end 346 wesnoth.select_unit(u.x, u.y, cfg.highlight, cfg.fire_event) 347end 348 349function wml_actions.unit_overlay(cfg) 350 local img = cfg.image or helper.wml_error( "[unit_overlay] missing required image= attribute" ) 351 for i,u in ipairs(wesnoth.get_units(cfg)) do 352 local ucfg = u.__cfg 353 for w in utils.split(ucfg.overlays) do 354 if w == img then ucfg = nil end 355 end 356 if ucfg then 357 ucfg.overlays = ucfg.overlays .. ',' .. img 358 wesnoth.put_unit(ucfg) 359 end 360 end 361end 362 363function wml_actions.remove_unit_overlay(cfg) 364 local img = cfg.image or helper.wml_error( "[remove_unit_overlay] missing required image= attribute" ) 365 366 -- Loop through all matching units. 367 for i,u in ipairs(wesnoth.get_units(cfg)) do 368 local ucfg = u.__cfg 369 local t = utils.parenthetical_split(ucfg.overlays) 370 -- Remove the specified image from the overlays. 371 for i = #t,1,-1 do 372 if t[i] == img then table.remove(t, i) end 373 end 374 -- Reassemble the list of remaining overlays. 375 ucfg.overlays = table.concat(t, ',') 376 wesnoth.put_unit(ucfg) 377 end 378end 379 380function wml_actions.store_turns(cfg) 381 local var = cfg.variable or "turns" 382 wml.variables[var] = wesnoth.game_config.last_turn 383end 384 385function wml_actions.store_unit(cfg) 386 local filter = wml.get_child(cfg, "filter") or 387 helper.wml_error "[store_unit] missing required [filter] tag" 388 local kill_units = cfg.kill 389 390 --cache the needed units here, since the filter might reference the to-be-cleared variable(s) 391 local units = wesnoth.get_units(filter) 392 local recall_units = wesnoth.get_recall_units(filter) 393 394 local writer = utils.vwriter.init(cfg, "unit") 395 396 for i,u in ipairs(units) do 397 utils.vwriter.write(writer, u.__cfg) 398 if kill_units then u:erase() end 399 end 400 401 if (not filter.x or filter.x == "recall") and (not filter.y or filter.y == "recall") then 402 for i,u in ipairs(recall_units) do 403 local ucfg = u.__cfg 404 ucfg.x = "recall" 405 ucfg.y = "recall" 406 utils.vwriter.write(writer, ucfg) 407 if kill_units then u:erase() end 408 end 409 end 410end 411 412function wml_actions.sound(cfg) 413 local name = cfg.name or helper.wml_error("[sound] missing required name= attribute") 414 wesnoth.play_sound(name, cfg["repeat"]) 415end 416 417function wml_actions.store_locations(cfg) 418 -- the variable can be mentioned in a [find_in] subtag, so it 419 -- cannot be cleared before the locations are recovered 420 local locs = wesnoth.get_locations(cfg) 421 local writer = utils.vwriter.init(cfg, "location") 422 for i, loc in ipairs(locs) do 423 local x, y = loc[1], loc[2] 424 local t = wesnoth.get_terrain(x, y) 425 local res = { x = x, y = y, terrain = t } 426 if wesnoth.get_terrain_info(t).village then 427 res.owner_side = wesnoth.get_village_owner(x, y) or 0 428 end 429 utils.vwriter.write(writer, res) 430 end 431end 432 433function wml_actions.store_reachable_locations(cfg) 434 local unit_filter = wml.get_child(cfg, "filter") or 435 helper.wml_error "[store_reachable_locations] missing required [filter] tag" 436 local location_filter = wml.get_child(cfg, "filter_location") 437 local range = cfg.range or "movement" 438 local moves = cfg.moves or "current" 439 local variable = cfg.variable or helper.wml_error "[store_reachable_locations] missing required variable= key" 440 local reach_param = { viewing_side = cfg.viewing_side or 0 } 441 if range == "vision" then 442 moves = "max" 443 reach_param.ignore_units = true 444 end 445 446 local reach = location_set.create() 447 448 for i,unit in ipairs(wesnoth.get_units(unit_filter)) do 449 local unit_reach 450 if moves == "max" then 451 local saved_moves = unit.moves 452 unit.moves = unit.max_moves 453 unit_reach = location_set.of_pairs(wesnoth.find_reach(unit, reach_param)) 454 unit.moves = saved_moves 455 else 456 unit_reach = location_set.of_pairs(wesnoth.find_reach(unit, reach_param)) 457 end 458 459 if range == "vision" or range == "attack" then 460 unit_reach:iter(function(x, y) 461 reach:insert(x, y) 462 for u,v in helper.adjacent_tiles(x, y) do 463 reach:insert(u, v) 464 end 465 end) 466 else 467 reach:union(unit_reach) 468 end 469 end 470 471 if location_filter then 472 reach = reach:filter(function(x, y) 473 return wesnoth.match_location(x, y, location_filter) 474 end) 475 end 476 reach:to_wml_var(variable) 477end 478 479function wml_actions.hide_unit(cfg) 480 for i,u in ipairs(wesnoth.get_units(cfg)) do 481 u.hidden = true 482 end 483 wml_actions.redraw {} 484end 485 486function wml_actions.unhide_unit(cfg) 487 for i,u in ipairs(wesnoth.get_units(cfg)) do 488 u.hidden = false 489 end 490 wml_actions.redraw {} 491end 492 493function wml_actions.capture_village(cfg) 494 local side = cfg.side 495 local filter_side = wml.get_child(cfg, "filter_side") 496 local fire_event = cfg.fire_event 497 if side then side = tonumber(side) or helper.wml_error("invalid side in [capture_village]") end 498 if filter_side then 499 if side then helper.wml_error("duplicate side information in [capture_village]") end 500 side = wesnoth.get_sides(filter_side)[1] 501 if side then side = side.side end 502 end 503 local locs = wesnoth.get_locations(cfg) 504 505 for i, loc in ipairs(locs) do 506 wesnoth.set_village_owner(loc[1], loc[2], side, fire_event) 507 end 508end 509 510function wml_actions.terrain(cfg) 511 local terrain = cfg.terrain or helper.wml_error("[terrain] missing required terrain= attribute") 512 cfg = wml.shallow_parsed(cfg) 513 cfg.terrain = nil 514 for i, loc in ipairs(wesnoth.get_locations(cfg)) do 515 wesnoth.set_terrain(loc[1], loc[2], terrain, cfg.layer, cfg.replace_if_failed) 516 end 517end 518 519function wml_actions.delay(cfg) 520 local delay = tonumber(cfg.time) or 521 helper.wml_error "[delay] missing required time= attribute." 522 local accelerate = cfg.accelerate or false 523 wesnoth.delay(delay, accelerate) 524end 525 526function wml_actions.floating_text(cfg) 527 local locs = wesnoth.get_locations(cfg) 528 local text = cfg.text or helper.wml_error("[floating_text] missing required text= attribute") 529 530 for i, loc in ipairs(locs) do 531 wesnoth.float_label(loc[1], loc[2], text, cfg.color) 532 end 533end 534 535function wml_actions.petrify(cfg) 536 for index, unit in ipairs(wesnoth.get_units(cfg)) do 537 unit.status.petrified = true 538 -- Extract unit and put it back to update animation (not needed for recall units) 539 unit:extract() 540 unit:to_map() 541 end 542 543 for index, unit in ipairs(wesnoth.get_recall_units(cfg)) do 544 unit.status.petrified = true 545 end 546end 547 548function wml_actions.unpetrify(cfg) 549 for index, unit in ipairs(wesnoth.get_units(cfg)) do 550 unit.status.petrified = false 551 -- Extract unit and put it back to update animation (not needed for recall units) 552 unit:extract() 553 unit:to_map() 554 end 555 556 for index, unit in ipairs(wesnoth.get_recall_units(cfg)) do 557 unit.status.petrified = false 558 end 559end 560 561function wml_actions.transform_unit(cfg) 562 local transform_to = cfg.transform_to 563 564 for index, unit in ipairs(wesnoth.get_units(cfg)) do 565 566 if transform_to then 567 wesnoth.transform_unit( unit, transform_to ) 568 else 569 local hitpoints = unit.hitpoints 570 local experience = unit.experience 571 local recall_cost = unit.recall_cost 572 local status = wml.get_child( unit.__cfg, "status" ) 573 574 unit.experience = unit.max_experience 575 wesnoth.advance_unit(unit, false, false) 576 577 unit.hitpoints = hitpoints 578 unit.experience = experience 579 unit.recall_cost = recall_cost 580 581 for key, value in pairs(status) do unit.status[key] = value end 582 if unit.status.unpoisonable then unit.status.poisoned = nil end 583 end 584 end 585 586 wml_actions.redraw {} 587end 588 589function wml_actions.store_side(cfg) 590 local writer = utils.vwriter.init(cfg, "side") 591 for t, side_number in helper.get_sides(cfg) do 592 local container = t.__cfg 593 -- set values not properly handled by the __cfg 594 container.income = t.total_income 595 container.net_income = t.net_income 596 container.base_income = t.base_income 597 container.expenses = t.expenses 598 container.total_upkeep = t.total_upkeep 599 container.num_units = t.num_units 600 container.num_villages = t.num_villages 601 container.side = side_number 602 utils.vwriter.write(writer, container) 603 end 604end 605 606function wml_actions.add_ai_behavior(cfg) 607 local unit = wesnoth.get_units(wml.get_child(cfg, "filter"))[1] or 608 helper.wml_error("[add_ai_behavior]: no unit specified") 609 610 local side = cfg.side or 611 helper.wml_error("[add_ai_behavior]: no side attribute given") 612 613 local sticky = cfg.sticky or false 614 local loop_id = cfg.loop_id or "main_loop" 615 local eval = cfg.evaluation 616 local exec = cfg.execution 617 local id = cfg.bca_id or ("bca-" .. unit.__cfg.underlying_id) 618 619 local ux = unit.x -- @note: did I get it right that coordinates in C++ differ by 1 from thos in-game(and in Lua)? 620 local uy = unit.y 621 622 if not (eval and exec) then 623 helper.wml_error("[add_ai_behavior]: invalid execution/evaluation handler(s)") 624 end 625 626 local path = "stage[" .. loop_id .. "].candidate_action[" .. id .. "]" -- bca: behavior candidate action 627 628 wesnoth.wml_actions.modify_ai { 629 action = "add", 630 engine = "lua", 631 path = path, 632 side = side, 633 634 T.candidate_action { 635 id = id, 636 name = id, 637 engine = "lua", 638 sticky = sticky, 639 unit_x = ux, 640 unit_y = uy, 641 evaluation = eval, 642 execution = exec 643 }, 644 } 645end 646 647function wml_actions.store_starting_location(cfg) 648 local writer = utils.vwriter.init(cfg, "location") 649 for _, side in ipairs(wesnoth.get_sides(cfg)) do 650 local loc = wesnoth.get_starting_location(side.side) 651 if loc then 652 local terrain = wesnoth.get_terrain(loc[1], loc[2]) 653 local result = { x = loc[1], y = loc[2], terrain = terrain } 654 if wesnoth.get_terrain_info(terrain).village then 655 result.owner_side = wesnoth.get_village_owner(loc[1], loc[2]) or 0 656 end 657 utils.vwriter.write(writer, result) 658 end 659 end 660end 661 662function wml_actions.store_villages( cfg ) 663 local villages = wesnoth.get_villages( cfg ) 664 local writer = utils.vwriter.init(cfg, "location") 665 for index, village in ipairs( villages ) do 666 utils.vwriter.write(writer, { 667 x = village[1], 668 y = village[2], 669 terrain = wesnoth.get_terrain( village[1], village[2] ), 670 owner_side = wesnoth.get_village_owner( village[1], village[2] ) or 0 671 }) 672 end 673end 674 675function wml_actions.put_to_recall_list(cfg) 676 local units = wesnoth.get_units(cfg) 677 678 for i, unit in ipairs(units) do 679 if cfg.heal then 680 unit.hitpoints = unit.max_hitpoints 681 unit.moves = unit.max_moves 682 unit.attacks_left = unit.max_attacks 683 unit.status.poisoned = false 684 unit.status.slowed = false 685 end 686 wesnoth.put_recall_unit(unit, unit.side) 687 end 688end 689 690function wml_actions.allow_undo(cfg) 691 wesnoth.allow_undo() 692end 693 694function wml_actions.allow_end_turn(cfg) 695 wesnoth.allow_end_turn(true) 696end 697 698function wml_actions.disallow_end_turn(cfg) 699 wesnoth.allow_end_turn(false) 700end 701 702function wml_actions.clear_menu_item(cfg) 703 wesnoth.clear_menu_item(cfg.id) 704end 705 706function wml_actions.set_menu_item(cfg) 707 wesnoth.set_menu_item(cfg.id, cfg) 708end 709 710function wml_actions.place_shroud(cfg) 711 local sides = utils.get_sides(cfg) 712 local tiles = wesnoth.get_locations(cfg) 713 for i,side in ipairs(sides) do 714 wesnoth.place_shroud(side.side, tiles) 715 end 716end 717 718function wml_actions.remove_shroud(cfg) 719 local sides = utils.get_sides(cfg) 720 local tiles = wesnoth.get_locations(cfg) 721 for i,side in ipairs(sides) do 722 wesnoth.remove_shroud(side.side, tiles) 723 end 724end 725 726function wml_actions.time_area(cfg) 727 if cfg.remove then 728 wml_actions.remove_time_area(cfg) 729 else 730 wesnoth.add_time_area(cfg) 731 end 732end 733 734function wml_actions.remove_time_area(cfg) 735 local id = cfg.id or helper.wml_error("[remove_time_area] missing required id= key") 736 737 for w in utils.split(id) do 738 wesnoth.remove_time_area(w) 739 end 740end 741 742function wml_actions.replace_schedule(cfg) 743 wesnoth.replace_schedule(cfg) 744end 745 746function wml_actions.scroll(cfg) 747 local sides = utils.get_sides(cfg) 748 local have_human = false 749 for i, side in ipairs(sides) do 750 if side.controller == 'human' and side.is_local then 751 have_human = true 752 end 753 end 754 if have_human or #sides == 0 then 755 wesnoth.scroll(cfg.x or 0, cfg.y or 0) 756 end 757end 758 759function wml_actions.color_adjust(cfg) 760 wesnoth.color_adjust(cfg) 761end 762 763function wml_actions.end_turn(cfg) 764 wesnoth.end_turn() 765end 766 767function wml_actions.event(cfg) 768 if cfg.remove then 769 wml_actions.remove_event(cfg) 770 else 771 wesnoth.add_event_handler(cfg) 772 end 773end 774 775function wml_actions.remove_event(cfg) 776 local id = cfg.id or helper.wml_error("[remove_event] missing required id= key") 777 778 for w in utils.split(id) do 779 wesnoth.remove_event_handler(w) 780 end 781end 782 783function wml_actions.inspect(cfg) 784 wesnoth.gamestate_inspector(cfg) 785end 786 787function wml_actions.label( cfg ) 788 local new_cfg = wml.parsed( cfg ) 789 for index, location in ipairs( wesnoth.get_locations( cfg ) ) do 790 new_cfg.x, new_cfg.y = location[1], location[2] 791 wesnoth.label( new_cfg ) 792 end 793end 794 795function wml_actions.open_help(cfg) 796 wesnoth.open_help(cfg.topic) 797end 798 799function wml_actions.redraw(cfg) 800 local clear_shroud = cfg.clear_shroud 801 802 -- Backwards compat, the behavior of the tag was to clear shroud in case that side= is given. 803 if cfg.clear_shroud == nil and cfg.side ~= nil then 804 clear_shroud = true 805 end 806 807 wesnoth.redraw(cfg, clear_shroud) 808end 809 810function wml_actions.print(cfg) 811 wesnoth.print(cfg) 812end 813 814function wml_actions.unsynced(cfg) 815 wesnoth.unsynced(function () 816 wml_actions.command(cfg) 817 end) 818end 819 820local function on_board(x, y) 821 if type(x) ~= "number" or type(y) ~= "number" then 822 return false 823 end 824 local w, h = wesnoth.get_map_size() 825 return x >= 1 and y >= 1 and x <= w and y <= h 826end 827 828wml_actions.unstore_unit = function(cfg) 829 local variable = cfg.variable or helper.wml_error("[unstore_unit] missing required 'variable' attribute") 830 local unit_cfg = wml.variables[variable] or helper.wml_error("[unstore_unit]: variable '" .. variable .. "' doesn't exist") 831 if type(unit_cfg) ~= "table" or unit_cfg.type == nil then 832 helper.wml_error("[unstore_unit]: variable '" .. variable .. "' doesn't contain unit data") 833 end 834 local unit = wesnoth.create_unit(unit_cfg) 835 local advance = cfg.advance ~= false 836 local animate = cfg.animate ~= false 837 local check_passability = cfg.check_passability ~= false or nil 838 local x = cfg.x or unit.x or -1 839 local y = cfg.y or unit.y or -1 840 wesnoth.add_known_unit(unit.type) 841 if on_board(x, y) then 842 if cfg.find_vacant then 843 x,y = wesnoth.find_vacant_tile(x, y, check_passability and unit) 844 end 845 unit:to_map(x, y, cfg.fire_event) 846 local text 847 if unit_cfg.gender == "female" then 848 text = cfg.female_text or cfg.text 849 else 850 text = cfg.male_text or cfg.text 851 end 852 local color = cfg.color 853 if color == nil and cfg.red and cfg.blue and cfg.green then 854 color = cfg.red .. "," .. cfg.green .. "," .. cfg.blue 855 end 856 if text ~= nil and not wesnoth.is_skipping_messages() then 857 wesnoth.float_label(x, y, text, color) 858 end 859 if advance then 860 wesnoth.advance_unit(unit, animate, cfg.fire_event) 861 end 862 else 863 unit:to_recall() 864 end 865end 866 867wml_actions.teleport = function(cfg) 868 local context = wesnoth.current.event_context 869 local filter = wml.get_child(cfg, "filter") or { x = context.x1, y = context.y1 } 870 local unit = wesnoth.get_units(filter)[1] 871 if not unit then 872 -- No error if no unit matches. 873 return 874 end 875 wesnoth.teleport(unit, cfg.x, cfg.y, cfg.check_passability == false, cfg.clear_shroud ~= false, cfg.animate) 876end 877 878function wml_actions.remove_sound_source(cfg) 879 wesnoth.remove_sound_source(cfg.id) 880end 881 882function wml_actions.sound_source(cfg) 883 wesnoth.add_sound_source(cfg) 884end 885 886function wml_actions.deprecated_message(cfg) 887 if type(cfg.level) ~= "number" or cfg.level < 1 or cfg.level > 4 then 888 local _ = wesnoth.textdomain "wesnoth" 889 helper.wml_error(_"Invalid deprecation level (should be 1-4)") 890 end 891 wesnoth.deprecated_message(cfg.what, cfg.level, cfg.version, cfg.message or '') 892end 893 894function wml_actions.wml_message(cfg) 895 wesnoth.log(cfg.logger, cfg.message, cfg.to_chat) 896end 897 898local function parse_fog_cfg(cfg) 899 -- Side filter 900 local ssf = wml.get_child(cfg, "filter_side") 901 local sides = wesnoth.get_sides(ssf or {}) 902 -- Location filter 903 local locs = wesnoth.get_locations(cfg) 904 return locs, sides 905end 906 907function wml_actions.lift_fog(cfg) 908 local locs, sides = parse_fog_cfg(cfg) 909 for i = 1, #sides do 910 wesnoth.remove_fog(sides[i].side, locs, not cfg.multiturn) 911 end 912end 913 914function wml_actions.reset_fog(cfg) 915 local locs, sides = parse_fog_cfg(cfg) 916 for i = 1, #sides do 917 wesnoth.add_fog(sides[i].side, locs, cfg.reset_view) 918 end 919end 920 921function wesnoth.wml_actions.change_theme(cfg) 922 local new_theme = cfg.theme 923 924 if new_theme == nil then 925 new_theme = "" 926 end 927 928 wesnoth.game_config.theme = new_theme 929end 930 931function wesnoth.wml_actions.zoom(cfg) 932 wesnoth.zoom(cfg.factor, cfg.relative) 933end 934 935function wesnoth.wml_actions.story(cfg) 936 local title = cfg.title or helper.wml_error "Missing title key in [story] ActionWML" 937 wesnoth.show_story(cfg, title) 938end 939 940function wesnoth.wml_actions.cancel_action(cfg) 941 wesnoth.cancel_action() 942end 943 944function wesnoth.wml_actions.store_unit_defense(cfg) 945 local unit = wesnoth.get_units(cfg)[1] or helper.wml_error "[store_unit_defense]'s filter didn't match any unit" 946 local terrain = cfg.terrain 947 local defense 948 949 if terrain then 950 defense = wesnoth.unit_defense(unit, terrain) 951 elseif cfg.loc_x and cfg.loc_y then 952 defense = wesnoth.unit_defense(unit, wesnoth.get_terrain(cfg.loc_x, cfg.loc_y)) 953 else 954 defense = wesnoth.unit_defense(unit, wesnoth.get_terrain(unit.x, unit.y)) 955 end 956 wml.variables[cfg.variable or "terrain_defense"] = defense 957end 958