ID:152080
 
If you've ever played a good Interactive Fiction game, you'll notice that there are a lot of instances where you can use an object on something that it was totally not intended to be used on, which will often result in a goofy response. At least if its a Sierra game.

For example, if you have a knife object, and the real purpose for the knife in the game is to give you a tool to free yourself from a net trap later on. However, you want to try some other things with it, since you don't know that its supposed to be used on the net trap yet.

So, we try to use the knife on the couch:
I don't think the home owner would appreciate that.

Okay, how about using the knife on an apple:
You consider dicing up the apple and eating it, but you're not all that hungry and you can probably find better uses for the apple.

What about throwing it at that guy across the street?
Be careful with that, you'll poke somebody's eye out!


Anyway, I'm just curious if anyone knows a spectacular way to design a system that supports multiple responses like this. I imagine people who've worked on Interactive Fiction games have it down to a science, but since IF isn't really very popular here at BYOND, I'm not familiar with any of those techniques.

Oh, and I'm not saying that I can't do it on my own, I'm just looking to see if there's a more refined way of doing it than anything I'd come up with on the spot.
Well you didn't say how you would do it, but in my game any object can be used on any atom and produce unique reactions.

Every atom is basically responsible for it's own response to an object that is used on it. The object being used does not need to know how the atom will react.
I'll probably go the <s>simple</s> stupid way:

obj
proc
Act(obj/O)
O.React(src)

React(obj/O)
// Stub.

knife
Act(obj/O)
switch(O.type)
if(/obj/couch) usr << "I don't think the home owner would appreciate that."
if(/obj/apple) usr << "You consider dicing up the apple and eating it, but\
you're not all that hungry and you can probably find better uses for the apple."

if(/obj/person) usr << "You throw the knife at [O.name]."
return ..() // Trigger reaction

couch
apple

person
name = "John"

React(obj/O)
if(istype(O,/obj/knife))
usr << "The knife pokes [name]'s eye!"
RemoveBodyPart(RIGHT_EYE) // :-p
For this, the functionality of hascall() and call() comes to mind; I like them very much in this case. This is the method I would personally use:
mob/proc/UseKnife(O)
if(O && hascall(O, "KnifeResponse"))
call(O, "KnifeResponse")(src)
return TRUE // Just something to say that something happened
else return FALSE // Nothing happened

couch
parent_type = /obj
proc/KnifeResponse(mob/M)
M << "I don't think the home owner would appreciate that."

apple
parent_type = /obj
proc/KnifeResponse(mob/M)
M << "You consider dicing up the apple and eating it, but you're not all that hungry and you can probably find better uses for the apple."

creepy_guy
parent_type = /mob
proc/KnifeResponse(mob/M)
M << "Be careful with that, you'll poke somebody's eye out!"


Of course, if you don't want to use hascall() and call(), you could just define some large-scale proc to cover the whole thing, though I find this method uglier (although more compile-safe):
mob/proc/UseKnife(atom/O)
if(istype(O))
return O.KnifeResponse(src)

atom/proc/KnifeResponse(mob/M)
return FALSE // Nothing happened

couch
parent_type = /obj
KnifeResponse(mob/M)
M << "I don't think the home owner would appreciate that."
return TRUE

apple
parent_type = /obj
KnifeResponse(mob/M)
M << "You consider dicing up the apple and eating it, but you're not all that hungry and you can probably find better uses for the apple."
return TRUE

creepy_guy
parent_type = /mob
KnifeResponse(mob/M)
M << "Be careful with that, you'll poke somebody's eye out!"
return TRUE


Either of these methods is more extensible than, say, hard-coding each response into some large switch() statement in the UseKnife() proc.

Hiead
I'd probably go with a system of generic responses, that also allow specific responses, based on the types of objects you expect to be in the game.

The apple and the knife scenario can be both a specific case, or a generic case depending on how you look at it:

Specific case: This Knife + This Apple
Generic case 1: Knife object + Apple object
Generic case 2: Sharp cutting object + Eatable food
Generic case 3: Sharp object + Food

This all depends on the level of detail you want, and the diversity you'll end up with. I'd experiment a bit and see what tends to net the nicest results.

If you're looking for an actual implementation on this, ask and I'll try and whip something up - I'm off to a birthday party now, though.

You can spice things up with sets of responses mixed in with generic cases, to provide a more broad scale of response (So every object you use to try and cut an apple won't trigger the same dull response). If you're worried about consistency and don't want to make it seem random, cache the random response you generated based on the two objects and search for that before generating a new random response. This has the added quirk of making responses unique each game.
If your game is going to include a reasonably limited variety of objects (say, 100 or fewer), your best bet might be to create a big table. The column labels would be types of effects you grant the various objects (which could include NPC's), e.g.:

damage: blunt
damage: edged
damage: piercing
damage: chemical
damage: heat
damage: fire
damage: cold
inspire: fear
inspire: affection
inspire: seduction
power: magnify
power: illuminate
power: wash

and so forth. The row labels would be the objects themselves.

Step 2: Having made the table, you put an X in every box where there should be some significant effect. (For example: having a "soap" object, you included a column for "power:wash". Combining that with the "car" object, you get a shiny car, which in turn acquires "power:seduction".) For fun you could also put an O in boxes where there is an atmospheric or humorous, but not significant, effect ("You wash the BYONDer. It is still a BYONDer."). Probably over 90% of the boxes will end up blank.

Step 3: Program the appropriate results for the non-blank combinations. The rest can be handled by default text ("It seems to make no difference" or whatever).