102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
import { Color, GArray, Node, Node3D, PackedScene, PhysicsBody3D, PhysicsRayQueryParameters3D, Signal1, Timer, Vector3 } from 'godot'
|
|
import { export_file, signal } from 'godot.annotations'
|
|
import { export_node, onready } from './annotations'
|
|
import Weapon from './weapon'
|
|
import AsyncResourceLoader from './async_resource_loader'
|
|
import { forward } from './vec'
|
|
import DebugDraw from './debug_draw'
|
|
import { find_in_anscestors, find_in_descendents, implements_interface } from './node'
|
|
import { Damageable } from './health'
|
|
|
|
const { MULTIPLY: mul, ADD: add } = Vector3
|
|
|
|
export default class EquippedWeapon extends Node3D {
|
|
@export_file('*.tres')
|
|
starting_weapon?: string
|
|
_starting_weapon?: Weapon
|
|
|
|
@export_node()
|
|
transform_parent!: Node3D
|
|
|
|
_equipped_weapon?: Weapon
|
|
_has_equipped_weapon: boolean = false
|
|
|
|
@signal()
|
|
equipped!: Signal1<Weapon>
|
|
|
|
@signal()
|
|
unequipped!: Signal1<Weapon>
|
|
|
|
@onready('FireRate')
|
|
_fire_rate_timer!: Timer
|
|
|
|
has_equipped_weapon(): boolean {
|
|
return this._has_equipped_weapon
|
|
}
|
|
|
|
_ready(): void {
|
|
if (this.starting_weapon != null) {
|
|
AsyncResourceLoader.instance.load<Weapon>(this.starting_weapon, 'Weapon').then(weapon => this.equip(weapon))
|
|
}
|
|
}
|
|
|
|
unequip() {
|
|
if (this._equipped_weapon != null) {
|
|
const previous = this._equipped_weapon
|
|
this._equipped_weapon = undefined
|
|
this._has_equipped_weapon = false
|
|
this.unequipped.emit(previous)
|
|
}
|
|
}
|
|
|
|
_parent_scene_to_transform(scene: PackedScene) {
|
|
const children: GArray<Node> = this.transform_parent.get_children()
|
|
for (const child of children) {
|
|
child.queue_free()
|
|
}
|
|
|
|
const node = scene.instantiate() as Node3D
|
|
this.add_child(node)
|
|
node.reparent(this.transform_parent, false)
|
|
}
|
|
|
|
equip(weapon: Weapon) {
|
|
AsyncResourceLoader.instance.load<PackedScene>(weapon.scene, 'PackedScene')
|
|
.then(scene => this._parent_scene_to_transform(scene))
|
|
|
|
if (this._has_equipped_weapon) {
|
|
this.unequip()
|
|
}
|
|
|
|
this._has_equipped_weapon = true
|
|
this._equipped_weapon = weapon
|
|
this._fire_rate_timer.wait_time = weapon.fire_rate
|
|
this.equipped.emit(weapon)
|
|
}
|
|
|
|
fire(): boolean {
|
|
if (this._equipped_weapon && this._fire_rate_timer.time_left <= 0) {
|
|
this._fire_rate_timer.start()
|
|
const space_state = this.get_world_3d().direct_space_state
|
|
const origin = this.global_position
|
|
const end = add(origin, mul(forward(this.global_transform), this._equipped_weapon.range))
|
|
const query = PhysicsRayQueryParameters3D.create(origin, end)
|
|
const result = space_state.intersect_ray(query)
|
|
|
|
if (!result.is_empty()) {
|
|
DebugDraw.draw_ray(query, Color.DARK_RED)
|
|
const root = find_in_anscestors(result.get('collider'), n => n instanceof PhysicsBody3D)
|
|
const health = find_in_descendents<Node & Damageable>(root, n => implements_interface<Damageable>(['apply_damage'], n))
|
|
if (health != null) {
|
|
health.apply_damage(this, this._equipped_weapon.damage)
|
|
}
|
|
} else {
|
|
DebugDraw.draw_ray(query, Color.DARK_GREEN)
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|