Following on from what I said last week in the map editor thread, I'm going to go into more depth about when to define objects, and when instead to use a database.
Behavior vs Variation
In order to best understand when to define new object types, we need to understand what behavior and variation are. Behavior is best explained as anything that happens within a proc or verb (function), or when we change the basic properties or structure (members) that a type has. Variation is when we change variables.
For instance:
Creating a type:
turf
sometype
Specifying new behavior:
turf
sometype
Enter(atom/movable/o)
return 0
Also specifying new behavior:
turf
sometype
var
somevar = 0
variation:
turf
sometype
density = 1
Adding new procs, or new variables changes the structure of the type, and therefore, it is considered a change in the behavior of the type. It is not a change of behavior simply to change an existing variable. This is called variation, and results in duplicate nodes in the type tree.
It is sometimes useful to have the duplicate nodes for the sake of reference, but the vast majority of the time, it is not appropriate to have these duplicate nodes lying around in the tree, due to it making the project larger, the type tree more confusing, and it unnecessarily obfuscating the operations of your project.
Heirarchy, not a database, ya dingus!
You will see examples of something like this rampant throughout BYOND projects:
obj/items
newbiesword
attack = 4
defense = 2
bettersword
attack = 8
defense = 4
This approach does have some advantages over my approach, but I will continue to argue that it's wrong for a number of reasons:
1) If an item is removed from the game, loading savefiles with items of that type will begin to fail.
2) It unnecessarily ties the properties of items to specific instances.
3) It unnecessarily bloats the item tree.
Instead, let's introduce a new way of doing things.
First, my requirements are this:
1) Item prototypes should contain all variations for a specified item type.
2) Item prototypes should not need to be subclassed to be functional. They should automagically do their work without regard of what the item itself is.
3) The Item prototypes should be able to be saved and restored from savefiles, as well as defined in code in a single line.
item_prototype
var
id = 0
itemtype
list/properties = list()
proc
Instance(loc=null)
new itemtype(loc,id)
Inherit(obj/item/ref)
for(var/prop in properties)
ref.vars[prop] = properties[prop]
New(id,itemtype,list/properties)
src.id = id
src.itemtype = itemtype
src.properties = properties
That's really all that's required of the item prototype object. You will notice that this prototype object takes a number, a type path, and a list as arguments for initialization. This will come in handy later.
However, we also need to define an item object with the specified New() behavior too:
obj
item
var
id = null
New(loc=null,id=null)
..(loc)
if(id)
src.id = id
if(src.id)
InheritItem(src.id,src)
Read(savefile/F)
..(F)
InheritItem(id,src)
Okay, now we have some global stuff to define. We need to keep a database of items around, and the ID of the item needs to point to a unique item_prototype object in the global database.
var
list/item_database = list()
proc
InheritItem(id,obj/item/ref)
var/item_prototype/proto = item_database[id]
proto.Inherit(ref)
Now, let's take a look at how to define new items for your game!
#define ITEM_SWORD "item_sword"
#define ITEM_SPEAR "item_spear"
#define ITEM_SHIELD "item_shield"
#define ITEM_POTION "item_potion"
var
list/item_database = list(
ITEM_SWORD = new/item_prototype(ITEM_SWORD,/obj/item/equipment/weapon,list(weapon_type="sword",icon='sword.dmi',min_dmg=4,max_dmg=8)),
ITEM_SPEAR = new/item_prototype(ITEM_SPEAR,/obj/item/equipment/weapon,list(weapon_type="spear",icon='spear.dmi',min_dmg=6,max_dmg=12,hands=2),
ITEM_SHIELD = new/item_prototype(ITEM_SHIELD,/obj/item/equipment/shield,list(icon='shield.dmi',defense=10),
ITEM_POTION = new/item_prototype(ITEM_POTION,/obj/item/consumable/health_potion,list(icon='hp_pot.dmi',restore=25))
That's really all there is to it. Notice how we are only defining new types for the underlying behavior, and not one per item? Notice how sword and spear share the same behavior, and therefore share the same type?
There are additional pros to my approach:
1) This approach allows you to add new items in-game on the fly.
2) This approach uses #defines for handling item types. This allows more versatile object creation without costly path traversal operations.
3) This approach doesn't clutter the type tree.
In addition, in case you want the ability to create modified item stacks of a particular item type, I'll go ahead and include a version of the above example that does just that:
var
list/item_database = list()
proc
InheritItem(id,obj/item/ref)
var/item_prototype/proto = item_database[id]
proto.Inherit(ref)
item_prototype
var
id = 0
itemtype
list/properties = list()
proc
Instance(loc=null)
new itemtype(loc,id)
Inherit(obj/item/ref)
for(var/prop in properties)
ref.vars[prop] = properties[prop]
if(ref.modifications)
for(var/prop in ref.modifications)
ref.vars[prop] = ref.modifications[prop]
ref.modifications = null
New(id,itemtype,list/properties)
src.id = id
src.itemtype = itemtype
src.properties = properties
obj
item
var
id = null
tmp
list/modifications
New(loc=null,id=null,list/modifications=null)
..(loc)
if(modifications)
src.modifications = modifications
if(id)
src.id = id
if(src.id)
InheritItem(src.id,src)
Read(savefile/F)
..(F)
InheritItem(id,src)
The above example will allow you to create modified types on the map simply by editing the modifications list and typing in an id. It is also good for initializing modified types via code simply by using the new parameters. In addition, it will allow you to specify the properties that should be modified from the default. This means you can have items that match the prototype's id, but have a different icon, for instance.
Next week's Snippet sunday will actually be postponed until the following tuesday due to the BYOND game Jam. Please provide topic suggestions below!