class_name Unit extends CharacterBody2D signal selected static var selected_unit: Unit var is_selected = false : set(value): if value: Unit.selected_unit = self selected.emit() $Label.visible = true line.visible = true else: if Unit.selected_unit == self: Unit.selected_unit = null $Label.visible = false line.visible = false is_selected = value queue_redraw() var is_hovered = false : set(value): if value: $Label.visible = true else: if not is_selected: $Label.visible = false is_hovered = value queue_redraw() var target: Node2D: set(value): target = value reset_path() var immediate_target: Vector2 var current_path: PackedVector2Array var current_path_idx = 0 : set(value): current_path_idx = value if line: line.points = PackedVector2Array(current_path.slice(value)) var previous_position: Vector2 var recent_closest_paths: Array[PackedVector2Array] var stuck_position_accumulator = 0.0 var roaming_mode = false # rpc owner id @export var owner_id = 1 @export var base_speed: float = 100 @export var speed: float = base_speed @export var hp = 50 : set = set_hp @onready var line: Line2D = $UnitPathLine.duplicate() @onready var sprite: Sprite2D = $Sprite2D func _ready(): if not target: target = Client.stage.get_node("%Goal").path_position line.name = "Line@" + name Client.stage.units.add_child(line) reset_path() Client.stage.path_grid_changed.connect(func(): # TODO: get current position from owner_id rpc var has_intersection = Client.array_intersect(current_path, Stage.last_solid_set_points) # bug: when setting two towers in close succession, new tower overwrites old last solid points #print(current_path) #print(Stage.last_solid_set_points) if has_intersection: #print("true") reset_path() current_path_idx = min(1, current_path.size() - 1) ) %HPBar.init(hp) set_hp(hp) $SelectionArea/CollisionShape2D.shape.size = $Sprite2D.texture.get_size() * $Sprite2D.scale func _physics_process(delta): if not multiplayer.is_server(): #Network.update_unit.rpc(get_path(), { #"hp": hp, #}) return previous_position = global_position if not current_path.is_empty(): if (global_position - current_path[current_path_idx]).is_zero_approx(): current_path_idx += 1 if current_path_idx >= current_path.size(): reset_path() walk_along_path(current_path, current_path_idx, delta) if roaming_mode: var collision = get_last_slide_collision() if collision: var tower = collision.get_collider() as Node2D Client.destroy_tower(tower) # if unit stuck in tower var position_difference = abs(previous_position - global_position) if position_difference.x < 0.1 and position_difference.y < 0.1: stuck_position_accumulator += delta if stuck_position_accumulator >= 1.0: reset_path() else: stuck_position_accumulator = 0 #Network.update_unit.rpc(get_path(), { #"position": global_position, #"hp": hp, #"sprite": {"self_modulate": $Sprite2D.self_modulate}, #"current_path_idx": current_path_idx, #"current_path": current_path, #}) func _draw(): if is_selected: draw_circle( Vector2.ZERO, Client.stage.map.tile_set.tile_size.x * 0.75, Color(1, 1, 1, 0.75), false, 1.0 ) modulate = Color(1.5, 1.5, 1.5) elif is_hovered: draw_circle( Vector2.ZERO, Client.stage.map.tile_set.tile_size.x * 0.75, Color(1, 1, 1, 0.5), false, 1.0 ) modulate = Color(1.25, 1.25, 1.25) else: modulate = Color(1, 1, 1) func _on_navigation_base_area_entered(area: Area2D): if not multiplayer.is_server(): return if area.is_in_group("goal"): Client.player.score += 1 queue_free() if area.is_in_group("path"): var path_node: PathNode = area.get_parent() if path_node == target: target = path_node.next_node func walk_along_path(path: PackedVector2Array, index: int, delta: float): immediate_target = path[index] var displacement := immediate_target - global_position var direction := displacement.normalized() var distance := displacement.length() var max_speed: float = (distance / delta) velocity = direction * minf(speed, max_speed) for effect in get_effects(): if effect.has_method("apply_physics"): effect.apply_physics(delta) # todo: changing velocity here doesn't work nicely # todo: because the velocity expects ??to each the point??, so it stutters move_and_slide() func set_hp(value): # TODO: rpc on damage hp = value if get_node("%HPBar"): %HPBar.set_value(value) if get_node("Label"): $Label.text = str(hp) if hp <= 0: queue_free() func get_effects(): var effects = [] for node in get_children(): if is_instance_of(node, Effect): effects.append(node) return effects func add_effect(effect: Effect): var node = get_node_or_null(NodePath(effect.name)) as Effect if node: node.set_duration(node.duration) else: add_child(effect) func reset_path(): roaming_mode = false current_path = get_grid_path() if current_path.is_empty(): # TODO: nimm letzte route davor die noch ging, falls verfügbar # TODO: schneide ab bis eins vor neue tower position. (=partial path) # TODO: laufe bis dahin und checke dann end of partial path current_path = get_grid_path(true) recent_closest_paths.append(current_path) # reached end of partial path if current_path.size() == 1 and current_path[0] == global_position: roaming_mode = true current_path = PackedVector2Array([target.path_position + Vector2(16,16)]) # iterating between one or more closest paths elif recent_closest_paths.count(current_path) >= 2: roaming_mode = true current_path = PackedVector2Array([target.path_position + Vector2(16,16)]) recent_closest_paths = [] else: recent_closest_paths = [] roaming_mode = false current_path_idx = 0 if line: line.points = PackedVector2Array(current_path) func get_grid_path(partial = false): return Client.stage.path_grid.get_point_path( Client.stage.map.local_to_map(global_position), Client.stage.map.local_to_map(target.path_position), partial ) func _on_selection_area_input_event(_viewport: Node, event: InputEvent, _shape_idx: int) -> void: # disable remote select for now if owner_id != multiplayer.get_unique_id(): return if Client.state is StateDefault: if event.is_action_pressed("select"): if selected_unit: selected_unit.is_selected = false is_selected = true $Label.text = str(hp) func _on_selection_area_mouse_entered() -> void: is_hovered = true func _on_selection_area_mouse_exited() -> void: is_hovered = false func _on_tree_exiting() -> void: is_selected = false line.queue_free() Network.remove_unit.rpc(get_path()) class NetworkData extends Resource: var name: String var position: Vector2 var target_path: String var hp: int var speed: float var texture_path: String func to_network_data() -> NetworkData: var data = NetworkData.new() data.name = name data.position = global_position data.target_path = target.get_path() data.hp = hp data.speed = speed data.texture_path = get_node("Sprite2D").texture.resource_path return data #func update_with_network_data(data: NetworkData): # TODO static func from_network_data(data: NetworkData) -> Unit: var unit: Unit = preload("res://Units/Unit.tscn").instantiate() unit.name = data.name unit.global_position = data.position unit.target = unit.get_tree().current_scene.get_node(data.target_path) unit.hp = data.hp unit.speed = data.speed unit.get_node("Sprite2D").texture = load(data.texture_path) return unit