class_name Unit extends CharacterBody2D signal reached_goal 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_path: NodePath 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 unit_resource: UnitResource var base_speed: float = 100 var speed: float = base_speed var hp = 50 : set = set_hp @onready var line: Line2D = $UnitPathLine.duplicate() var sprite: AnimatedSprite2D: get(): if not sprite: return get_node("AnimatedSprite2D") return sprite func _init(): scale = Vector2(Client.current_stage.map.tile_set.tile_size) / Vector2(16, 16) func _ready(): # setup with data from UnitResource sprite.sprite_frames = unit_resource.sprite_frames base_speed = unit_resource.speed hp = unit_resource.hp base_speed *= scale.x speed = base_speed if not target: if target_path: target = get_node(target_path) line.name = "Line@" + name get_parent().add_child(line) reset_path() Client.current_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) var sprite_texture: Texture2D = sprite.sprite_frames.get_frame_texture("down", 0) $SelectionArea/CollisionShape2D.shape.size = sprite_texture.get_size() func _physics_process(delta): if not multiplayer.is_server(): 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 #Client.update_unit(get_path(), to_network_data()) func _draw(): if is_selected: draw_circle( sprite.position, Client.current_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( sprite.position, Client.current_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"): reached_goal.emit() 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: Vector2 = 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 if velocity.x > 0 and velocity.y == 0: sprite.play("right") elif velocity.x < 0 and velocity.y == 0: sprite.play("left") elif velocity.x == 0 and velocity.y > 0: sprite.play("down") elif velocity.x == 0 and velocity.y < 0: sprite.play("up") elif velocity.x > 0 and velocity.y > 0: sprite.play("down_right") elif velocity.x > 0 and velocity.y < 0: sprite.play("up_right") elif velocity.x < 0 and velocity.y > 0: sprite.play("down_left") elif velocity.x < 0 and velocity.y < 0: sprite.play("up_left") move_and_slide() func set_hp(value): 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 and not node.is_stackable: 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.current_stage.path_grid.get_point_path( Client.current_stage.map.local_to_map(global_position), Client.current_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 owner_id: int var position: Vector2 var target_path: String var hp: int var base_speed: float var speed: float var sprite_frames_path: String var unit_resource: UnitResource func to_network_data() -> NetworkData: var data = NetworkData.new() data.name = name data.owner_id = owner_id data.position = global_position data.target_path = target.get_path() data.hp = hp data.base_speed = base_speed data.speed = speed data.sprite_frames_path = sprite.sprite_frames.resource_path data.unit_resource = unit_resource return data func update_with_network_data(_data: NetworkData): pass static func from_network_data(data: NetworkData) -> Unit: var unit: Unit = preload("res://Units/Unit.tscn").instantiate() unit.name = data.name unit.owner_id = data.owner_id unit.global_position = data.position unit.target_path = data.target_path unit.hp = data.hp unit.base_speed = data.base_speed unit.speed = data.speed unit.sprite.sprite_frames = load(data.sprite_frames_path) unit.unit_resource = data.unit_resource return unit