JavaScript's current type system only supports passing parameters by copy and SugarCube adheres to the same model. Do not confuse passing a reference type by copy, which is what you do with objects in JavaScript, with passing any type by reference.
What this means is that if you're doing something like the following:
<<addToContainer $player.inventory $FancyWatch>>
Then the following part of your <<initContainer>>/<<emptyContainer>> code:
var container = this.args[0];
container = [];
Will not work because you're overwriting container with a new value.
What you need to be doing there is something like the following instead:
// Assign the local copy of the array reference to container.
var container = this.args[0];
// Use the <Array>.splice() method to empty the array.
container.splice(0, container.length);
This also means that the containers must be initialized as arrays before passing them to the macro.
UPDATE: Of course, you're also redefining the findByID() expando method every time you call it, which is not a great idea. I'd suggest something like the following:
Macro.add(['initContainer', 'emptyContainer'], {
handler : function () {
var container = this.args[0];
if (!Array.isArray(container)) {
return this.error('container is not an array');
}
container.splice(0, container.length);
if (!container.findByID) {
container.findByID = function(obj) {
if (
this.length === 0 ||
typeof obj !== 'object' ||
!obj.hasOwnProperty('id')
) {
return false;
}
return !!this.find(function (item) {
return item.id === obj.id;
});
};
}
}
});
ALSO:@greyelf Honestly, findByID() is not a great name for that method. With the standard library taken into account, its name makes it sound like it returns the object, rather than whether it exists. I think that hasById() or existsById(), something like that, would be less confusing.
now, when I'm shopping and I'm calling inside a widget:
<<addToContainer $player.inventory, $args[0]>>
where
args[0]
is
$fancyWatch
this is how I call the widget
<<buyOnce $fancyWatch>>
I get this:
cannot find property findIndex: of undefined
I'll go over this and see where things are going bad. I'm basically trying to generalize the application of this code for multiple containers without copying all the macros and applying them to different containers (that would be a disaster and bigger disaster to maintain).
Macros fall into two broad categories based on the kind of arguments they accept: those that want an expression (e.g. <<set>> and <<print>>) and those that want discrete arguments separated by whitespace (e.g. <<link>> and <<audio>>). The documentation for each macro will tell you what it expects.
All of the inventory macros shown within this thread so far fall into the latter category—i.e. those that want discrete arguments separated by whitespace.
The following from your example:
<<addToContainer $player.inventory, $args[0]>>
Yields three arguments to the macro: the current value of $player.inventory, a string containing a comma (","), and the current value of $args[0].
Yeah, the comma was the cause of all the trouble. Now, I have an inventory system that somewhat works. I'll try and get the transfer macro implemented and finish my shopping demo.
Hey,
I'm trying to write code to equip items that I have in my inventory.
Now, I've written a JS function that is supposed to return an object from my inventory:
/*
It is supposed to search a container for an item ID and return the item
container - the container that we search through
searchId - the ID of the item we are looking for and will return when found
*/
window.getItemById = function(container, searchId)
{
var searched_item = container.find(function(item)
{
return item.id === searchId;
});
if (searched_item)
{
return searched_item;
}
else
{
return -1;
}
}
Now, this returns an undefined, which I can't do anything with. It's not even a -1 value.
This is the experimental code that I'm running the JS function in.
I'm trying to pass getItemById() to $item.
I know that I'm missing lots of checks to make the code bullet proof. It's just frustrating that I can't return an object and store it just like that.
<<nobr>><<linkreplace "Equip trousers">>
<<if countItemById($player.inventory, $trousers.id) > 0>>
<<if $player.clothing.pants !== $trousers>>
<<set $item is getItemById($player.inventory, $trousers.id)>>
<<if typeof $item !== "undefined">>
Found trousers inside your inventory!
<<else>>
YOU FAIL!
<</if>>
<<else>>
Trousers already equipped!
<</if>>
<<else>>
There are no trousers in your inventory!
<</if>>
<</linkreplace>><</nobr>>
<<back>>
I tried turning this into a macro and passing $item back as an argument. It also yields an undefined. Is there any way to return an element of an array inside JS back to SugarCube?
Update:
<<nobr>><<linkreplace "Equip trousers">>
<<if countItemById($player.inventory, $trousers.id) > 0>>
<<if $player.clothing.pants !== $trousers>>
<<set $item is "undefined">>
<<for _i = 0; _i < $player.inventory.length; _i++>>
<<if $player.inventory[_i].id === $trousers.id>>
<<set $item = $player.inventory[_i]>>
<</if>>
<</for>>
<<if typeof $item !== "undefined">>
Found trousers inside your inventory!
<<else>>
<<if $item == "undefined">>
YOU FAIL!
<</if>>
You fail again!
<</if>>
<<else>>
Trousers already equipped!
<</if>>
<<else>>
There are no trousers in your inventory!
<</if>>
<</linkreplace>><</nobr>>
<<back>>
Interestingly this works when I look up the object in SugarCube.
This begs the question:
Is it possible to return values from JS function or macro to SugarCube and continue working with it there? I don't like the solution that I got right now, cause it's ugly as hell, I'd rather have a JS macro that takes care of looking up values inside the array.
@color:green;FOUND : $returned_item.name@@color:yellow;it is not : $inventory[_i].id@@
<</if>>
<</nobr>><</widget>>%/
Yeah, I go that part fixed and understood. Thank you! But what about the code above? For some reason I always get returned_item undefined. To me, it appears that the third argument is not passed to the widget correctly. I'm passing an array, a ref and another ref. I'm trying to return an item and I can't get a hold of it. This is how I call the widget:
@
<</if>>
<</linkreplace>>
<</nobr>>
<<back>>
The player has a structure something like the following:
where hats, pants, shirts and shoes are basically pointing on an item in the inventory and these items have a value, isEquipped = false or true. It seems the widget approach has failed me, I can't get the item to be assigned to this slot and I can't set the isEquipped value.
Right, now I'm restructuring the whole thing and try to do it in JavaScript by passing the player and the item to macro and look up the type of the clothing (yeah, I introduced another property, these objects are just growing) and based on that assign it to the proper slot if it hasn't been assigned yet.
It might be faster to list what isn't wrong with it. Try this:
@color:green;FOUND: _inventory[_i].id / _inventory[_i].name@@color:yellow;It is not: _inventory[_i].id@@
<</if>>
<</nobr>><</widget>>
I can't be certain that you aren't doing something wrong elsewhere, however, that should be closer to correct at least. Among the corrections, I changed several story variables to temporary variables as it seemed like that was their intended purpose.
Also, in your <<linkreplace>> the following line:
<<if $player.clothing.pants !== $trousers>>
Should probably be comparing the object's IDs, rather than the objects directly. For example:
oh, there's "break" inside the for loop, that's useful, is there a "continue" as well? I was also wondering how scope worked here. So, local variables start with an underscore.
Thank you for fixing this mess!
Though
<<if $player.clothing.pants.id !== $trousers.id>>
is not working it appears that
player.clothing.pants
is undefined and doesn't have an
id
property. I might be misunderstanding what this should do.
<< set $player =
{
...
"clothing" : {
...
"pants" : $OldTrousers,
}
}>>
where $OldTrousers:
<<set $OldTrousers =
{
"type" : "item",
"isEquipped" : true,
"id" : "trousers.clothing.old",
"name" : "Old Trousers",
"count" : 1,
"cost" : 5,
"descrition" : "Old trousers with full of holes. You feel embarassed wearing it publicly.",
"image" : "<...>\\Disane\\<..>\\OldTrousers.jpg"
}>>
Update: Yep, this won't cut it:
"pants" : $OldTrousers
I need to do either this:
<<set $player.clothing.pants to $OldTrousers>>
or
<<set $player.clothing.pants to $playerInventory[1]>>
Before equipping ($player.clothing.pants):
[object Object]
item
Old Trousers
trousers.clothing.old
equipping code running:
FOUND: trousers.clothing / Trousers\ \
Old Trousers was unequipped
Trousers was equipped ($player.clothing.pants):
After equipping:
[object Object]
item
Old Trousers
trousers.clothing.old
so basically the <<widget>> has no influence on the "outside" value of $player.clothing.pants.
My code:
@
<</if>>
After equipping:
$player.clothing.pants
$player.clothing.pants.type
$player.clothing.pants.name
$player.clothing.pants.id
<</linkreplace>>
<</nobr>>
@color:green;FOUND: _inventory[_i].id / _inventory[_i].name@@color:yellow;It is not: _inventory[_i].id@@
<</if>>
<</nobr>><</widget>>
Now I tried to use $clothingSlot or $args[2] and writing their values to see if anything changes in the outside value. This wasn't the case. I must be failing to understand some key concept here about SugarCube's concept of scope.
Normally, JS would apply the changes to the passed values. Here, the case is different.
<<set $OldTrousers =
{
"type" : "item",
"isEquipped" : true,
"id" : "trousers.clothing.old",
"name" : "Old Trousers",
"count" : 1,
"cost" : 5,
"descrition" : "Old trousers with full of holes. You feel embarassed wearing it publicly.",
"image" : "<...>\\Disane\\<..>\\OldTrousers.jpg"
}>>
That should work fine, as long as you're defining $OldTrousersbefore$player. You cannot reference $OldTrousers within $player before you've defined it, obviously—attempting to do so will yield an undefined value, because you hadn't defined it yet.
That should work fine, as long as you're defining $OldTrousersbefore$player. You cannot reference $OldTrousers within $player before you've defined it, obviously—attempting to do so will yield an undefined value, because you hadn't defined it yet.
Aha! That makes sense. Thank you! Can you also answer my question about scope in the case of <<widgets>>? Check my question above.
Update:
This JS code worked:
Macro.add('equipClothingJS',
{
handler : function()
{
var character = this.args[0];
var clothing = this.args[1];
if(!character.hasOwnProperty('inventory'))
{
this.error('the passed character has no inventory');
}
if(!clothing.hasOwnProperty('clothingType'))
{
this.error('clothing provided has no ClothingType defined');
}
var item = getItemById(character.inventory, clothing.id);
if (!item)
{
this.error("could not find item in player's inventory");
}
switch(clothing.clothingType)
{
case "hats":
character.clothing.hats.isEquipped = false;
character.clothing.hats = item;
item.isEquipped = true;
break;
case "shirts":
character.clothing.shirts.isEquipped = false;
character.clothing.shirts = item;
item.isEquipped = true;
break;
case "pants":
character.clothing.pants.isEquipped = false;
character.clothing.pants = item;
item.isEquipped = true;
break;
case "shoes":
character.clothing.shoes.isEquipped = false;
character.clothing.shoes = item;
item.isEquipped = true;
break;
}
}
});
Before equipping:
[object Object]
item
Old Trousers
trousers.clothing.old
After equipping:
[object Object]
item
Trousers trousers.clothing
Now, the code is probably a piece of garbage and needs refactoring even maybe a complete rethinking, but it kinda works.
Is there a way to achieve the same using a <<widget>>?
You're passing $player.clothing.pants into the widget, meaning you're passing a copy of the pants property's value. You cannot change the value of the pants property, when you pass a copy of its value—you'll only change the copy. You must pass its parent object instead—$player.clothing in this case.
Try:
@color:green;FOUND: _inventory[_i].id / _inventory[_i].name@@color:yellow;It is not: _inventory[_i].id@@
<</if>>
<</nobr>><</widget>>
I have no idea what this thread is even about anymore. All I can see is a wall of code, and I'm wondering what it's added functionality is compared to the original. o_o
I have no idea what this thread is even about anymore.
It is still about Inventory systems but the last page or so is related to how @Disane wants their specific inventory system to work, which may not be how you want yours to work.
One of the main issues faced by anyone trying to explain how to implement a non-trivial inventory system is the fact that the design quickly become very specific to the needs of the person asking for that explanation.
You're passing $player.clothing.pants into the widget, meaning you're passing a copy of the pants property's value. You cannot change the value of the pants property, when you pass a copy of its value—you'll only change the copy. You must pass its parent object instead—$player.clothing in this case.
Thank you! I was not aware of this. I mean, I understand the fundamental of copies and how they are different, but the fact that you needed to pass the object that held the slot, was something I have not thought of.
Alright, so, now we need 4 arguments for the widget. That escalated quickly.
I'll try this one out and see if its enough to finish the demo.
@greyelf : nah, it's not the design i'm planning on rolling with. I'm merely stretching my legs to write a demo, see if I understand how SugarCube works. TheMadExile and you sir have been very helful and I'm thankful for that.
I'm happy to announce that TheMadExile's solution works flawlessly. I'm on my way to finish this demo. I really hope that I'll not bump into any more hindrances or if so ... well, there's still the fun part finding out what has gone wrong.
This is going to be my first "real" Twine game, although not a full game, hehe.
Alright this really puts the icing on the cake for me. I have no clue what could cause this error, but he <<widget>> that I wrote works on all my other items except for the Watch, which is somehow very-very special, since according to JS it doesn't have an "id", although I defined it and I can add and remove this object from my inventory, yet, the following script tells me, there's no id to be found:
Here's how I call the item from the passage called "Watch":
<<inspectOwnedItem $player %Watch>>
<<back>>
I'm suspecting that the word "watch" or "Watch" has a special meaning somewhere and it escapes the script, making it crash. I had a similar script crash when I had an item named "T-Shirt". Basically the same script could not run the <<if>> statement and could not find it's <</if>> and <<else>> keywords and I had to rename the object to make it work. Every other item worked only these two did not. So, I decided to rename the T-Shirt to Tshirt and everything went fine and dandy. Now, the question is, what is so special about "Watch"? The script is telling me that this item has no "id" property.
Assuming that it's not a transcription error, look at how you're passing the watch's story variable to the widget very closely—hint: story variables do not start with a percent sign (%).
As for the t-shirt. Assuming you tried naming the story variable something like $T-Shirt, then that's your problem, it's not a valid variable name.
So, I tried to turn my getItemById() into an inner function of all of my containers:
Macro.add(['initContainer', 'emptyContainer'],
{
handler : function ()
{
var container = this.args[0];
if (!Array.isArray(container)) {
return this.error('container is not an array');
}
container.splice(0, container.length);
// if findByID() has not been defined
if (!container.findByID)
{
// define new member function findByID
container.findByID = function(obj)
{
if (
this.length === 0 ||
typeof obj !== 'object' ||
!obj.hasOwnProperty('id')
) {
return false;
}
return !!this.find(function (item)
{
return item.id === obj.id;
});
};
}
if(!container.getItemById)
{
container.getItemById = function(searchId)
{
var searched_item = container.find(function(item)
{
console.log("getItemById, item.id: " + item.id);
return item.id === searchId;
});
return searched_item;
};
}
}
});
// define outside container
window.getItemById = function(searchId)
{
var searched_item = container.find(function(item)
{
console.log("getItemById, item.id: " + item.id);
return item.id === searchId;
});
return searched_item;
}
// say we have
<<initContainer $player.inventory>>
<<addToContainer $player.inventory $boots>>
//inside some JS function:
var item1 = container.getItemById(boots.id)
var item2 = getItemById(boots.id)
// when comparing the two
item1 === item2
false
Can anyone explain to me why is this happening?
I tried to implement an equip/unequip mechanic and i use the container.getItemById() variant which pointed on an object that wasn't actually inside the player's inventory, so but when I used the getItemById() variant, it worked as intended and changed the item that was inside the actual container and thus worked nicely.
I really wish to know what the differences are. I'm thinking that the member function (method) is grabbing something different, maybe a copy of the object.
That function shouldn't even be working unless you've defined a container variable someplace in-scope for it to capture. Even if you did, unless you're reassigning that container every turn, it's unlikely to be referring to what it should be and/or you think it is.
You should probably be passing the appropriate container in as a parameter. For example:
I don't mean to intrude, or even de-rail, but I've been following this topic closely as I'm nearing the point that I am going to need to start implementing an inventory system of my own. And I was curious what the advantages of, or rather why individual objects for each individual item?
For example, I have my npcs setup in the following way:
<<set $npcs to {
players_mother : {
name : "Courtney",
age : 36,
height : 163,
hairColor : 0,
eyeColor : 0,
skinTone : 0
},
players_brother : {
name : "Jason",
age : 16,
height : 163,
hairColor : 0,
eyeColor : 0,
skinTone : 0
}
}>>
Which I can then reference as $npcs["players_mother"] which makes it easy when developing a story and helps avoid "variable creep". So to use your item declarations I would have used something like:
<<set $items to {
milk : {
type : "item",
isConsumable : true,
name : "Milk",
count : 1,
cost : 3,
desc : "Some delicious cow milk."
},
trousers : {
type : "item",
isEquipped : false,
clothingType : "pants",
id : "trousers.clothing",
name : "Trousers",
count : 1,
cost : 75,
desc : "Nice trousers for you to wear.",
image : "images/trousers.jpg"
}
}>>
While still giving the player a $player.inventory container for them to be added to.
With this being just a rough example, but I am curious why one implementation over the other?
It's mostly a design decision that me and the op decided to go for our own games. If you are planning on having more than one object of the same type - say for example: 5x rocks and you don't want each individual rock to be displayed in your inventory, taking up precious screen space and naming each rock individually: rock0, rock1.. rockN-1, rockN then going for a quantity based item system might be better.
I also need to add that not everything about the inventory system (that I had in mind) is covered here. In my game, I'm using clones of the items that I have, since I like to keep unique instances in memory, so I can adjust my item's properties individually, without "corrupting" the properties of the rest of the items with the same id and name.
Now, if one of the item's have meaningful differences then I tend to assign a different ID to them dynamically. So this way if I have 3 "Silver Sword's" and I decide to enchant one of them, my "Silver Sword" after adding an echantemnt to it can become an "Enchanted Silver Sword", which has a higher price value and does more damage to certain enemy types. Now, I will have 2 "Silver Swords" in my inventory and 1 "Enchanted Silver Sword". This way I also prevent loosing the enchantment (and the enchantment properties) when moving items from a container over to the player's inventory or selling or buying items.
If you are not building RPG elements into your game, then this kind of inventory system might be an overkill, it is you the writer/designer/programmer who needs to decide in the design phase of your game/story what you want to go for.
Comments
What this means is that if you're doing something like the following: Then the following part of your <<initContainer>>/<<emptyContainer>> code: Will not work because you're overwriting container with a new value.
What you need to be doing there is something like the following instead: This also means that the containers must be initialized as arrays before passing them to the macro.
UPDATE: Of course, you're also redefining the findByID() expando method every time you call it, which is not a great idea. I'd suggest something like the following:
ALSO: @greyelf Honestly, findByID() is not a great name for that method. With the standard library taken into account, its name makes it sound like it returns the object, rather than whether it exists. I think that hasById() or existsById(), something like that, would be less confusing.
now, when I'm shopping and I'm calling inside a widget: where is this is how I call the widget I get this:
I'll go over this and see where things are going bad. I'm basically trying to generalize the application of this code for multiple containers without copying all the macros and applying them to different containers (that would be a disaster and bigger disaster to maintain).
It appears inside There's an issue with this:
this one yields an error, telling me that what i'm passing is not an array.
Update: I had to slap my forehead after finding the solution. It appears I was passing the arguments to the macro in a wrong way.
From SugarCube v2's macro documentation (right at the top): All of the inventory macros shown within this thread so far fall into the latter category—i.e. those that want discrete arguments separated by whitespace.
The following from your example: Yields three arguments to the macro: the current value of $player.inventory, a string containing a comma (","), and the current value of $args[0].
Try the following instead:
I'm trying to write code to equip items that I have in my inventory.
Now, I've written a JS function that is supposed to return an object from my inventory:
Now, this returns an undefined, which I can't do anything with. It's not even a -1 value.
This is the experimental code that I'm running the JS function in.
I'm trying to pass getItemById() to $item.
I know that I'm missing lots of checks to make the code bullet proof. It's just frustrating that I can't return an object and store it just like that.
Any help is welcome!
Update: Interestingly this works when I look up the object in SugarCube.
This begs the question:
Is it possible to return values from JS function or macro to SugarCube and continue working with it there? I don't like the solution that I got right now, cause it's ugly as hell, I'd rather have a JS macro that takes care of looking up values inside the array.
Yeah, I go that part fixed and understood. Thank you! But what about the code above? For some reason I always get returned_item undefined. To me, it appears that the third argument is not passed to the widget correctly. I'm passing an array, a ref and another ref. I'm trying to return an item and I can't get a hold of it. This is how I call the widget:
The player has a structure something like the following:
where hats, pants, shirts and shoes are basically pointing on an item in the inventory and these items have a value, isEquipped = false or true. It seems the widget approach has failed me, I can't get the item to be assigned to this slot and I can't set the isEquipped value.
Right, now I'm restructuring the whole thing and try to do it in JavaScript by passing the player and the item to macro and look up the type of the clothing (yeah, I introduced another property, these objects are just growing) and based on that assign it to the proper slot if it hasn't been assigned yet.
Also, in your <<linkreplace>> the following line: Should probably be comparing the object's IDs, rather than the objects directly. For example:
Thank you for fixing this mess!
Though is not working it appears that is undefined and doesn't have an property. I might be misunderstanding what this should do.
Update: Yep, this won't cut it:
"pants" : $OldTrousers
I need to do either this: or and it seems to work.
so basically the <<widget>> has no influence on the "outside" value of $player.clothing.pants.
My code:
Now I tried to use $clothingSlot or $args[2] and writing their values to see if anything changes in the outside value. This wasn't the case. I must be failing to understand some key concept here about SugarCube's concept of scope.
Normally, JS would apply the changes to the passed values. Here, the case is different.
Aha! That makes sense. Thank you! Can you also answer my question about scope in the case of <<widgets>>? Check my question above.
Update:
This JS code worked:
Now, the code is probably a piece of garbage and needs refactoring even maybe a complete rethinking, but it kinda works.
Is there a way to achieve the same using a <<widget>>?
Try: Which would be used thus:
There are probably ways to simply that a bit.
One of the main issues faced by anyone trying to explain how to implement a non-trivial inventory system is the fact that the design quickly become very specific to the needs of the person asking for that explanation.
Thank you! I was not aware of this. I mean, I understand the fundamental of copies and how they are different, but the fact that you needed to pass the object that held the slot, was something I have not thought of.
Alright, so, now we need 4 arguments for the widget. That escalated quickly.
I'll try this one out and see if its enough to finish the demo.
@greyelf : nah, it's not the design i'm planning on rolling with. I'm merely stretching my legs to write a demo, see if I understand how SugarCube works. TheMadExile and you sir have been very helful and I'm thankful for that.
This is going to be my first "real" Twine game, although not a full game, hehe.
Definition of my items in StoryInit:
Here's how I call the item from the passage called "Watch":
I'm suspecting that the word "watch" or "Watch" has a special meaning somewhere and it escapes the script, making it crash. I had a similar script crash when I had an item named "T-Shirt". Basically the same script could not run the <<if>> statement and could not find it's <</if>> and <<else>> keywords and I had to rename the object to make it work. Every other item worked only these two did not. So, I decided to rename the T-Shirt to Tshirt and everything went fine and dandy. Now, the question is, what is so special about "Watch"? The script is telling me that this item has no "id" property.
Any ideas?
As for the t-shirt. Assuming you tried naming the story variable something like $T-Shirt, then that's your problem, it's not a valid variable name.
SEE: SugarCube v2's variables documentation.
So, I tried to turn my getItemById() into an inner function of all of my containers:
Can anyone explain to me why is this happening?
I tried to implement an equip/unequip mechanic and i use the container.getItemById() variant which pointed on an object that wasn't actually inside the player's inventory, so but when I used the getItemById() variant, it worked as intended and changed the item that was inside the actual container and thus worked nicely.
I really wish to know what the differences are. I'm thinking that the member function (method) is grabbing something different, maybe a copy of the object.
That function shouldn't even be working unless you've defined a container variable someplace in-scope for it to capture. Even if you did, unless you're reassigning that container every turn, it's unlikely to be referring to what it should be and/or you think it is.
You should probably be passing the appropriate container in as a parameter. For example:
Correct usage of both in TwineScript:
Correct usage of both in pure JavaScript:
window.getItemById() does indeed have two arguments (my bad, for incorrect source)
Oh wow, yeah my mistake there. I have no idea how I missed the "this" keyword...
I don't mean to intrude, or even de-rail, but I've been following this topic closely as I'm nearing the point that I am going to need to start implementing an inventory system of my own. And I was curious what the advantages of, or rather why individual objects for each individual item?
For example, I have my npcs setup in the following way:
Which I can then reference as $npcs["players_mother"] which makes it easy when developing a story and helps avoid "variable creep". So to use your item declarations I would have used something like:
While still giving the player a $player.inventory container for them to be added to.
With this being just a rough example, but I am curious why one implementation over the other?
I also need to add that not everything about the inventory system (that I had in mind) is covered here. In my game, I'm using clones of the items that I have, since I like to keep unique instances in memory, so I can adjust my item's properties individually, without "corrupting" the properties of the rest of the items with the same id and name.
Now, if one of the item's have meaningful differences then I tend to assign a different ID to them dynamically. So this way if I have 3 "Silver Sword's" and I decide to enchant one of them, my "Silver Sword" after adding an echantemnt to it can become an "Enchanted Silver Sword", which has a higher price value and does more damage to certain enemy types. Now, I will have 2 "Silver Swords" in my inventory and 1 "Enchanted Silver Sword". This way I also prevent loosing the enchantment (and the enchantment properties) when moving items from a container over to the player's inventory or selling or buying items.
If you are not building RPG elements into your game, then this kind of inventory system might be an overkill, it is you the writer/designer/programmer who needs to decide in the design phase of your game/story what you want to go for.