class_name Tower extends StaticBody2D signal selected signal deselected static var selected_towers: Array[Tower] static var hovered_tower: Tower var is_selected := false: set(value): if value: if not Tower.selected_towers.has(self): Tower.selected_towers.append(self) selected.emit() else: Tower.selected_towers.erase(self) deselected.emit() is_highlighted = false is_selected = value queue_redraw() var is_hovered := false: set(value): if value: hovered_tower = self Input.set_default_cursor_shape(Input.CURSOR_POINTING_HAND) else: if hovered_tower == self: hovered_tower = null Input.set_default_cursor_shape(Input.CURSOR_ARROW) is_hovered = value queue_redraw() var is_highlighted := false: set(value): is_highlighted = value queue_redraw() var mobs_in_range: Array = [] #var selection_area: Area2D signal selection_group_id_changed(previous_id: String) @onready var selection_group_id := get_group_id(): set(value): var previous_id = selection_group_id selection_group_id = value selection_group_id_changed.emit(previous_id) # rpc owner id @export var owner_id = 1 signal attack_value_changed(value: int) @export var attack_range: int = 32 @export var attack_power: int = 1 @export var attack_speed: int = 1 var speed_base: float = 2.0 signal components_changed @export var components: Array[TowerComponent] = [] func _init(): scale = Vector2(Client.current_stage.map.tile_set.tile_size) / Vector2(16, 16) func _ready(): $Range/CollisionShape2D.shape.radius = attack_range $ShootCooldown.wait_time = speed_base / attack_speed %Data.text = "Range: %s - Power: %s - Speed: %s" % [ attack_range, attack_power, attack_speed ] %HUD.visible = false redraw_components() components_changed.connect(func(): selection_group_id = get_group_id() ) func _draw(): modulate = Color(1, 1, 1) if is_hovered: if Client.state is StateDefault: draw_circle( Vector2(Client.current_stage.map.tile_set.tile_size) / scale, 8 + attack_range, Color(1, 1, 1, 0.5), false, 1.0 ) modulate = Color(1.25, 1.25, 1.25) if is_selected: modulate = Color(1.5, 1.5, 1.5) if is_highlighted: var sprite_size = $Sprite2D.get_rect().size draw_rect( Rect2($Sprite2D.position, sprite_size), Color(1, 1, 1, 1.0), false, 2.0 / get_viewport().get_camera_2d().zoom.x ) func _process(_delta: float) -> void: if $ShootCooldown.is_stopped() and not mobs_in_range.is_empty(): shoot() $ShootCooldown.start() #if selection_area and is_instance_valid(selection_area): #var bodies = selection_area.get_overlapping_bodies() #if bodies.size() > 0: #selection_area.queue_free() #for body in bodies: #Client.select_tower(body) func _on_input_event(_viewport: Node, event: InputEvent, _shape_idx: int): # disable remote select for now if owner_id != multiplayer.get_unique_id(): return if Client.state is StateBuild: if event.is_action_pressed("builder_cancel"): Client.remove_tower(self) func _on_range_body_entered(body: Node2D) -> void: mobs_in_range.append(body) func _on_range_body_exited(body: Node2D) -> void: mobs_in_range.erase(body) func _on_selectable_area_hover_enter() -> void: is_hovered = true func _on_selectable_area_hover_exit() -> void: is_hovered = false func _on_selectable_area_select(event: InputEvent) -> void: # disable remote select for now if owner_id != multiplayer.get_unique_id(): return if Client.state is StateDefault: if Input.is_action_pressed("select_multiple") and is_selected: is_selected = false else: Client.select_tower(self) if event.is_double_click(): Client.multi_select(3) func add_component(component: TowerComponent): components.append(component) var sprite = component.sprite $ComponentsAnchor.add_child(sprite) redraw_components() components_changed.emit() func remove_component(component_name: String): for component in components: if component.name == component_name: components.erase(component) $ComponentsAnchor.remove_child($ComponentsAnchor.get_node(NodePath(component.name))) break redraw_components() components_changed.emit() func redraw_components(): for node in %Components.get_children(): node.modulate = Color(1.0, 1.0, 1.0, 0.5) for idx in range(components.size()): var component = components[idx] var sprite = $ComponentsAnchor.get_node(NodePath(component.name)) sprite.position.y = (idx + 1) * -16 %Components.get_node(component.name).modulate = Color(1.0, 1.0, 1.0, 1.0) func is_melee_range(): return attack_range <= (Client.current_stage.map.tile_set.tile_size.x * 2) func shoot(): if not multiplayer.is_server(): # TODO: do shoot animation, but don't subtract hp return var target = mobs_in_range[0] as Unit for component in components: if component.has_method("on_shoot"): component.on_shoot(target) if is_melee_range(): target.set_hp(target.hp - 1) else: # TODO target.set_hp(target.hp - 1) func get_hud(): var data = %HUD.get_child(0).duplicate() data.name = name return data func get_region(): var collision_shape := $CollisionShape2D var shape = $CollisionShape2D.shape as RectangleShape2D return Rect2( collision_shape.position, shape.size * scale ) func get_group_id() -> String: var string := "" string += str(attack_range) string += str(attack_power) string += str(attack_speed) for component in components: string += component.name return string.md5_text() func _on_tree_exiting() -> void: is_selected = false func _on_h_box_container_gui_input(event: InputEvent) -> void: # TODO: money cost # TODO: upgradeable if event.is_action_pressed("select"): get_viewport().set_input_as_handled() var hud: HUD = Client.current_stage.get_node("HUD") var towers = hud.selection_groups[hud.selected_group].duplicate() for tower: Tower in towers: var found = false for idx in range(tower.components.size()): var component = tower.components[idx] if component.name == "Frost": found = true tower.remove_component(component.name) break if not found: tower.add_component(preload("res://Towers/Components/FrostComponent.gd").new()) Client.update_tower(tower.get_path(), tower.to_network_data()) func _on_h_box_container_2_gui_input(event: InputEvent) -> void: if event.is_action_pressed("select"): get_viewport().set_input_as_handled() var hud: HUD = Client.current_stage.get_node("HUD") var towers = hud.selection_groups[hud.selected_group].duplicate() for tower: Tower in towers: var found = false for idx in range(tower.components.size()): var component = tower.components[idx] if component.name == "Burn": found = true tower.remove_component(component.name) break if not found: tower.add_component(preload("res://Towers/Components/BurnComponent.gd").new()) Client.update_tower(tower.get_path(), tower.to_network_data()) func _on_button_1_pressed() -> void: var hud: HUD = Client.current_stage.get_node("HUD") var towers = hud.selection_groups[hud.selected_group].duplicate() if Client.player.money < (attack_range / 8.0) * 10 * towers.size(): return for tower: Tower in towers: tower.attack_range += 8 tower.attack_value_changed.emit((attack_range / 8.0) - 1) tower.get_node("%Data").text = "Range: %s - Power: %s - Speed: %s" % [ tower.attack_range, tower.attack_power, tower.attack_speed ] tower.get_node("%Range/CollisionShape2D").shape.radius = attack_range tower.selection_group_id = tower.get_group_id() Client.update_tower(tower.get_path(), tower.to_network_data()) func _on_button_2_pressed() -> void: var hud: HUD = Client.current_stage.get_node("HUD") var towers = hud.selection_groups[hud.selected_group].duplicate() if Client.player.money < attack_power * 10 * towers.size(): return for tower: Tower in towers: tower.attack_power += 1 tower.attack_value_changed.emit(attack_power - 1) tower.get_node("%Data").text = "Range: %s - Power: %s - Speed: %s" % [ tower.attack_range, tower.attack_power, tower.attack_speed ] tower.selection_group_id = tower.get_group_id() Client.update_tower(tower.get_path(), tower.to_network_data()) func _on_button_3_pressed() -> void: var hud: HUD = Client.current_stage.get_node("HUD") var towers = hud.selection_groups[hud.selected_group].duplicate() if Client.player.money < attack_speed * 10 * towers.size(): return for tower: Tower in towers: tower.attack_speed += 1 tower.attack_value_changed.emit(attack_speed - 1) tower.get_node("%Data").text = "Range: %s - Power: %s - Speed: %s" % [ tower.attack_range, tower.attack_power, tower.attack_speed ] tower.get_node("%ShootCooldown").wait_time = (speed_base / attack_speed) tower.selection_group_id = tower.get_group_id() Client.update_tower(tower.get_path(), tower.to_network_data()) class NetworkData extends Resource: var name: String var owner_id: int var position: Vector2 var attack_range: int var attack_power: int var attack_speed: int var components: Array var sprite_modulate: Color var components_anchor_modulate: Color func to_network_data() -> NetworkData: var data = NetworkData.new() data.name = name data.owner_id = owner_id data.position = global_position data.attack_range = attack_range data.attack_power = attack_power data.attack_speed = attack_speed data.components = components.map(func(item: TowerComponent): return item.name ) data.sprite_modulate = $Sprite2D.modulate data.components_anchor_modulate = $ComponentsAnchor.modulate # IMPROVEMENT: check against last update and only set changed values return data func update_with_network_data(data: NetworkData): for component in components.duplicate(): remove_component(component.name) for component_name in data.components: add_component( load("res://Towers/Components/" + component_name + "Component.gd").new() ) static func from_network_data(data: NetworkData) -> Tower: var tower: Tower = preload("res://Towers/Tower.tscn").instantiate() tower.name = data.name tower.owner_id = data.owner_id tower.global_position = data.position tower.attack_range = data.attack_range tower.attack_power = data.attack_power tower.attack_speed = data.attack_speed return tower