item, inventory, and interaction system

This commit is contained in:
Rowan 2025-05-02 12:56:07 -05:00
parent 60e50e902f
commit 6df47a294b
7 changed files with 166 additions and 4 deletions

View file

@ -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<Interactor>
interact(interactor: Interactor) {
console.log('awawawawawa', interactor)
this.interacted.emit(interactor)
}
}

30
src/interactable/door.ts Normal file
View file

@ -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')
}
}
}

View file

@ -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<Interactable> = []
_ready(): void {
this._root = this.get_node(this._root_node)
this.area_entered.connect(Callable.create(
this,
this._on_area_entered

69
src/inventory.ts Normal file
View file

@ -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<StringName, ItemInstance> = new Map()
static find_inventory(root: Node): Inventory | null | undefined {
const child_enumerator: GArrayEnumerator<Node> = 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)
}
}

22
src/item_data.ts Normal file
View file

@ -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
}

26
src/item_pickup.ts Normal file
View file

@ -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()
}
}

View file

@ -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()
}