class_name Hack extends Area3D enum Type{ DESTROY, PURGE, INFECT, REDIRECT, TRIGGER, CONTAIN } const range_radius : Dictionary = { Hack.Type.DESTROY : 2, Hack.Type.INFECT : .5, Hack.Type.CONTAIN : .5, Hack.Type.REDIRECT : .5, Hack.Type.TRIGGER : 1, Hack.Type.PURGE : 1.5, } const hack_icons : Dictionary = { Hack.Type.DESTROY : preload("res://external/destroy-icon.png"), Hack.Type.INFECT : preload("res://external/infect-icon.png"), Hack.Type.CONTAIN : preload("res://external/contain-icon.png"), Hack.Type.REDIRECT : preload("res://external/redirect-icon.png"), Hack.Type.TRIGGER : preload("res://external/trigger-icon.png"), Hack.Type.PURGE : preload("res://external/purge-icon.png"), } const destroy_explosion_template = preload("res://experimental/BinbunVFX/impact_explosions/effects/explosion/vfx_explosion_01.tscn") const purge_explosion_template = preload("res://experimental/BinbunVFX/impact_explosions/effects/explosion/vfx_explosion_03.tscn") const contain_template = preload("res://templates/pitfall.tscn") const trigger_explosion_template = preload("res://experimental/BinbunVFX/impact_explosions/effects/explosion/vfx_explosion_04.tscn") const infect_emitter_template = preload("res://templates/gas_emitter.tscn") const force_activate_sound = preload("res://audio/sounds/TomWinandySFX_UI_ScifiTech_Start_06.wav") @onready var range_area : Area3D = %RangeArea @onready var range_shape : SphereShape3D = %RangeShape.shape @onready var model : MeshInstance3D = %Model @onready var icon : Sprite3D = %Icon @onready var force_strip : Sprite3D = %ForceStrip @onready var material : ShaderMaterial = model.get_active_material(0) @onready var reveal_timer : Timer = %RevealTimer @onready var activation_timer : Timer = %ActivationTimer var type : Type var square : Vector3i var hack_owner : int var direction : Vector3 var expended : bool = false var decompiling : bool var decompile_id : int var delayed_trigger_tween : Tween = null var damage : int = 10 var just_revealed : bool = false signal removed(type : Hack.Type) signal decompiled(type : Hack.Type) signal activated(type : Hack.Type) signal revealed(is_visible : bool) func _enter_tree() -> void: Game.level.add_map_marker(self) func _exit_tree() -> void: Game.level.remove_map_marker(self) func setup(new_type : Type, dir : Vector3, h_owner : int) -> void: type = new_type hack_owner = h_owner if type == Type.REDIRECT: var r : float = atan2(dir.z, dir.x) var cardinal : float = roundi(r * 2 / PI) * PI / 2 direction = Vector3(cos(cardinal), 0, sin(cardinal)) func remove() -> void: removed.emit(type) queue_free() func decompile() -> void: decompiled.emit(type) queue_free() func reveal() -> void: if model.visible: return model.visible = true reveal_timer.start(5) just_revealed = true revealed.emit(true) func is_just_revealed() -> bool: return just_revealed func is_revealed() -> bool: return model.visible func _on_reveal_timeout() -> void: if Game.level.is_square_detected(square) or decompiling: reveal_timer.start(5) else: model.visible = false revealed.emit(false) func _ready() -> void: var owns_hack = hack_owner == Multiplayer.id material.set_shader_parameter("glow_color", Color.YELLOW if owns_hack else Color.RED) icon.texture = hack_icons[type] model.visible = owns_hack icon.visible = owns_hack range_shape.radius = range_radius[type] match(type): Type.DESTROY: damage = 15 Type.PURGE: damage = 10 Type.TRIGGER: damage = 5 Type.INFECT: activation_timer.start() Type.REDIRECT: var r : float = atan2(direction.z, -direction.x) + PI/2 force_strip.rotate_y(r) force_strip.visible = owns_hack func _process(_delta: float) -> void: just_revealed = false func blast(body : PawnController) -> void: body.hurt(damage) body.knockup((global_position.direction_to(body.global_position) + Vector3(0,2,0)) * 3) func activate() -> void: if expended: return var explode : bool = false match(type): Type.CONTAIN: generate_contain(square) for body in get_overlapping_bodies(): body.start_pitfall(square, 6.0) #Generate the circle here Type.REDIRECT: Game.oneshot(force_activate_sound) for body in get_overlapping_bodies(): body.fling(direction, 5.0) Type.PURGE: var expl = purge_explosion_template.instantiate() Game.level.add_vfx(expl, square) explode = true Type.TRIGGER: var expl = trigger_explosion_template.instantiate() Game.level.add_vfx(expl, square) explode = true Type.DESTROY: var expl = destroy_explosion_template.instantiate() Game.level.add_vfx(expl, square) explode = true Type.INFECT: var emitter = infect_emitter_template.instantiate() emitter.hack_owner = hack_owner emitter.square = square emitter.damage = 4 Game.level.add_vfx(emitter, square) if explode: trigger_adjacent_bombs() blast_players() expended = true Game.level.uninstall_hack_square(square) activated.emit(type) queue_free() func trigger_adjacent_bombs() -> void: for hack : Hack in range_area.get_overlapping_areas(): if hack.type == Type.DESTROY and hack != self: hack.delay_trigger() func generate_contain(sq : Vector3i) -> void: var contain = contain_template.instantiate() contain.duration = 6.0 Game.level.add_vfx(contain, sq) func blast_players() -> void: for body in range_area.get_overlapping_bodies(): blast(body) func delay_trigger() -> void: if delayed_trigger_tween != null: return delayed_trigger_tween = create_tween() delayed_trigger_tween.tween_interval(.25) delayed_trigger_tween.tween_callback(activate) func _on_body_entered(body: Node3D) -> void: if type == Type.INFECT or type == Type.DESTROY: return if body.id == hack_owner: return if !decompiling or body.id != decompile_id: if !body.is_detecting(): activate() func _on_activation_timer_timeout() -> void: activate()