extends Area2D class_name Demon @onready var sprite: AnimatedSprite2D = $AnimatedSprite2D @onready var vision: ShapeCast2D = $Vision @onready var vision_polygon: Polygon2D = $Vision/Polygon2D @onready var gun_shot_sound: AudioStreamPlayer = $GunShotSound @export var shot_probability: float = 0.5 var sp_modifier: float = 0.0 var ammo: int = 0 enum State { STATE_IDLE, STATE_SHOOTING, STATE_RELOADING, } var state: State = State.STATE_IDLE var is_being_dragged: bool = false func _ready() -> void: ammo = Game.max_ammo func _process(_delta: float) -> void: vision_polygon.visible = GlobalInput.visible_vision func _physics_process(_delta: float) -> void: if state != State.STATE_IDLE: return vision.force_shapecast_update() if not vision.is_colliding(): return var foe = vision.get_collider(0) if foe == null || not (foe is Area2D) || not foe.visible: return var value = randf() var actual_probability = shot_probability + sp_modifier if value <= actual_probability: sp_modifier = 0.0 await shoot_at(foe as Area2D) else: sp_modifier += 0.1 print("%s: not shooting (%.2f > %.2f)" % [name, value, actual_probability]) func _on_mouse_entered(): if not GlobalInput.is_dragging(): # TODO: add custom cursor image Input.set_custom_mouse_cursor(null, Input.CURSOR_MOVE) func _on_mouse_exited(): if not GlobalInput.is_dragging(): # TODO: add custom cursor image Input.set_custom_mouse_cursor(null, Input.CURSOR_ARROW) func _on_input_event(_viewport: Node, event: InputEvent, _shape_idx: int): if event is InputEventMouseButton: handle_click(event) func handle_click(event: InputEventMouseButton): if event.button_index != MOUSE_BUTTON_LEFT: return if event.pressed && not GlobalInput.is_dragging(): is_being_dragged = true GlobalInput.start_dragging(self) elif not event.pressed && is_being_dragged: is_being_dragged = false GlobalInput.stop_dragging() func reload() -> void: state = State.STATE_RELOADING print("%s: reloading (%.2f seconds)" % [name, Game.reload_time]) await get_tree().create_timer(Game.reload_time).timeout print(name + ": done reloading") ammo = Game.max_ammo state = State.STATE_IDLE func shoot_at(foe: Area2D) -> void: if state != State.STATE_IDLE: return print("%s: Ammo = %d/%d" % [name, ammo, Game.max_ammo]) if ammo <= 0: await reload() else: state = State.STATE_SHOOTING ammo -= 1 gun_shot_sound.play() sprite.play("shoot") var distance: float = clampf((foe.global_position - global_position).length(), 140.0, 250.0) var distance_percent: float = 1.0 - ((distance - 140.0) / 110.0) #print(distance_percent) var speed: float = minf(foe.speed, 100.0) var speed_percent: float = 1.0 - ((speed - 64.0) / 36.0) #print(speed_percent) var base = Game.base_accuracy var rest = (1.0 - base) / 2.0 var hit_chance: float = base + distance_percent * rest + speed_percent * rest print("%s: Base %.2f Speed %.2f Distance %.2f"% [name, base, speed_percent * rest, distance_percent * rest]) print("%s: Shooting at %s with %.2f hit chance" % [name, foe.name, hit_chance]) var hit: float = randf() if hit > hit_chance: print("%s: missed shot (%.2f > %.2f)" % [name, hit, hit_chance]) else: if foe.has_method("take_damage"): await foe.take_damage() else: foe.queue_free() await sprite.animation_finished if ammo <= 0: await reload() else: state = State.STATE_IDLE func _on_animation_finished(): sprite.animation = "idle" sprite.frame = 0