diff --git a/assets/cn/war_archives/TEMPLATE_VIRTUAL_TOWER.png b/assets/cn/war_archives/TEMPLATE_VIRTUAL_TOWER.png new file mode 100644 index 000000000..5edc39da9 Binary files /dev/null and b/assets/cn/war_archives/TEMPLATE_VIRTUAL_TOWER.png differ diff --git a/campaign/Readme.md b/campaign/Readme.md index d85e5337c..72ca4f42c 100644 --- a/campaign/Readme.md +++ b/campaign/Readme.md @@ -50,6 +50,7 @@ To add a new event, add a new row in here, and run `python -m module.config.conf | 20240829 | war archives 20210422 cn | Daedalian Hymn | 复兴的赞美诗 | Daedalian Hymn | 讃える復興の迷路 | 復興的讚美詩 | | 20241017 | war archives 20211229 cn | Tower of Transcendence | 逆转彩虹之塔 | Tower of Transcendence | 遡望せし虹彩の塔 | 逆轉彩虹之塔 | | 20250109 | war archives 20220224 cn | Abyssal Refrain | 深度回音 | Abyssal Refrain | 鳴動せし星霜の淵 | 深度回音 | +| 20250320 | war archives 20220324 cn | Virtual Tower | 虚像构筑之塔 | Virtual Tower | 幻像の塔 | 虛像構築之塔 | | 20200227 | event 20200227 cn | Northern Overture | 北境序曲 | Northern Overture | 凍絶の北海 | - | | 20200312 | event 20200312 cn | The Solomon Ranger | 复刻斯图尔特的硝烟 | The Solomon Ranger Rerun | 南洋に靡く硝煙(復刻) | - | | 20200326 | event 20200326 cn | Microlayer Medley | 微层混合 | Microlayer Medley | 闇靄払う銀翼 | - | diff --git a/campaign/war_archives_20220324_cn/sp1.py b/campaign/war_archives_20220324_cn/sp1.py new file mode 100644 index 000000000..c321f4bce --- /dev/null +++ b/campaign/war_archives_20220324_cn/sp1.py @@ -0,0 +1,97 @@ +from module.logger import logger +from module.map.map_base import CampaignMap +from module.map.map_grids import RoadGrids, SelectedGrids + +from ..campaign_war_archives.campaign_base import CampaignBase + +MAP = CampaignMap('SP1') +MAP.shape = 'H7' +MAP.camera_data = ['D2', 'D5', 'E2', 'E5'] +MAP.camera_data_spawn_point = ['D2'] +MAP.map_data = """ + SP SP -- -- -- -- -- -- + -- -- -- ++ ++ -- -- -- + ME -- -- MB MB -- -- ME + -- -- MS -- -- MS -- -- + Me ++ ++ __ __ ++ ++ Me + -- ME ++ ME ME ++ ME -- + ME -- Me -- -- Me -- ME +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 2, 'siren': 1}, + {'battle': 1, 'enemy': 2}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, \ +A2, B2, C2, D2, E2, F2, G2, H2, \ +A3, B3, C3, D3, E3, F3, G3, H3, \ +A4, B4, C4, D4, E4, F4, G4, H4, \ +A5, B5, C5, D5, E5, F5, G5, H5, \ +A6, B6, C6, D6, E6, F6, G6, H6, \ +A7, B7, C7, D7, E7, F7, G7, H7, \ + = MAP.flatten() + + +class Config: + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = ['DD', 'CL'] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = True + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + INTERNAL_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (150, 255 - 10), + 'width': (0.9, 10), + 'prominence': 10, + 'distance': 35, + } + EDGE_LINES_FIND_PEAKS_PARAMETERS = { + 'height': (255 - 10, 255), + 'prominence': 10, + 'distance': 50, + 'wlen': 1000 + } + HOMO_CANNY_THRESHOLD = (75, 150) + HOMO_EDGE_COLOR_RANGE = (0, 10) + MAP_SWIPE_MULTIPLY = (1.245, 1.268) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.204, 1.226) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.169, 1.190) + MAP_ENEMY_GENRE_DETECTION_SCALING = { + 'DD': 1.111, + 'CL': 1.111, + 'CA': 1.111, + 'CV': 1.111, + 'BB': 1.111, + } + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/war_archives_20220324_cn/sp2.py b/campaign/war_archives_20220324_cn/sp2.py new file mode 100644 index 000000000..40bb775bb --- /dev/null +++ b/campaign/war_archives_20220324_cn/sp2.py @@ -0,0 +1,80 @@ +from module.logger import logger +from module.map.map_base import CampaignMap +from module.map.map_grids import RoadGrids, SelectedGrids + +from ..campaign_war_archives.campaign_base import CampaignBase +from .sp1 import Config as ConfigBase + +MAP = CampaignMap('SP2') +MAP.shape = 'H8' +MAP.camera_data = ['D2', 'D5', 'E2', 'E5'] +MAP.camera_data_spawn_point = ['D2'] +MAP.map_data = """ + -- ME ++ MB MB ++ ME -- + SP -- -- __ __ -- -- ME + SP -- MS ME ME MS -- ME + -- Me -- ++ ++ -- Me -- + -- Me -- ++ ++ -- Me -- + -- -- Me -- -- Me -- -- + ++ -- -- ME ME -- -- ++ + -- -- -- ++ ++ -- -- -- +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 3, 'siren': 1}, + {'battle': 1, 'enemy': 2}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, \ +A2, B2, C2, D2, E2, F2, G2, H2, \ +A3, B3, C3, D3, E3, F3, G3, H3, \ +A4, B4, C4, D4, E4, F4, G4, H4, \ +A5, B5, C5, D5, E5, F5, G5, H5, \ +A6, B6, C6, D6, E6, F6, G6, H6, \ +A7, B7, C7, D7, E7, F7, G7, H7, \ +A8, B8, C8, D8, E8, F8, G8, H8, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = ['CL', 'CA'] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.210, 1.233) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.170, 1.192) + MAP_SWIPE_MULTIPLY_MAATOUCH = (1.136, 1.156) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_4(self): + return self.clear_boss() diff --git a/campaign/war_archives_20220324_cn/sp3.py b/campaign/war_archives_20220324_cn/sp3.py new file mode 100644 index 000000000..bf0a19c53 --- /dev/null +++ b/campaign/war_archives_20220324_cn/sp3.py @@ -0,0 +1,81 @@ +from module.logger import logger +from module.map.map_base import CampaignMap +from module.map.map_grids import RoadGrids, SelectedGrids + +from ..campaign_war_archives.campaign_base import CampaignBase +from .sp1 import Config as ConfigBase + +MAP = CampaignMap('SP3') +MAP.shape = 'H8' +MAP.camera_data = ['D2', 'D6', 'E2', 'E6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + -- -- ME -- -- -- ME -- + -- Me ++ ME -- ME -- -- + Me -- ++ Me -- ++ ++ -- + ++ -- -- __ -- -- ++ -- + SP -- MS -- Me -- ME -- + SP -- -- ++ ++ ME -- ME + -- -- MS ++ ++ -- ME ++ + ++ ME -- MB MB -- -- -- +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 3, 'siren': 2}, + {'battle': 1, 'enemy': 2}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4}, + {'battle': 5, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, \ +A2, B2, C2, D2, E2, F2, G2, H2, \ +A3, B3, C3, D3, E3, F3, G3, H3, \ +A4, B4, C4, D4, E4, F4, G4, H4, \ +A5, B5, C5, D5, E5, F5, G5, H5, \ +A6, B6, C6, D6, E6, F6, G6, H6, \ +A7, B7, C7, D7, E7, F7, G7, H7, \ +A8, B8, C8, D8, E8, F8, G8, H8, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = ['CA', 'BB'] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = True + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.037, 1.056) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.002, 1.021) + MAP_SWIPE_MULTIPLY_MAATOUCH = (0.973, 0.991) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/campaign/war_archives_20220324_cn/sp4.py b/campaign/war_archives_20220324_cn/sp4.py new file mode 100644 index 000000000..a62f66764 --- /dev/null +++ b/campaign/war_archives_20220324_cn/sp4.py @@ -0,0 +1,81 @@ +from module.logger import logger +from module.map.map_base import CampaignMap +from module.map.map_grids import RoadGrids, SelectedGrids + +from ..campaign_war_archives.campaign_base import CampaignBase +from .sp1 import Config as ConfigBase + +MAP = CampaignMap('SP4') +MAP.shape = 'H8' +MAP.camera_data = ['D3', 'D6', 'E3', 'E6'] +MAP.camera_data_spawn_point = ['D6'] +MAP.map_data = """ + ++ -- -- -- -- ++ -- -- + -- Me ++ ++ ME ++ ME -- + ME -- ++ ++ -- ME -- ++ + Me -- MB MB -- -- -- ME + ++ MS -- __ -- MS -- -- + SP -- -- Me ++ ++ -- ME + SP -- -- Me -- ++ ME -- + ++ -- MS -- ME -- -- -- +""" +MAP.weight_data = """ + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 + 50 50 50 50 50 50 50 50 +""" +MAP.spawn_data = [ + {'battle': 0, 'enemy': 3, 'siren': 2}, + {'battle': 1, 'enemy': 2, 'siren': 1}, + {'battle': 2, 'enemy': 1}, + {'battle': 3, 'enemy': 1}, + {'battle': 4}, + {'battle': 5, 'boss': 1}, +] +A1, B1, C1, D1, E1, F1, G1, H1, \ +A2, B2, C2, D2, E2, F2, G2, H2, \ +A3, B3, C3, D3, E3, F3, G3, H3, \ +A4, B4, C4, D4, E4, F4, G4, H4, \ +A5, B5, C5, D5, E5, F5, G5, H5, \ +A6, B6, C6, D6, E6, F6, G6, H6, \ +A7, B7, C7, D7, E7, F7, G7, H7, \ +A8, B8, C8, D8, E8, F8, G8, H8, \ + = MAP.flatten() + + +class Config(ConfigBase): + # ===== Start of generated config ===== + MAP_SIREN_TEMPLATE = ['BB', 'CV'] + MOVABLE_ENEMY_TURN = (2,) + MAP_HAS_SIREN = True + MAP_HAS_MOVABLE_ENEMY = True + MAP_HAS_MAP_STORY = False + MAP_HAS_FLEET_STEP = True + MAP_HAS_AMBUSH = False + MAP_HAS_MYSTERY = False + # ===== End of generated config ===== + + MAP_SWIPE_MULTIPLY = (1.037, 1.056) + MAP_SWIPE_MULTIPLY_MINITOUCH = (1.002, 1.021) + MAP_SWIPE_MULTIPLY_MAATOUCH = (0.973, 0.991) + + +class Campaign(CampaignBase): + MAP = MAP + ENEMY_FILTER = '1L > 1M > 1E > 1C > 2L > 2M > 2E > 2C > 3L > 3M > 3E > 3C' + + def battle_0(self): + if self.clear_siren(): + return True + if self.clear_filter_enemy(self.ENEMY_FILTER, preserve=0): + return True + + return self.battle_default() + + def battle_5(self): + return self.fleet_boss.clear_boss() diff --git a/config/template.json b/config/template.json index 21df39501..3890e459e 100644 --- a/config/template.json +++ b/config/template.json @@ -670,7 +670,7 @@ }, "Campaign": { "Name": "D3", - "Event": "war_archives_20220224_cn", + "Event": "war_archives_20220324_cn", "Mode": "normal", "UseClearMode": true, "UseFleetLock": true, diff --git a/module/config/argument/args.json b/module/config/argument/args.json index 5419f222b..69a396b00 100644 --- a/module/config/argument/args.json +++ b/module/config/argument/args.json @@ -3641,15 +3641,16 @@ "war_archives_20211229_cn", "war_archives_20220210_cn", "war_archives_20220224_cn", + "war_archives_20220324_cn", "war_archives_20220414_cn" ], "option_bold": [ - "war_archives_20220224_cn" + "war_archives_20220324_cn" ], - "cn": "war_archives_20220224_cn", - "en": "war_archives_20220224_cn", - "jp": "war_archives_20220224_cn", - "tw": "war_archives_20220224_cn" + "cn": "war_archives_20220324_cn", + "en": "war_archives_20220324_cn", + "jp": "war_archives_20220324_cn", + "tw": "war_archives_20220324_cn" }, "Mode": { "type": "select", diff --git a/module/config/i18n/en-US.json b/module/config/i18n/en-US.json index 4123113f1..f6c17f648 100644 --- a/module/config/i18n/en-US.json +++ b/module/config/i18n/en-US.json @@ -793,6 +793,7 @@ "war_archives_20211229_cn": "archives Tower of Transcendence", "war_archives_20220210_cn": "archives Northern Overture", "war_archives_20220224_cn": "archives Abyssal Refrain", + "war_archives_20220324_cn": "archives Virtual Tower", "war_archives_20220414_cn": "archives Aurora Noctis" }, "Mode": { diff --git a/module/config/i18n/ja-JP.json b/module/config/i18n/ja-JP.json index c16253dbb..39207496a 100644 --- a/module/config/i18n/ja-JP.json +++ b/module/config/i18n/ja-JP.json @@ -793,6 +793,7 @@ "war_archives_20211229_cn": "檔案 遡望せし虹彩の塔", "war_archives_20220210_cn": "檔案 凍絶の北海", "war_archives_20220224_cn": "檔案 鳴動せし星霜の淵", + "war_archives_20220324_cn": "檔案 幻像の塔", "war_archives_20220414_cn": "檔案 極夜照らす幻光" }, "Mode": { diff --git a/module/config/i18n/zh-CN.json b/module/config/i18n/zh-CN.json index d7dbf3cf0..7cf659c76 100644 --- a/module/config/i18n/zh-CN.json +++ b/module/config/i18n/zh-CN.json @@ -793,6 +793,7 @@ "war_archives_20211229_cn": "档案 逆转彩虹之塔", "war_archives_20220210_cn": "档案 北境序曲", "war_archives_20220224_cn": "档案 深度回音", + "war_archives_20220324_cn": "档案 虚像构筑之塔", "war_archives_20220414_cn": "档案 永夜幻光" }, "Mode": { diff --git a/module/config/i18n/zh-TW.json b/module/config/i18n/zh-TW.json index fd463dd55..6f158ca58 100644 --- a/module/config/i18n/zh-TW.json +++ b/module/config/i18n/zh-TW.json @@ -793,6 +793,7 @@ "war_archives_20211229_cn": "檔案 逆轉彩虹之塔", "war_archives_20220210_cn": "檔案 北境序曲", "war_archives_20220224_cn": "檔案 深度回音", + "war_archives_20220324_cn": "檔案 虛像構築之塔", "war_archives_20220414_cn": "檔案 永夜幻光" }, "Mode": { diff --git a/module/war_archives/assets.py b/module/war_archives/assets.py index 86339322f..2a62d3ab1 100644 --- a/module/war_archives/assets.py +++ b/module/war_archives/assets.py @@ -39,6 +39,7 @@ TEMPLATE_THE_WAY_HOME_IN_THE_NIGHT = Template(file={'cn': './assets/cn/war_archi TEMPLATE_TOWER_OF_TRANSCENDENCE = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_TOWER_OF_TRANSCENDENCE.png', 'en': './assets/cn/war_archives/TEMPLATE_TOWER_OF_TRANSCENDENCE.png', 'jp': './assets/cn/war_archives/TEMPLATE_TOWER_OF_TRANSCENDENCE.png', 'tw': './assets/cn/war_archives/TEMPLATE_TOWER_OF_TRANSCENDENCE.png'}) TEMPLATE_UNIVERSE_IN_UNISON = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_UNIVERSE_IN_UNISON.png', 'en': './assets/en/war_archives/TEMPLATE_UNIVERSE_IN_UNISON.png', 'jp': './assets/jp/war_archives/TEMPLATE_UNIVERSE_IN_UNISON.png', 'tw': './assets/cn/war_archives/TEMPLATE_UNIVERSE_IN_UNISON.png'}) TEMPLATE_UPON_THE_SHIMMERING_BLUE = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_UPON_THE_SHIMMERING_BLUE.png', 'en': './assets/cn/war_archives/TEMPLATE_UPON_THE_SHIMMERING_BLUE.png', 'jp': './assets/cn/war_archives/TEMPLATE_UPON_THE_SHIMMERING_BLUE.png', 'tw': './assets/cn/war_archives/TEMPLATE_UPON_THE_SHIMMERING_BLUE.png'}) +TEMPLATE_VIRTUAL_TOWER = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_VIRTUAL_TOWER.png', 'en': './assets/cn/war_archives/TEMPLATE_VIRTUAL_TOWER.png', 'jp': './assets/cn/war_archives/TEMPLATE_VIRTUAL_TOWER.png', 'tw': './assets/cn/war_archives/TEMPLATE_VIRTUAL_TOWER.png'}) TEMPLATE_VISITORS_DYED_IN_RED = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_VISITORS_DYED_IN_RED.png', 'en': './assets/en/war_archives/TEMPLATE_VISITORS_DYED_IN_RED.png', 'jp': './assets/jp/war_archives/TEMPLATE_VISITORS_DYED_IN_RED.png', 'tw': './assets/tw/war_archives/TEMPLATE_VISITORS_DYED_IN_RED.png'}) TEMPLATE_WINTERS_CROWN = Template(file={'cn': './assets/cn/war_archives/TEMPLATE_WINTERS_CROWN.png', 'en': './assets/en/war_archives/TEMPLATE_WINTERS_CROWN.png', 'jp': './assets/jp/war_archives/TEMPLATE_WINTERS_CROWN.png', 'tw': './assets/tw/war_archives/TEMPLATE_WINTERS_CROWN.png'}) WAR_ARCHIVES_CAMPAIGN_CHECK = Button(area={'cn': (1150, 101, 1166, 130), 'en': (1150, 101, 1166, 130), 'jp': (1150, 101, 1166, 130), 'tw': (1150, 101, 1166, 130)}, color={'cn': (134, 175, 207), 'en': (134, 175, 207), 'jp': (134, 175, 207), 'tw': (134, 175, 207)}, button={'cn': (1150, 101, 1166, 130), 'en': (1150, 101, 1166, 130), 'jp': (1150, 101, 1166, 130), 'tw': (1150, 101, 1166, 130)}, file={'cn': './assets/cn/war_archives/WAR_ARCHIVES_CAMPAIGN_CHECK.png', 'en': './assets/en/war_archives/WAR_ARCHIVES_CAMPAIGN_CHECK.png', 'jp': './assets/jp/war_archives/WAR_ARCHIVES_CAMPAIGN_CHECK.png', 'tw': './assets/tw/war_archives/WAR_ARCHIVES_CAMPAIGN_CHECK.png'}) diff --git a/module/war_archives/dictionary.py b/module/war_archives/dictionary.py index 713e2bc83..94f81f8ea 100644 --- a/module/war_archives/dictionary.py +++ b/module/war_archives/dictionary.py @@ -37,4 +37,5 @@ dic_archives_template = { 'war_archives_20210422_cn': TEMPLATE_DAEDALIAN_HYMN, 'war_archives_20211229_cn': TEMPLATE_TOWER_OF_TRANSCENDENCE, 'war_archives_20220224_cn': TEMPLATE_ABYSSAL_REFRAIN, + 'war_archives_20220324_cn': TEMPLATE_VIRTUAL_TOWER, }