Problem with type and classes

A little problem with typescript and typo, first, we have the consumables class with its interfaces…

class Consumable {
	constructor(consumableProp: consumableProp) {
		this.prop = consumableProp;
	}

	// Return a new instance containing our own data.
    clone() {
        return new Consumable(clone(this.prop))
    }

    toJSON() {
        return JSON.reviveWrapper('new setup.Consumable($ReviveData$)', clone(this.prop))
	}
	
	// Equipment getters
	get id() {
		return this.prop.id;
	}
	set id(value) {
		this.prop.id = value;
	}

	get name() {
		return this.prop.name;
	}
	set name(value) {
		this.prop.name = value;
	}

	get desc() {
		return this.prop.desc;
	}
	set desc(value) {
		this.prop.desc = value;
	}

	get quantity() {
		return this.prop.quantity;
	}
	set quantity(value) {
		this.prop.quantity = value;
	}

	get maxQuantity() {
		return this.prop.maxQuantity;
	}
	set maxQuantity(value) {
		this.prop.maxQuantity = value;
	}

	// get consumable effect
	get effect() {
		return this.prop.effect;
	}
}

// Equipments model interface
interface Consumable {
	prop: consumableProp;
}

interface consumableProp {
        id     : string;
	name   : string;
	desc   : string;
	quantity : number;
	maxQuantity : number;
	effect : () => void;
}

The State.variables typing…

consumables : {
    healthPot : Consumable;

    getItem : (ID: keyof ConsumablesBoard) => Consumable;
}

So, we have the setting of the items and methods in the story variables.

State.variables.consumables = {
	healthPot: new Consumable({
		id     : "healthPot", 
		name   : "Health potion",
		desc   : "text",
		quantity : 1,
		maxQuantity : 10,
		effect : () => { 
			const _player = State.variables.player;
			const _restaure = 20;

			setup.output = `text! (+ 20 health)`;

			_player.health += _restaure;
		}
	}),

	getItem(ID) {
		return clone(this[ID])
	}
}

// Consumables types list
interface ConsumablesBoard {
	healthPot : string;
}

The problem is in the getItem method, when used in this way in the inventory functions… const _newItem = State.variables.consumables.getItem(_temToAdd.id)

getItem(_temToAdd.id) triggers a typo error. Argument of type ‘string’ is not assignable to parameter of type ‘“healthPot”’.

I’m kind of clueless to solve this, I would like to ask if anyone has an idea of where the error is here.

Some things I see right away.

If you’re using TypeScript, and good for you for doing that, type your parameters, as in “getItem(ID : string ) {…}”. Besides documenting your intent, it helps catch typing errors earlier, preferably before running the code.

In the case of “clone()”, you don’t have a parameter at all, and yet clone takes a parameter.

The code “constructor(consumableProp: consumableProp) {…}” is odd in that the parameter name and its type are identical. In some languages, that would be a scoping error – the parameter name hides the type name. To avoid confusion, I would change the interface to “ConsumableProp”.

You also have a class called “Consumable” and an interface called “Consumable”. I can’t even begin to guess what the parser/compiler would be doing in this case. You might be able to simplify things by using “class ConsumableItem implements Consumable”. Then you could get rid of “this.prop” and just have the local data structures needed to implement the methods called out in the interface.

In that case, the constructor would be:

constructor(consumableProperties : {
id : string, // mandatory
name: string, // mandatory
desc?: string, // optional
// and so on
} (
// assign the property to a local variable or set of variables, depending on your preference
)

This is an excellent start to a class-based approach, but I’d recommend you untangle your naming, and collapse the levels of interfaces and classes in play. That will either fix the problem (in case it’s some weird scoping issue) or make it apparent where the problem is.

If you’re referring to the Consumable instance method clone(), then it does not need to take a parameter in this case—or most cases for that matter.

What is ConsumablesBoard? Edit: I’m no TS expert, but I’m guessing that it’s a typing issue with ConsumablesBoard or how it’s used.

Also. You’ve been told, repeatedly, not to attach methods to generic objects, which by what you’ve shown appears to be what you’re doing with the getItem() static method in the object assigned to $consumables. Edit: I’m not saying that’s the issue, but it’s a bad practice regardless.

Just pointing out what my TypeScript IDE flagged when I popped the code in. Not the only thing it flagged either. In any case, I would put an optional parameter to be clear: “clone(itemToClone? : typeName)”.

And wait a mo’ – isn’t this recursive?

clone() {
return new Consumable(clone(this.prop))
}

In other words, if clone is calling itself, then you’d be stuck in a recursion loop until your call stack overflowed.

Oops, the inner “clone” is the built-in Sugarcube function. My bad. I’m still absorbing the format and missing details like that. But the question stands – how does the inner clone call not get masked by the method “clone” wrapping it?

The <Consumable>.clone() method neither takes nor needs a parameter. I’m unsure why TS thinks one is necessary there.

Unless maybe you’re confusing an error message for the function as one for the method? If so, I’d recheck that the SugarCube v2 type definitions are installed correctly.

Edit: Okay, yeah, you were getting a message for the function. Should have read your last reply better.

You may want to install the Definitely Typed package, @types/twine-sugarcube, which provides type definitions for SugarCube v2 APIs.

The DT typing for clone() looks like:

function clone<T>(original: T): T;

 

There’s no recursion there. There’s no confusion between calls to the clone() function or the <Consumable>.clone() method as how you call them is different. E.g.,

// Function:
clone(…)

// Method: <…>.clone()
this.clone() // internally
anInstance.clone() // externally

Thanks for taking the time to point out what I missed. So far I’ve only messed with macro and presentation code in my Twee3-based forays into IF, though creating classes for in-world objects (characters, inventory items, and so on) is on the roadmap. And thanks for the point to Definitely Typed.