diff --git a/src/interactable.ts b/src/interactable.ts index cc6cb82..bc59001 100644 --- a/src/interactable.ts +++ b/src/interactable.ts @@ -1,8 +1,13 @@ -import { Area3D } from 'godot' +import { Area3D, Signal1 } from 'godot' import Interactor from './interactor' +import { signal } from 'godot.annotations' export default class Interactable extends Area3D { + @signal() + readonly interacted!: Signal1 + interact(interactor: Interactor) { - console.log('awawawawawa', interactor) + this.interacted.emit(interactor) } } + diff --git a/src/interactable/door.ts b/src/interactable/door.ts new file mode 100644 index 0000000..9deee32 --- /dev/null +++ b/src/interactable/door.ts @@ -0,0 +1,30 @@ +import { Node3D, Variant } from 'godot' +import { export_ } from 'godot.annotations' +import Interactor from '../interactor' +import ItemData from '../item_data' +import Inventory from '../inventory' + +export default class Door extends Node3D { + @export_(Variant.Type.TYPE_BOOL) + readonly locked: boolean = false + + @export_(Variant.Type.TYPE_BOOL) + readonly requires_key: boolean = false + + @export_(Variant.Type.TYPE_OBJECT, { class_: ItemData }) + readonly key_item?: ItemData + + open(interactor: Interactor): void { + console.log('trying to open...') + + if (this.locked && this.requires_key) { + console.log('this door requires a key') + const inventory = Inventory.find_inventory(interactor.root_node) + if (inventory != null && this.key_item != null && inventory.has(this.key_item)) { + console.log('interactor has a', this.key_item.name) + } + } else { + console.log('this door is opened some other way') + } + } +} diff --git a/src/interactor.ts b/src/interactor.ts index 229d404..767047b 100644 --- a/src/interactor.ts +++ b/src/interactor.ts @@ -1,5 +1,6 @@ -import { Area3D, Callable } from 'godot' +import { Area3D, Callable, Node, NodePath, Variant } from 'godot' import Interactable from './interactable' +import { export_ } from 'godot.annotations' class InteractableDistance { static Empty = new InteractableDistance(Infinity) @@ -14,9 +15,19 @@ class InteractableDistance { } export default class Interactor extends Area3D { + @export_(Variant.Type.TYPE_NODE_PATH) + private _root_node: NodePath = new NodePath('.') + private _root!: Node + + get root_node() { + return this._root + } + _interactables: Array = [] _ready(): void { + this._root = this.get_node(this._root_node) + this.area_entered.connect(Callable.create( this, this._on_area_entered diff --git a/src/inventory.ts b/src/inventory.ts new file mode 100644 index 0000000..965e9dd --- /dev/null +++ b/src/inventory.ts @@ -0,0 +1,69 @@ +import { clampi, Node, StringName } from 'godot' +import ItemData from './item_data' +import { GArrayEnumerator } from './collection/enumerable' +import { Enumerable } from '../addons/enumerable-ts/src/index' + +class ItemInstance { + readonly resource: ItemData + + get name() { + return this.resource.name + } + + get type() { + return this.resource.type + } + + get max_quantity() { + return this.resource.max_quantity + } + + quantity: number + + constructor(resource: ItemData, quantity: number = 1) { + this.resource = resource + this.quantity = quantity + } + + set_quantity(quantity: number = 1) { + this.quantity = clampi(quantity, 0, this.max_quantity) + } + + increase_quantity(quantity: number = 1) { + this.set_quantity(this.quantity + quantity) + } + + decrease_quantity(quantity: number = 1) { + this.set_quantity(this.quantity - quantity) + } + + has_none(): boolean { + return this.quantity <= 0 + } +} + +export default class Inventory extends Node { + items: Map = new Map() + + static find_inventory(root: Node): Inventory | null | undefined { + const child_enumerator: GArrayEnumerator = new GArrayEnumerator(root.get_children()) + const children = Enumerable.from(child_enumerator) + return children.find(child => child instanceof Inventory) as Inventory + } + + add(item: ItemData, quantity: number = 1) { + const existing_item = this.items.get(item.name) + + if (!existing_item) { + this.items.set(item.name, new ItemInstance(item, quantity)) + } else { + existing_item.increase_quantity(quantity) + } + } + + has(item: ItemData): boolean { + console.log('checking for', item.name) + return this.items.has(item.name) + } +} + diff --git a/src/item_data.ts b/src/item_data.ts new file mode 100644 index 0000000..299b03b --- /dev/null +++ b/src/item_data.ts @@ -0,0 +1,22 @@ +import { Resource, StringName, Variant } from 'godot' +import { export_, export_enum, export_multiline } from 'godot.annotations' + +enum ItemType { + None, + Key +} + +export default class ItemData extends Resource { + @export_(Variant.Type.TYPE_STRING_NAME) + readonly name: StringName = '' + + @export_enum(ItemType) + readonly type: ItemType = ItemType.None + + @export_multiline() + readonly description: string = '' + + @export_(Variant.Type.TYPE_INT) + readonly max_quantity: number = 99 +} + diff --git a/src/item_pickup.ts b/src/item_pickup.ts new file mode 100644 index 0000000..6b23a2a --- /dev/null +++ b/src/item_pickup.ts @@ -0,0 +1,26 @@ +import { Node, Variant } from 'godot' +import { export_, export_range } from 'godot.annotations' +import ItemData from './item_data' +import Interactor from './interactor' +import Inventory from './inventory' + +export default class ItemPickup extends Node { + @export_(Variant.Type.TYPE_OBJECT, { class_: ItemData }) + readonly resource!: ItemData + + @export_range(0, 99) + readonly quantity: number = 1 + + add_to_inventory(interactor: Interactor) { + const inventory = Inventory.find_inventory(interactor.root_node) + + if (inventory) { + inventory.add(this.resource, this.quantity) + } else { + console.error(interactor.root_node.get_name(), 'has no inventory') + } + + this.queue_free() + } +} + diff --git a/src/player.ts b/src/player.ts index 8f665fe..bd9ceac 100644 --- a/src/player.ts +++ b/src/player.ts @@ -26,7 +26,6 @@ export default class Player extends CharacterBody3D { turn_speed = 1 _rotation_speed = 2 * Math.PI - is_running() { return this.player_input.is_running && !this.velocity.is_zero_approx() }