diff --git a/data/campaigns/Son_Of_The_Black_Eye/ai/ca_transport.lua b/data/campaigns/Son_Of_The_Black_Eye/ai/ca_transport.lua new file mode 100644 index 00000000000..3f3e86292a6 --- /dev/null +++ b/data/campaigns/Son_Of_The_Black_Eye/ai/ca_transport.lua @@ -0,0 +1,179 @@ +local H = wesnoth.require "lua/helper.lua" +local LS = wesnoth.require "lua/location_set.lua" + +local ca_transport = {} + +-- Move transport ships according to these rules: +-- 1. If transport can get to its designated landing site this move, find +-- close hex with the most unoccupied adjacent non-water hexes and move there +-- 2. If landing site is out of reach, move toward destination while +-- staying in deep water surrounded by deep water only + +function ca_transport:evaluation(ai) + local units = wesnoth.get_units { + side = wesnoth.current.side, + formula = '$this_unit.moves > 0', + } + + for i,u in ipairs(units) do + local vars = H.get_child(u.__cfg, "variables") + if vars.destination_x and vars.destination_y then + return 300000 + end + end + + return 0 +end + +function ca_transport:execution(ai) + local units = wesnoth.get_units {} + + -- Need all transport units plus maps of all units, all transport units and + -- all other units (as those block hexes accessible to transport units) + local transports = {} + local unit_map, transport_map, blocked_hex_map = LS.create(), LS.create(), LS.create() + + for i,u in ipairs(units) do + unit_map:insert(u.x, u.y) + + if (u.side == wesnoth.current.side) and (u.moves > 0) + and u.variables.destination_x and u.variables.destination_x + then + transport_map:insert(u.x, u.y) + table.insert(transports, u) + --print("----> Inserting " .. u.id, u.x, u.y, u.variables.destination_x, u.variables.destination_y) + else + blocked_hex_map:insert(u.x, u.y) + end + end + + -- First see if a transport is within landing distance + local landing_site_map = LS.of_pairs( + wesnoth.get_locations { + terrain = 'W*', + { "filter_adjacent_location", { terrain = '!, W*' } } + } + ) + + local max_rating, best_unit, best_hex, best_adj_tiles = -9e99 + for i,u in ipairs(transports) do + local dst = { u.variables.destination_x, u.variables.destination_y } + + if (not u.variables.landed) and (H.distance_between(u.x, u.y, dst[1], dst[2]) <= u.moves) then + local reach = wesnoth.find_reach(u) + + for i,r in ipairs(reach) do + if landing_site_map:get(r[1], r[2]) and (not unit_map:get(r[1], r[2])) + then + -- Distance from destination is minor rating + local rating = -H.distance_between(r[1], r[2], dst[1], dst[2]) / 100. + + -- Main rating is number of unoccupied land hexes and + -- water hexes next to land hexes + -- But shouldn't be too far away (arb. set to 5 hexes here) + -- This is mostly to avoid it being across the bay in S6 + local adj_tiles = {} + if (rating >= -0.05) then + for x,y in H.adjacent_tiles(r[1], r[2]) do + if (not unit_map:get(x, y)) then + if wesnoth.match_location(x, y, { terrain = "!, W*" }) then + rating = rating + 1 + table.insert(adj_tiles, { x, y, 1. } ) + end + + if wesnoth.match_location(x, y, + { + terrain = "W*", + { "filter_adjacent_location", { terrain = "!, W*" } } + } + ) + then + rating = rating + 0.1 + table.insert(adj_tiles, { x, y, 0.1 } ) + end + end + end + end + + if (rating > max_rating) then + max_rating = rating + best_unit = u + best_hex = r + best_adj_tiles = adj_tiles + end + end + end + end + end + + if (max_rating > -9e99) then + -- Also find the best unloading hexes + -- TODO: this is currently not used as the AI functionality to make + -- it replay-safe does not exist; to be added later + --table.sort(best_adj_tiles, function(a, b) return a[3] > b[3] end) + --for i,tile in ipairs(best_adj_tiles) do + -- best_unit.variables['hex' .. i .. '_x'] = tile[1] + -- best_unit.variables['hex' .. i .. '_y'] = tile[2] + --end + -- For remaining hexes, simply use the center hex and let the engine decide itself + -- This also provides a safeguard against too many hexes being occupied + --for i = #best_adj_tiles + 1, 6 do + -- best_unit.variables['hex' .. i .. '_x'] = best_hex[1] + -- best_unit.variables['hex' .. i .. '_y'] = best_hex[2] + --end + + ai.move_full(best_unit, best_hex[1], best_hex[2]) + return + end + + -- If we got here, no landing site was found. Do a deep-water move instead + local deep_water_map = LS.of_pairs( + wesnoth.get_locations { + terrain = 'Wo', + { "not", { { "filter_adjacent_location", { terrain = '!, Wo' } } } } + } + ) + + local max_rating, best_unit, best_hex = -9e99, {}, {} + for i,u in ipairs(transports) do + local dst = { u.variables.destination_x, u.variables.destination_y } + local reach = wesnoth.find_reach(u) + + local max_rating_unit, best_hex_unit = -9e99, {} + for i,r in ipairs(reach) do + if deep_water_map:get(r[1], r[2]) and (not blocked_hex_map:get(r[1], r[2])) then + local rating = -H.distance_between(r[1], r[2], dst[1], dst[2]) + -- If possible, also move in a straight line + rating = rating - math.abs(r[1] - dst[1]) / 100. + rating = rating - math.abs(r[2] - dst[2]) / 100. + if (rating > max_rating_unit) then + max_rating_unit = rating + best_hex_unit = r + end + end + end + + -- We give a penalty to hexes occupied by another transport that can still move away. + -- All ratings need to be set to the same value for this to work. + if (max_rating_unit > -9e99) then + max_rating_unit = 0 + if transport_map:get(best_hex_unit[1], best_hex_unit[2]) then + max_rating_unit = -1 + end + + if (max_rating_unit > max_rating) then + max_rating = max_rating_unit + best_unit = u + best_hex = best_hex_unit + end + end + end + + if best_unit.id then + ai.move_full(best_unit, best_hex[1], best_hex[2]) + else -- still need to make sure gamestate gets changed + ai.stopunit_moves(transports[1]) + end +end + +return ca_transport diff --git a/data/campaigns/Son_Of_The_Black_Eye/ai/transport_engine.lua b/data/campaigns/Son_Of_The_Black_Eye/ai/transport_engine.lua deleted file mode 100644 index a2104fb08cb..00000000000 --- a/data/campaigns/Son_Of_The_Black_Eye/ai/transport_engine.lua +++ /dev/null @@ -1,184 +0,0 @@ -return { - init = function(ai) - - local engine = {} - - local H = wesnoth.require "lua/helper.lua" - local LS = wesnoth.require "lua/location_set.lua" - - -- Move transport ships according to these rules: - -- 1. If transport can get to its designated landing site this move, find - -- close hex with the most unoccupied adjacent non-water hexes and move there - -- 2. If landing site is out of reach, move toward destination while - -- staying in deep water surrounded by deep water only - - function engine:transport_eval() - local units = wesnoth.get_units { - side = wesnoth.current.side, - formula = '$this_unit.moves > 0', - } - - for i,u in ipairs(units) do - local vars = H.get_child(u.__cfg, "variables") - if vars.destination_x and vars.destination_y then - return 300000 - end - end - - return 0 - end - - function engine:transport_exec(type) - local units = wesnoth.get_units {} - - -- Need all transport units plus maps of all units, all transport units and - -- all other units (as those block hexes accessible to transport units) - local transports = {} - local unit_map, transport_map, blocked_hex_map = LS.create(), LS.create(), LS.create() - - for i,u in ipairs(units) do - unit_map:insert(u.x, u.y) - - if (u.side == wesnoth.current.side) and (u.moves > 0) - and u.variables.destination_x and u.variables.destination_x - then - transport_map:insert(u.x, u.y) - table.insert(transports, u) - --print("----> Inserting " .. u.id, u.x, u.y, u.variables.destination_x, u.variables.destination_y) - else - blocked_hex_map:insert(u.x, u.y) - end - end - - -- First see if a transport is within landing distance - local landing_site_map = LS.of_pairs( - wesnoth.get_locations { - terrain = 'W*', - { "filter_adjacent_location", { terrain = '!, W*' } } - } - ) - - local max_rating, best_unit, best_hex, best_adj_tiles = -9e99 - for i,u in ipairs(transports) do - local dst = { u.variables.destination_x, u.variables.destination_y } - - if (not u.variables.landed) and (H.distance_between(u.x, u.y, dst[1], dst[2]) <= u.moves) then - local reach = wesnoth.find_reach(u) - - for i,r in ipairs(reach) do - if landing_site_map:get(r[1], r[2]) and (not unit_map:get(r[1], r[2])) - then - -- Distance from destination is minor rating - local rating = -H.distance_between(r[1], r[2], dst[1], dst[2]) / 100. - - -- Main rating is number of unoccupied land hexes and - -- water hexes next to land hexes - -- But shouldn't be too far away (arb. set to 5 hexes here) - -- This is mostly to avoid it being across the bay in S6 - local adj_tiles = {} - if (rating >= -0.05) then - for x,y in H.adjacent_tiles(r[1], r[2]) do - if (not unit_map:get(x, y)) then - if wesnoth.match_location(x, y, { terrain = "!, W*" }) then - rating = rating + 1 - table.insert(adj_tiles, { x, y, 1. } ) - end - - if wesnoth.match_location(x, y, - { - terrain = "W*", - { "filter_adjacent_location", { terrain = "!, W*" } } - } - ) - then - rating = rating + 0.1 - table.insert(adj_tiles, { x, y, 0.1 } ) - end - end - end - end - - if (rating > max_rating) then - max_rating = rating - best_unit = u - best_hex = r - best_adj_tiles = adj_tiles - end - end - end - end - end - - if (max_rating > -9e99) then - -- Also find the best unloading hexes - -- TODO: this is currently not used as the AI functionality to make - -- it replay-safe does not exist; to be added later - --table.sort(best_adj_tiles, function(a, b) return a[3] > b[3] end) - --for i,tile in ipairs(best_adj_tiles) do - -- best_unit.variables['hex' .. i .. '_x'] = tile[1] - -- best_unit.variables['hex' .. i .. '_y'] = tile[2] - --end - -- For remaining hexes, simply use the center hex and let the engine decide itself - -- This also provides a safeguard against too many hexes being occupied - --for i = #best_adj_tiles + 1, 6 do - -- best_unit.variables['hex' .. i .. '_x'] = best_hex[1] - -- best_unit.variables['hex' .. i .. '_y'] = best_hex[2] - --end - - ai.move_full(best_unit, best_hex[1], best_hex[2]) - return - end - - -- If we got here, no landing site was found. Do a deep-water move instead - local deep_water_map = LS.of_pairs( - wesnoth.get_locations { - terrain = 'Wo', - { "not", { { "filter_adjacent_location", { terrain = '!, Wo' } } } } - } - ) - - local max_rating, best_unit, best_hex = -9e99, {}, {} - for i,u in ipairs(transports) do - local dst = { u.variables.destination_x, u.variables.destination_y } - local reach = wesnoth.find_reach(u) - - local max_rating_unit, best_hex_unit = -9e99, {} - for i,r in ipairs(reach) do - if deep_water_map:get(r[1], r[2]) and (not blocked_hex_map:get(r[1], r[2])) then - local rating = -H.distance_between(r[1], r[2], dst[1], dst[2]) - -- If possible, also move in a straight line - rating = rating - math.abs(r[1] - dst[1]) / 100. - rating = rating - math.abs(r[2] - dst[2]) / 100. - if (rating > max_rating_unit) then - max_rating_unit = rating - best_hex_unit = r - end - end - end - - -- We give a penalty to hexes occupied by another transport that can still move away. - -- All ratings need to be set to the same value for this to work. - if (max_rating_unit > -9e99) then - max_rating_unit = 0 - if transport_map:get(best_hex_unit[1], best_hex_unit[2]) then - max_rating_unit = -1 - end - - if (max_rating_unit > max_rating) then - max_rating = max_rating_unit - best_unit = u - best_hex = best_hex_unit - end - end - end - - if best_unit.id then - ai.move_full(best_unit, best_hex[1], best_hex[2]) - else -- still need to make sure gamestate gets changed - ai.stopunit_moves(transports[1]) - end - end - - return engine - end -} diff --git a/data/campaigns/Son_Of_The_Black_Eye/scenarios/06_Black_Flag.cfg b/data/campaigns/Son_Of_The_Black_Eye/scenarios/06_Black_Flag.cfg index 762e1315754..f5fcc08dd2e 100644 --- a/data/campaigns/Son_Of_The_Black_Eye/scenarios/06_Black_Flag.cfg +++ b/data/campaigns/Son_Of_The_Black_Eye/scenarios/06_Black_Flag.cfg @@ -54,23 +54,13 @@ user_team_name=_"Humans" [ai] - version=10710 - [engine] - name="lua" - code= << - local ai = ... - return wesnoth.require("campaigns/Son_Of_The_Black_Eye/ai/transport_engine.lua").init(ai) - >> - [/engine] - {RCA_STAGE} {MODIFY_AI_ADD_CANDIDATE_ACTION 3 main_loop ( [candidate_action] engine=lua name=transport id=transport max_score=300000 - evaluation="return (...):transport_eval()" - execution="(...):transport_exec()" + location="campaigns/Son_Of_The_Black_Eye/ai/ca_transport.lua" [/candidate_action] )} [/ai] diff --git a/data/core/macros/ai_micro_ais.cfg b/data/core/macros/ai_micro_ais.cfg index b77d62e42a4..2de14898e33 100644 --- a/data/core/macros/ai_micro_ais.cfg +++ b/data/core/macros/ai_micro_ais.cfg @@ -1,23 +1,5 @@ #textdomain wesnoth-ai -#define RCA_STAGE - # The standard RCA stage with its candidate actions; same for all Micro AIs - [stage] - id=main_loop - name=ai_default_rca::candidate_action_evaluation_loop - {AI_CA_GOTO} - {AI_CA_RECRUITMENT} - {AI_CA_MOVE_LEADER_TO_GOALS} - {AI_CA_MOVE_LEADER_TO_KEEP} - {AI_CA_COMBAT} - {AI_CA_HEALING} - {AI_CA_VILLAGES} - {AI_CA_RETREAT} - {AI_CA_MOVE_TO_TARGETS} - {AI_CA_LEADER_SHARES_KEEP} - [/stage] -#enddef - #define MAI_MACRO_DEPRECATION MACRO_NAME # Somewhat hacky way to get a deprecation message displayed # This macro goes into the [side] tag, so ActionWML tags cannot be used diff --git a/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj b/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj index 9f4c2ff22ef..33b5c656956 100644 --- a/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj +++ b/projectfiles/Xcode/Wesnoth.xcodeproj/project.pbxproj @@ -898,6 +898,8 @@ B5FCE9371104C2A50081F1DF /* toggle_panel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B5FCE9091104C2A50081F1DF /* toggle_panel.cpp */; }; B5FCE9391104C2A50081F1DF /* vertical_scrollbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B5FCE90D1104C2A50081F1DF /* vertical_scrollbar.cpp */; }; B5FCE93A1104C2A50081F1DF /* window.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B5FCE90F1104C2A50081F1DF /* window.cpp */; }; + EC44E382181DDAF90037A62F /* menu_item.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC44E37E181DDAF90037A62F /* menu_item.cpp */; }; + EC44E383181DDAF90037A62F /* wmi_container.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC44E380181DDAF90037A62F /* wmi_container.cpp */; }; EC48D74D17F3148300DE80BF /* recruitment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC48D74B17F3148300DE80BF /* recruitment.cpp */; }; EC48D75817F318DA00DE80BF /* flg_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC48D74E17F318DA00DE80BF /* flg_manager.cpp */; }; EC48D75917F318DA00DE80BF /* mp_game_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC48D75017F318DA00DE80BF /* mp_game_utils.cpp */; }; @@ -2011,6 +2013,10 @@ B5FCE90E1104C2A50081F1DF /* vertical_scrollbar.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = vertical_scrollbar.hpp; sourceTree = ""; }; B5FCE90F1104C2A50081F1DF /* window.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = window.cpp; sourceTree = ""; }; B5FCE9101104C2A50081F1DF /* window.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = window.hpp; sourceTree = ""; }; + EC44E37E181DDAF90037A62F /* menu_item.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = menu_item.cpp; path = game_events/menu_item.cpp; sourceTree = ""; }; + EC44E37F181DDAF90037A62F /* menu_item.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = menu_item.hpp; path = game_events/menu_item.hpp; sourceTree = ""; }; + EC44E380181DDAF90037A62F /* wmi_container.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = wmi_container.cpp; path = game_events/wmi_container.cpp; sourceTree = ""; }; + EC44E381181DDAF90037A62F /* wmi_container.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = wmi_container.hpp; path = game_events/wmi_container.hpp; sourceTree = ""; }; EC48D74B17F3148300DE80BF /* recruitment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = recruitment.cpp; path = recruitment/recruitment.cpp; sourceTree = ""; }; EC48D74C17F3148300DE80BF /* recruitment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = recruitment.hpp; path = recruitment/recruitment.hpp; sourceTree = ""; }; EC48D74E17F318DA00DE80BF /* flg_manager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = flg_manager.cpp; sourceTree = ""; }; @@ -2273,6 +2279,10 @@ children = ( EC4D40AD17FCCF14002E7C81 /* strftime.cpp */, EC4D40AE17FCCF14002E7C81 /* strftime.hpp */, + EC44E37E181DDAF90037A62F /* menu_item.cpp */, + EC44E37F181DDAF90037A62F /* menu_item.hpp */, + EC44E380181DDAF90037A62F /* wmi_container.cpp */, + EC44E381181DDAF90037A62F /* wmi_container.hpp */, ECA9E67917F478B600F5DDC4 /* desktop_util.cpp */, ECA9E67A17F478B600F5DDC4 /* desktop_util.hpp */, 62CC8E7917B9064B00C16B75 /* generators */, @@ -4149,6 +4159,7 @@ B552D92410869477002D8F86 /* distributor.cpp in Sources */, B552D92D108694BB002D8F86 /* ca_testing_move_to_targets.cpp in Sources */, B5CF7BBC10D55F5A00A8BEB5 /* timer.cpp in Sources */, + EC44E383181DDAF90037A62F /* wmi_container.cpp in Sources */, B59271F710E956D600A13911 /* repeating_button.cpp in Sources */, B59271FB10E9577800A13911 /* repeating_button.cpp in Sources */, B5FCE9111104C2A50081F1DF /* button.cpp in Sources */, @@ -4316,6 +4327,7 @@ 62D24F351519995200350848 /* palette_manager.cpp in Sources */, 62E48250154D865E001DD4FC /* pane.cpp in Sources */, 6216A91D1551CCF700E13C2D /* viewport.cpp in Sources */, + EC44E382181DDAF90037A62F /* menu_item.cpp in Sources */, F4D5AFEC1571208B0062EAFC /* placer.cpp in Sources */, F4D5AFF4157120B30062EAFC /* horizontal_list.cpp in Sources */, F4D5AFF5157120B30062EAFC /* vertical_list.cpp in Sources */,