My game has spells that you can cast (either alone as >cast foospell, or onto something as >cast foospell on object). If you try to cast an object that isn’t a spell, the game converts the input to ThrowAt (for inputs like >cast bread upon the waters).
The code works very well for spells that have unique names (i.e., there are no other objects in the game that use their vocabWords). I can:
foospell
cast foospell
cast foospell on object
cast foospell spell
cast foospell spell on object.
However, I have run into trouble in the following situation.
I have an alpaca, an alpaca spell, and an alpaca pen.
“cast alpaca” results in “What do you want to throw it at?”
“cast alpaca on object” results in "The alpaca won’t let you do that.
“alpaca” by itself, “cast alpaca spell” and “cast alpaca spell on object” work OK.
What is happening with “cast alpaca” is that gTopic.getBestMatch is finding all three alpaca objects in the following order
- th_alpaca (the actual animal)
- tp_alpaca_spell (the spell)
- rm_alpaca_pen (the room)
Because the actual animal appears first on the inScopeList, the game skips over the spell stuff and executes ThrowAt.
I can avoid this altogether by changing the vocabWords of the spell in the code below from
vocabWords = ‘(magic) alpaca spell’
to
vocabWords = ‘(magic) alpaca/spell’
Which converts alpaca from an adjective to a noun, and makes all the inputs work correctly. However, this kills the input “cast alpaca spell” which is an input that I want to be valid.
So it seems to me that the answer is to boost tp_alpaca_spell to the top of the inScopeList, but all my ham-fisted efforts to do so have failed.
Help along any lines would be appreciated.
Here is the code…
class SpellIAction: IAction
;
class SpellTAction: TAction
;
VerbRule(Cast)
[badness 500] // This prevents the parser from converting >cast frob on futon (while on the futon) to Cast, instead of CastOn.
'cast' singleTopic
: CastAction
verbPhrase = 'cast/casting (what)'
;
VerbRule(CastOn)
('cast') singleTopic ('on' | 'at') singleDobj // Note singleTopic (for scope reasons) and singleDobj instead of singleIobj
// See "Look Up" example as a parallel in Tech Manual under "Verbs with one object plus a topic"
: CastOnAction
verbPhrase = 'cast/casting (what) (on what)'
;
class Spell: Topic
/* Default if player types >cast spell. */
castAction () { "You must specify which spell you want to cast. "; }
/* Default if player types >cast spell on foo. */
castOnAction () { "You must specify which spell you want to cast. "; }
;
DefineTopicAction(Cast)
execAction()
{
local spell = gTopic.getBestMatch();
local obj = gTopic.getBestMatch(); // the same as the 'spell' local, but easier to read in code
local wrd = gTopic.getTopicText(); // Get the literal text that the player typed after the word >cast
if (wrd == 'spell' // Player typed >cast spell instead of cast {specific} spell
|| wrd == 'a spell'
|| wrd == 'the spell'
|| wrd == 'magic'
|| wrd == 'magic spell'
|| wrd == 'a magic spell'
|| wrd == 'the magic spell'
|| wrd == 'any spell') {
"You must specify which spell you want to cast. ";
exit;
}
else if (spell != nil && spell.ofKind(Spell)) {
spell.castAction(); //If it's a legitimate spell, cast it
}
// else if (obj == obj.ofKind(SomeTopicWeHaven'tDefinedYet, like Love)) TODO
// reportFailure('That\'s not a spell. ');
else if (obj != nil) {
replaceAction (Throw, obj); //If it's an object, throw it
}
else {
reportFailure('That\'s not a spell. '); //If we don't recognize it, say so.
}
}
;
DefineTopicTAction(CastOn, DirectObject) // Note use of DirectObject here because of TopicTAction
execAction()
{
local spell = gTopic.getBestMatch();
local obj = gTopic.getBestMatch(); // the same as the 'spell' local, but easier to read in code
local wrd = gTopic.getTopicText(); // Get the literal text that the player typed after the word >cast
if (wrd == 'spell' // Player typed >cast spell on foo, instead of cast {specific} spell on foo
|| wrd == 'a spell'
|| wrd == 'the spell'
|| wrd == 'magic'
|| wrd == 'magic spell'
|| wrd == 'a magic spell'
|| wrd == 'the magic spell'
|| wrd == 'any spell') {
"You must specify which spell you want to cast. ";
exit;
}
if (spell != nil && spell.ofKind(Spell)) {
spell.castOnAction();
}
// else if (obj == obj.ofKind(SomeTopicWeHaven'tDefinedYet, like Love)) TODO
// reportFailure('That\'s not a spell. ');
else if (obj != nil) { // If it's an object, throw it at the original iobj
replaceAction (ThrowAt, obj, gDobj);
}
else {
reportFailure('That\'s not a spell. ');
}
}
;
modify Thing
dobjFor(CastOn)
{
preCond = [objVisible] // Assumes we need to see a thing to cast a spell on it. If not, this preCond isn't needed
verify() { }
check()
{
/* if the topic in this command doesn't match any game object at all, we can't proceed with this action. */
if(gTopic.getBestMatch() == nil)
failCheck(unknownSpellMsg);
}
action()
{
local obj = gTopic.getBestMatch();
/*Note that if we've got this far, we know that obj is not nil (otherwise the action would have been stopped in
check()). So we can next test whether it's a Thing; if so we want to convert the action to THROW obj AT self */
if(obj.ofKind(Thing))
replaceAction(ThrowAt, obj, self);
/* Next test if obj is a Spell; if so, cast it */
if(obj.ofKind(Spell))
obj.castOnAction(self); // might be worth passing self to the castOnAction method. TODO. RAB, That's an Eric Comment. What does it mean?
/* Otherwise obj is presumably a Topic that's not a spell */
else //TODO. Do we need a check for a topic here? or is that not necessary because we are in thing.
reportFailure(unknownSpellMsg);
}
}
unknownSpellMsg = 'That\'s not a spell. '
;
/************************
ALPACA SPELL
************************/
tp_alpaca_spell: Spell
{
name = 'alpaca spell'
vocabWords = '(magic) alpaca spell'
spellLearned = nil
castAction () {
replaceAction (Alpaca);
}
castOnAction () {
if (!tp_alpaca_spell.spellLearned) {
"That spell won't work unless it is in your spellbook. ";
exit; // Stops here
}
else {
replaceAction (AlpacaOn, gDobj);
}
}
}
DefineSpellIAction(Alpaca)
execAction()
{
if (!tp_alpaca_spell.spellLearned) {
"That spell won't work unless it is in your spellbook. ";
exit; // Stops here
}
else {
"A ball of yarn suddenly pops into existence at your feet (Or, at least it will one day. At which time
we will also make sure that it only happens once). ";
}
}
;
VerbRule(Alpaca)
'alpaca'
: AlpacaAction
verbPhrase = 'summon/summoning'
;
/************************
ALPACA-ON SPELL
************************/
DefineSpellTAction(AlpacaOn);
VerbRule(AlpacaOn)
('alpaca' ) singleDobj
: AlpacaOnAction
verbPhrase = 'summon/summoning (what)'
;
modify Thing
dobjFor(AlpacaOn) {
action () {
if (!tp_alpaca_spell.spellLearned) {
"That spell won't work unless it is in your spellbook. ";
}
else {
"You can't cast alpaca on things. ";
}
}
}
;
/***************************************
THINGS
****************************************/
th_alpaca: UntakeableActor
{
name = 'alpaca'
vocabWords = 'alpaca'
location = rm_alpaca_pen
dobjFor(Examine) {
action() {
say('It\'s woolly. ');
}
}
}