@tool extends Control const LEVEL_PATH : String = "res://Levels" const LEVEL_REGEX_PATERN : String = "(level_.*)\\.tres" const ENEMY_PATH : String = "res://enemies" const ENEMY_REGEX_PATERN : String = "(enemy.*)\\.tscn" const BASE_LABEL_SETTINGS = preload("res://addons/LevelEditor/UI/baseLabel.tres") const WAVE_LABEL_SETTINGS = preload("res://addons/LevelEditor/UI/waveLabel.tres") const TROOP_LABEL_SETTINGS = preload("res://addons/LevelEditor/UI/troopLabel.tres") const space_multiplicator : int = 10 enum DIRECTION { UP, DOWN, TOP, LEFT, VERTICAL, HORIZONTAL} var enemies : Dictionary @onready var autoLaunchLevel := $VBoxContainer2/ButtonContainer2/AutoLaunchLevel @onready var levelSelect := $VBoxContainer2/HBoxContainer/LevelSelect @onready var waveTabContainer : TabContainer = $VBoxContainer2/ScrollContainer/WaveContainer @onready var towerSelector := $VBoxContainer2/ButtonContainer5/TowerSelector var level : Level var currentWave : int = -1 func _ready() -> void: resetApp() towerSelector.clear() towerSelector.max_columns = Tower.TYPES.size() for towerType : String in Tower.TYPES: if Tower.TYPES.NONE != Tower.TYPES.get(towerType): towerSelector.add_item(" " + towerType + " ") func _input(event: InputEvent) -> void: if event is not InputEventKey || !event.pressed: return # NOTE prevent to select new wave button if event.keycode == KEY_PAGEUP: waveTabContainer.select_next_available() elif event.keycode == KEY_PAGEDOWN: waveTabContainer.select_previous_available() func buildTree() -> void: if !level: return manageAllowedTowers() autoLaunchLevel.button_pressed = level.auto_start for i in level.waves.size(): waveTabContainer.add_child(buildWave(level.waves[i])) recreateTabBar() func manageAllowedTowers() -> void: towerSelector.deselect_all() for towerAllowed in level.allowedTowers: # NOTE minus one is for remove none value towerSelector.select(towerAllowed - 1, false) func buildWave(wave : Wave) -> VBoxContainer: var troopContainer := VBoxContainer.new() for i in wave.troops.size(): var troop : Troop = wave.troops[i] if troop.spawn_delay > 0 || i > 0 && \ !troopContainer.get_child(troopContainer.get_child_count() - 1).has_meta("troop_group"): troopContainer.add_child(HSeparator.new()) if troop.spawn_delay: var timeSeparator := buildInputLabel( func(newValue): troop.spawn_delay = newValue if newValue == 0: refreshWaveNode(level.waves[currentWave], currentWave), troop.spawn_delay, CustomLineEdit.TYPE.FLOAT, "sec." ) troopContainer.add_child(timeSeparator) var nodeToAppend : BoxContainer = troopContainer if i < wave.troops.size() -1 && wave.troops[i + 1].spawn_delay == 0: if i == 0 || !troopContainer.get_child(troopContainer.get_child_count() - 1).has_meta("troop_group"): nodeToAppend = HBoxContainer.new() nodeToAppend.size_flags_horizontal = Control.SIZE_SHRINK_CENTER nodeToAppend.set_meta("troop_group", true) troopContainer.add_child(nodeToAppend) if i > 0 && troop.spawn_delay == 0: nodeToAppend = troopContainer.get_child(troopContainer.get_child_count() - 1) nodeToAppend.add_child(VSeparator.new()) var ennemyContainer := VBoxContainer.new() ennemyContainer.add_child(createSection("Troop N°" + str(i + 1), removeTroop.bind(troop, wave), TROOP_LABEL_SETTINGS)) if troop.spawn_delay == 0: var button := Button.new() button.text = "Séparer" button.pressed.connect( func(): troop.spawn_delay = 1 refreshWaveNode(level.waves[currentWave], currentWave), ) ennemyContainer.add_child(button) buildTroop(troop, ennemyContainer) nodeToAppend.add_child(ennemyContainer) var addTroopBtn := Button.new() addTroopBtn.text = "Ajouter une troupe" addTroopBtn.pressed.connect(addTroop.bind(wave)) troopContainer.add_child(addTroopBtn) return troopContainer func buildTroop(troop : Troop, ennemyContainer : VBoxContainer) -> void: var qtyEdit := buildInputLabel( func(newValue): troop.number_to_spawn = newValue, troop.number_to_spawn, CustomLineEdit.TYPE.INT, "x" ) var enemySelector := CustomOptionButton.new() enemySelector.onValueChanged.connect(func(resourcePath): troop.enemy = load(resourcePath)) var enemy := troop.enemy.resource_path if troop.enemy else "" enemySelector.setOptions(enemies, enemy) qtyEdit.add_child(enemySelector) ennemyContainer.add_child(qtyEdit) func buildInputLabel(updateCallback : Callable, delay : float, type : CustomLineEdit.TYPE, text : String = "") -> HBoxContainer: var container := HBoxContainer.new() container.size_flags_vertical = Control.SIZE_SHRINK_CENTER container.alignment = BoxContainer.ALIGNMENT_CENTER var timeEdit := CustomLineEdit.new() timeEdit.inputType = type timeEdit.setValue(delay) timeEdit.valueHasChanged.connect(updateCallback) container.add_child(timeEdit) if text: var label := Label.new() label.text = text label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.label_settings = BASE_LABEL_SETTINGS container.add_child(label) return container func createSection(sectionName : String, BtnCallback : Callable, settings : LabelSettings = BASE_LABEL_SETTINGS) -> HSplitContainer : var container := HSplitContainer.new() var label := Label.new() label.text = sectionName label.label_settings = settings var button := Button.new() button.text = "Supprimer" button.pressed.connect(BtnCallback) container.add_child(label) container.add_child(button) return container func cleanMenu() -> void: for child in waveTabContainer.get_children(): waveTabContainer.remove_child(child) child.queue_free() func addTroop(toWave : Wave) -> void: toWave.troops.append(Troop.new()) refreshWaveNode(toWave, currentWave) func removeTroop(troop : Troop, fromWave : Wave) -> void: fromWave.troops.erase(troop) refreshWaveNode(fromWave, currentWave) func refreshWaveNode(wave : Wave, waveIdx : int) -> void: var waveNode := waveTabContainer.get_child(waveIdx) waveTabContainer.remove_child(waveNode) waveNode.queue_free() var waveUI := buildWave(wave) waveTabContainer.add_child(waveUI) waveTabContainer.move_child(waveUI, waveIdx) recreateTabBar() waveTabContainer.current_tab = waveIdx func cleanAndBuildMenu() -> void: cleanMenu() buildTree() func resetApp() -> void: enemies = getOptionsFromFile(ENEMY_PATH, ENEMY_REGEX_PATERN) levelSelect.setOptions(getOptionsFromFile(LEVEL_PATH, LEVEL_REGEX_PATERN)) cleanAndBuildMenu() func removeWave() -> void: level.waves.remove_at(currentWave) var wave := waveTabContainer.get_child(currentWave) waveTabContainer.remove_child(wave) wave.queue_free() recreateTabBar() func addWave() -> void: var wave := Wave.new() level.waves.append(wave) waveTabContainer.add_child(buildWave(wave)) recreateTabBar() waveTabContainer.current_tab = level.waves.size() - 1 func changeWaveOrder(newPos : int) -> void: var newWaveOrder : Array[Wave] var waveToMove = level.waves[currentWave] for i in level.waves.size(): if i == 0 && newPos == 0: newWaveOrder.append(waveToMove) if i != currentWave: newWaveOrder.append(level.waves[i]) if i == newPos && newPos != 0: newWaveOrder.append(waveToMove) currentWave = newPos level.waves = newWaveOrder func selectLevel(levelPath : String) -> void : level = load(levelPath) cleanAndBuildMenu() func tabFocusHaschanged(idx : int) -> void: currentWave = idx func _on_auto_launch_wave_toggled(toggled_on: bool) -> void: level.auto_start = toggled_on func _on_new_level_pressed() -> void: level = Level.new() var levelName = "level_" + str(levelSelect.item_count + 1) levelSelect.addOption(LEVEL_PATH + "/" + levelName + ".tres", levelName, true) _on_save_pressed() func _on_save_pressed() -> void: ResourceSaver.save(level, levelSelect.selectedValue, ResourceSaver.FLAG_BUNDLE_RESOURCES) func onSelectedTowerChange(index: int, selected: int) -> void: # NOTE Adding one for taking NONE value in account index += 1 if selected: level.allowedTowers.append(index) else: level.allowedTowers.erase(index) func getOptionsFromFile(path : String, regexPattern : String) -> Dictionary : var files = {} var regex := RegEx.create_from_string(regexPattern) var dir := DirAccess.open(path) for file in dir.get_files(): var fileMatch := regex.search(file) if fileMatch: files.set(fileMatch.strings[1], path + "/" + file) return files func recreateTabBar() -> void: var waveTabBar := waveTabContainer.get_tab_bar() for i in waveTabBar.tab_count: if i < waveTabContainer.get_child_count(): waveTabBar.set_tab_title(i, "Vague N°" + str(i + 1)) else: waveTabBar.remove_tab(i)