Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - Maeglin

Pages: [1]
1
Open Chat / Re: Quick and Easy MediaBox
« on: December 07, 2017, 09:07:47 PM »
That's odd - I've never had an issue with OpenELEC (not that "it works on my machine" is what you want to hear  :) )

My guess would be that there was some manner of failure in the copy of OpenELEC to your install media.

2
Promotions / Shameless plug
« on: December 04, 2017, 06:36:57 PM »
I've been posting a bit on here lately about the new lib I've been working on - http://lpmuds.net/smf/index.php?topic=1624.0

On the off chance that anyone who frequents this site is interested in joining in on all the fun - either as a content creator on Realms or to work on the core mud lib - please contact me.

3
Code Vault / New RealmsMUD core library
« on: December 04, 2017, 04:16:15 PM »
I'll start this with a caveat: I'm not yet prepared to officially release this. There are some things that are very much not completed that would make trying to run a mud with this less than ideal:
  • some rather useful player commands like the emote/soul stuff and (heh) help
  • some of the wiz commands (like a way to administrate the role-based access control and the more "mundane"/useless wiz commands (like ed ;) )
  • The player party system
  • The player's crafting interface
  • Non-equipment carry-able items (drinks, food, crafting materials)
  • Some of the crafting blueprints are not complete
  • I've identified a few lower priority defects that need to be fixed prior to release
  • I do not have a master.c or any sefun stuff included in this repo. I will, but the hacks I've done to get things to the point where I can wander around on a mud with this lib are just that - hacks. I need to follow proper procedures for these (since they're critical components!) and create automated tests for them.

So, while I'm happy to share what I have (and would certainly enjoy feedback), I would very much appreciate that none of this code make its way into another mud just yet and have updated my license to clearly state this. There are some areas that are still changing a lot and I absolutely do not want to support users yet since they would be trying to hit a rapidly moving target.

Anyway, those interested can look at the lib at:

http://scm.realmsmud.org

It is password protected (I don't have the ability to do anonymous access) and various web front-ends I found for git without just setting up my own instance of GitLab were rather lacking. The security-conscious person in me cringes at the thought of posting user auth stuff on a public forum, but if you send me a message here (or e-mail me: My user name here plus my realmsmud.org domain should give a pretty good clue as to my address.), I'll be happy to set you up.

4
General / Re: My Renewed Interest in LPMud stuff
« on: December 03, 2017, 04:13:29 PM »
Wow, what a blast from the past. I started playing RealmsMUD some time in 1994 not long after starting college. I remember your name from there. I also remember spending a lot of time running around as a Lich class. Most fun I've ever had with any class in any game. Ah, to have that sort of time to spare again....

That's cool. My roommate in college (Maelik) was the one who wrote the Lich guild.

5
General / Re: My Renewed Interest in LPMud stuff
« on: December 03, 2017, 04:11:15 PM »
p.s. I am really curious to know why you stuck with an old LP driver, though!  I have created a driver

Since I posted my original message, I updated to ldmud-3.5.0. For my new lib stuff, it wasn't too big a deal to accomplish - primarily changing calls to a few efuns and I did also have to implement an alternative to set_light (not a big deal since I eventually wanted to do something more sophisticated anyway.) I was also going to take a look at what it would take to go to DGD since its licensing is a bit more permissive, but have not yet done so.

The original reason I stuck with 3.2.17 was pure laziness - ie: I wanted to spend my efforts writing the "fun" stuff within the current Realms lib and I guessed (rightly, it turns out) that it would take a lot of work to go to 3.2.17 if I carried the existing Realms forward. I planned on writing a compat layer to translate all the pre-existing stuff in Realms to use the new lib. However, after having played with that mudlib again in earnest for almost a year, I'm much less excited about that because the code really is a mess and I'm not sure I want to spend all the mind-numbing hours making it work. As I did the update to 3.5 and had to severely hack them, it did become pretty apparent that I'm going to want to rewrite master and the sefuns because, well, yuck.

I'll definitely be curious to see how your driver dev goes and will have to check it out when you release it. My client is probably a couple years away from being "ready" (especially since I've been spending the bulk of my time on lib dev this year.)

6
Design Lab / Re: Testing in LPC
« on: November 26, 2017, 08:59:43 PM »
(continued)

This second one exercises the quest state machine. Note the last couple tests (QuestSucceededReturnsTrueWhenQuestCompletesAsSuccess, for example) as they use ancillary methods and fake objects that transition the quest to its various states:

Code: [Select]

//*****************************************************************************
// Copyright (c) 2017 - Allen Cummings, RealmsMUD, All rights reserved. See
//                      the accompanying LICENSE file for details.
//*****************************************************************************
inherit "/lib/tests/framework/testFixture.c";
#include "/lib/include/inventory.h"

object QuestItem;
object King;
object Quester;

/////////////////////////////////////////////////////////////////////////////
void SetUpQuestItem()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");

    QuestItem->testAddState("met the king",
        "I met King Tantor the Unclean of Thisplace. He seems to like me.");
    QuestItem->testAddTransition("meet the king", "met the king", "meetTheKing",
        "/lib/tests/support/quests/testKingObject.c");

    QuestItem->testAddState("serve the king",
        "The king asked me - ME - to be his personal manservant. Yay me!");
    QuestItem->testAddTransition("met the king", "serve the king", "serveTheKing");

    QuestItem->testAddState("ignore the king",
        "I told the king to piss off. I have socks to fold.");
    QuestItem->testAddTransition("met the king", "ignore the king", "ignoreTheKing");
    QuestItem->testAddEntryAction("ignore the king", "killTheKing");
    QuestItem->testAddFinalState("ignore the king", "failure");

    QuestItem->testAddState("save the king",
        "Earl the Grey tried to kill the king but I gutted him like a fish.");
    QuestItem->testAddTransition("serve the king", "save the king", "hailToTheKing");
    QuestItem->testAddFinalState("save the king", "success");

    QuestItem->testAddState("king is dead",
        "I must lay off the sauce - and the wenches. King Tantor is dead because of my night of debauchery.");
    QuestItem->testAddTransition("serve the king", "king is dead", "maybeNobodyWillNotice");
    QuestItem->testAddFinalState("king is dead", "failure");

    King = clone_object("/lib/tests/support/quests/testKingObject.c");
    QuestItem->testRegisterQuestActor(King);
    King->SetQuestItem(QuestItem);
    King->SetQuester(Quester);

    object room = clone_object("/lib/environment/room.c");
    move_object(King, room);

    QuestItem->testSetInitialState("meet the king");
    Quester->StartQuest(QuestItem);
}

/////////////////////////////////////////////////////////////////////////////
void Init()
{
    ignoreList += ({ "SetUpQuestItem" });
}

/////////////////////////////////////////////////////////////////////////////
void Setup()
{
    Quester = clone_object("/lib/tests/support/services/combatWithMockServices.c");
    Quester->ToggleMockQuests();

    QuestItem = clone_object("/lib/tests/support/quests/testQuestItem.c");
}

/////////////////////////////////////////////////////////////////////////////
void CleanUp()
{
    destruct(King);
    destruct(QuestItem);
    destruct(Quester);
}

/////////////////////////////////////////////////////////////////////////////
void SetNameThrowsWhenNameNotValid()
{
    string err = catch (QuestItem->setName(3));
    ExpectEq("*ERROR - questItem: the name must be a string.", err);
}

/////////////////////////////////////////////////////////////////////////////
void SetNameSetsTheNameOfTheQuestItem()
{
    ExpectFalse(QuestItem->name());
    QuestItem->setName("Test");
    ExpectEq("Test", QuestItem->name());
}

/////////////////////////////////////////////////////////////////////////////
void SetDescriptionThrowsWhenNameNotValid()
{
    string err = catch (QuestItem->setDescription(3));
    ExpectEq("*ERROR - questItem: the description must be a string.", err);
}

/////////////////////////////////////////////////////////////////////////////
void SetDescriptionSetsTheNameOfTheQuestItem()
{
    ExpectEq("", QuestItem->description());
    QuestItem->setDescription("Test");
    ExpectEq("Test", QuestItem->description());
}
/////////////////////////////////////////////////////////////////////////////
void AddStateThrowsWhenStateNotValid()
{
    string err = catch (QuestItem->testAddState());
    ExpectEq("*ERROR - stateMachine: the state could not be added.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddStateThrowsWhenDescriptionNotValid()
{
    string err = catch (QuestItem->testAddState("blah"));
    ExpectEq("*ERROR - stateMachine: the state could not be added.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddStateSilentlySucceedsWhenStateIsValid()
{
    // If anything were to go amiss, this would throw.
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
}

/////////////////////////////////////////////////////////////////////////////
void AddStateThrowsWhenAddingTheSameStateTwice()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
    string err = catch (QuestItem->testAddState("meet the king", "I've been asked to meet the king!"));
    ExpectEq("*ERROR - stateMachine: the 'meet the king' state has already been added.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddStateThrowsWhenAddingAnInvalidEntryEvent()
{
    string err = catch (QuestItem->testAddState("meet the king", "I've been asked to meet the king!", 25));
    ExpectEq("*ERROR - stateMachine: the entry event must be a string.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddStateSilentlySucceedsWhenEntryEventIsValid()
{
    // If anything were to go amiss, this would throw.
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!", "someEvent");
}

/////////////////////////////////////////////////////////////////////////////
void AddStateThrowsWhenAddingAnInvalidFinalStateResult()
{
    string err = catch (QuestItem->testAddState("meet the king", "I've been asked to meet the king!", "killTheKing", "blah"));
    ExpectEq("*ERROR - stateMachine: the final state result must be 'success' or 'failure'.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddStateSilentlySucceedsWhenFinalStateResultIsSuccess()
{
    // If anything were to go amiss, this would throw.
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!", "killTheKing", "success");
}

/////////////////////////////////////////////////////////////////////////////
void AddStateSilentlySucceedsWhenFinalStateResultIsFailure()
{
    // If anything were to go amiss, this would throw.
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!", "killTheKing", "failure");
}
/////////////////////////////////////////////////////////////////////////////
void InitialStateThrowsWhenStateNotPresent()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
    string err = catch (QuestItem->testSetInitialState("blah"));
    ExpectEq("*ERROR - stateMachine: the initial state must have been added first.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddEntryActionThrowsWhenStateNotPresent()
{
    string err = catch (QuestItem->testAddEntryAction("blah", "things"));
    ExpectEq("*ERROR - stateMachine: an entry action can only be added if both the state exists and the method to call has been implemented on this object.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddEntryActionThrowsWhenAddingAnInvalidEntryAction()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");

    string err = catch (QuestItem->testAddEntryAction("meet the king", "badMethod"));
    ExpectEq("*ERROR - stateMachine: an entry action can only be added if both the state exists and the method to call has been implemented on this object.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddEntryActionSilentlySucceedsWhenEverythingValidates()
{
    // If anything were to go amiss, this would throw.
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
    QuestItem->testAddEntryAction("meet the king", "killTheKing");
}

/////////////////////////////////////////////////////////////////////////////
void AddExitActionThrowsWhenStateNotPresent()
{
    string err = catch (QuestItem->testAddExitAction("blah", "things"));
    ExpectEq("*ERROR - stateMachine: an exit action can only be added if both the state exists and the method to call has been implemented on this object.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddExitActionThrowsWhenAddingAnInvalidEntryAction()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");

    string err = catch (QuestItem->testAddExitAction("meet the king", "badMethod"));
    ExpectEq("*ERROR - stateMachine: an exit action can only be added if both the state exists and the method to call has been implemented on this object.", err);
}

/////////////////////////////////////////////////////////////////////////////
void GetStateDescriptionReturnsDescriptionWhenStateExists()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
    ExpectEq("I've been asked to meet the king!", QuestItem->getStateDescription("meet the king"));
}

/////////////////////////////////////////////////////////////////////////////
void GetStateDescriptionReturnsNullWhenStateDoesNotExist()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
    ExpectFalse(QuestItem->getStateDescription("blah"));
}

/////////////////////////////////////////////////////////////////////////////
void AddExitActionSilentlySucceedsWhenEverythingValidates()
{
    // If anything were to go amiss, this would throw.
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
    QuestItem->testAddExitAction("meet the king", "killTheKing");
}

/////////////////////////////////////////////////////////////////////////////
void SetInitialStateSetsTheInitialState()
{
    // If anything were to go amiss, this would throw.
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
    QuestItem->testSetInitialState("meet the king");
    ExpectEq("meet the king", QuestItem->initialState());
}

/////////////////////////////////////////////////////////////////////////////
void AddTransitionThrowsWhenStatesDoNotExist()
{
    string err = catch (QuestItem->testAddTransition("a", "b", "someEvent"));
    ExpectEq("*ERROR - stateMachine: the transition could not be added.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddTransitionThrowsWhenFirstStateDoesNotExist()
{
    QuestItem->testAddState("b", "do b stuff");
    string err = catch (QuestItem->testAddTransition("a", "b", "someEvent"));
    ExpectEq("*ERROR - stateMachine: the transition could not be added.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddTransitionThrowsWhenSecondStateDoesNotExist()
{
    QuestItem->testAddState("a", "do a stuff");
    string err = catch (QuestItem->testAddTransition("a", "b", "someEvent"));
    ExpectEq("*ERROR - stateMachine: the transition could not be added.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddTransitionSilentlySucceedsWhenStatesValidate()
{
    QuestItem->testAddState("a", "do a stuff");
    QuestItem->testAddState("b", "do b stuff");
    QuestItem->testAddTransition("a", "b", "someEvent");
}

/////////////////////////////////////////////////////////////////////////////
void AddTransitionThrowsWhenEventHasAlreadyBeenAdded()
{
    QuestItem->testAddState("a", "do a stuff");
    QuestItem->testAddState("b", "do b stuff");
    QuestItem->testAddTransition("a", "b", "someEvent");

    QuestItem->testAddState("c", "do c stuff");
    string err = catch (QuestItem->testAddTransition("a", "c", "someEvent"));
    ExpectEq("*ERROR - stateMachine: a transition for that event already exists.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddTransitionThrowsWhenAnInvalidInitiatorIsPassed()
{
    QuestItem->testAddState("a", "do a stuff");
    QuestItem->testAddState("b", "do b stuff");

    string err = catch (QuestItem->testAddTransition("a", "b", "someEvent", "invalidInitiator"));
    ExpectEq("*ERROR - stateMachine: the transition initiator must be a valid program name.", err);
}

/////////////////////////////////////////////////////////////////////////////
void AddTransitionSilentlySucceedsWhenEverythingValidates()
{
    QuestItem->testAddState("a", "do a stuff");
    QuestItem->testAddState("b", "do b stuff");
    QuestItem->testAddTransition("a", "b", "someEvent", "/lib/realizations/living.c");
}

/////////////////////////////////////////////////////////////////////////////
void QuestStoryReturnsCorrectNarrativeForQuestStatesCompleted()
{
    SetUpQuestItem();
    ExpectEq("I've been asked to meet the king! I met King Tantor the Unclean of Thisplace. He seems to like me. The king asked me - ME - to be his personal manservant. Yay me! I told the king to piss off. I have socks to fold. [Failure]",
        QuestItem->questStory(({"meet the king", "met the king", "serve the king", "ignore the king"})));
}

/////////////////////////////////////////////////////////////////////////////
void CanTransitionQuestStates()
{
    SetUpQuestItem();

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
}

/////////////////////////////////////////////////////////////////////////////
void DoesNotTransitionIfNotInProperStateForEvent()
{
    SetUpQuestItem();

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));

    King->ConfuseTheForcesOfEvil();
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterEventsFireWhenTransitionOccurs()
{
    SetUpQuestItem();
    QuestItem->testAddEntryAction("met the king", "doEnterStuff");
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 0);

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 1);
    ExpectTrue(member(QuestItem->actionList(), "Enter stuff has been done.") == 0);
}

/////////////////////////////////////////////////////////////////////////////
void OnExitEventsFireWhenTransitionOccurs()
{
    SetUpQuestItem();
    QuestItem->testAddExitAction("meet the king", "doExitStuff");
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 0);

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 1);
    ExpectTrue(member(QuestItem->actionList(), "Exit stuff has been done.") == 0);
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterAndExitEventsFireInCorrectOrder()
{
    SetUpQuestItem();
    QuestItem->testAddExitAction("meet the king", "doExitStuff");
    QuestItem->testAddEntryAction("met the king", "doEnterStuff");

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 0);

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(sizeof(QuestItem->actionList()) == 2);
    ExpectTrue(member(QuestItem->actionList(), "Exit stuff has been done.") == 0);
    ExpectTrue(member(QuestItem->actionList(), "Enter stuff has been done.") == 1);
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterDoesNotFireEventWhenNoneSet()
{
    SetUpQuestItem();

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectFalse(King->checkNotification());

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectFalse(King->checkNotification());
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterFiresEventWhenItHasBeenSet()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");

    QuestItem->testAddState("met the king",
        "I met King Tantor the Unclean of Thisplace. He seems to like me.", "someEvent");
    QuestItem->testAddTransition("meet the king", "met the king", "meetTheKing");

    King = clone_object("/lib/tests/support/quests/testKingObject.c");
    object subscriber = clone_object("/lib/tests/support/quests/someEventHandler.c");
    King->registerEvent(subscriber);
    QuestItem->testRegisterQuestActor(King);
    King->SetQuestItem(QuestItem);
    King->SetQuester(Quester);

    object room = clone_object("/lib/environment/room.c");
    move_object(King, room);

    QuestItem->testSetInitialState("meet the king");
    Quester->StartQuest(QuestItem);
    ExpectTrue(QuestItem->beginQuest(Quester));

    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectFalse(subscriber->checkNotification());

    King->DoMeetTheKingStuff();
    ExpectEq("met the king", Quester->questState(program_name(QuestItem)));
    ExpectEq("someEvent", subscriber->checkNotification());
}

/////////////////////////////////////////////////////////////////////////////
void OnEnterFiresIfSetForInitialState()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!");
    QuestItem->testAddEntryAction("meet the king", "doEnterStuff");

    King = clone_object("/lib/tests/support/quests/testKingObject.c");
    QuestItem->testRegisterQuestActor(King);
    King->SetQuestItem(QuestItem);
    King->SetQuester(Quester);

    object room = clone_object("/lib/environment/room.c");
    move_object(King, room);

    QuestItem->testSetInitialState("meet the king");
    ExpectFalse(member(QuestItem->actionList(), "Enter stuff has been done.") == 0);
    Quester->StartQuest(QuestItem);

    ExpectTrue(QuestItem->beginQuest(Quester));
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(member(QuestItem->actionList(), "Enter stuff has been done.") == 0);
}

/////////////////////////////////////////////////////////////////////////////
void EntryEventFiresIfSetForInitialState()
{
    QuestItem->testAddState("meet the king", "I've been asked to meet the king!", "someEvent");

    King = clone_object("/lib/tests/support/quests/testKingObject.c");
    object subscriber = clone_object("/lib/tests/support/quests/someEventHandler.c");
    King->registerEvent(subscriber);
    QuestItem->testRegisterQuestActor(King);
    King->SetQuestItem(QuestItem);
    King->SetQuester(Quester);

    object room = clone_object("/lib/environment/room.c");
    move_object(King, room);

    QuestItem->testSetInitialState("meet the king");
    ExpectFalse(subscriber->checkNotification());
    Quester->StartQuest(QuestItem);

    ExpectTrue(QuestItem->beginQuest(Quester));
    ExpectEq("meet the king", Quester->questState(program_name(QuestItem)));
    ExpectEq("someEvent", subscriber->checkNotification());
}

/////////////////////////////////////////////////////////////////////////////
void CanBeginQuestReturnsTrueIfNoPrerequisitesSet()
{
    SetUpQuestItem();

    ExpectTrue(QuestItem->canBeginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void CanBeginQuestReturnsFalseIfPrerequisitesNotMet()
{
    SetUpQuestItem();
    QuestItem->testAddPrerequisite("long sword", (["type":"skill", "value" : 10]));

    ExpectFalse(QuestItem->canBeginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void CanBeginQuestReturnsTrueIfPrerequisitesMet()
{
    SetUpQuestItem();
    QuestItem->testAddPrerequisite("long sword", (["type":"skill", "value" : 10]));
    Quester->Str(20);
    Quester->addSkillPoints(100);
    Quester->advanceSkill("long sword", 10);
    ExpectTrue(QuestItem->canBeginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void BeginQuestReturnsFalseIfPrerequisitesNotMet()
{
    SetUpQuestItem();
    QuestItem->testAddPrerequisite("long sword", (["type":"skill", "value" : 10]));

    ExpectFalse(QuestItem->beginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void BeginQuestReturnsTrueIfPrerequisitesMet()
{
    SetUpQuestItem();
    QuestItem->testAddPrerequisite("long sword", (["type":"skill", "value" : 10]));
    Quester->Str(20);
    Quester->addSkillPoints(100);
    Quester->advanceSkill("long sword", 10);
    ExpectTrue(QuestItem->beginQuest(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void QuestInCompletionStateReturnsTrueWhenQuestCompletesAsFailure()
{
    SetUpQuestItem();

    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->DoMeetTheKingStuff();
    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->TimeToFoldSocks();
    ExpectEq("ignore the king", Quester->questState(program_name(QuestItem)));
    ExpectTrue(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));
}

/////////////////////////////////////////////////////////////////////////////
void QuestSucceededReturnsFalseWhenQuestCompletesAsFailure()
{
    SetUpQuestItem();

    ExpectFalse(QuestItem->questSucceeded(Quester));

    King->DoMeetTheKingStuff();
    King->TimeToFoldSocks();
    ExpectTrue(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));
    ExpectFalse(QuestItem->questSucceeded(Quester));
}

/////////////////////////////////////////////////////////////////////////////
void QuestInCompletionStateReturnsTrueWhenQuestCompletesAsSuccess()
{
    SetUpQuestItem();

    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->DoMeetTheKingStuff();
    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->SureIWillServe();
    ExpectFalse(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));

    King->ConfuseTheForcesOfEvil();
    ExpectTrue(QuestItem->questInCompletionState(Quester->questState(program_name(QuestItem))));
}

/////////////////////////////////////////////////////////////////////////////
void QuestSucceededReturnsTrueWhenQuestCompletesAsSuccess()
{
    SetUpQuestItem();

    ExpectFalse(QuestItem->questSucceeded(Quester));

    King->DoMeetTheKingStuff();
    ExpectFalse(QuestItem->questSucceeded(Quester));

    King->SureIWillServe();
    ExpectFalse(QuestItem->questSucceeded(Quester));

    King->ConfuseTheForcesOfEvil();
    ExpectTrue(QuestItem->questSucceeded(Quester));
}

Execution, then, will show the test executed and whether or not it passed. If it fails, it will report how it failed:

Code: [Select]
Compiling file: /lib/tests/items/shopSelectorTest.c
Testing lib/tests/items/shopSelectorTest
[  PASSED  ]  TopLevelMenuDisplaysCorrectly
[  PASSED  ]  DescriptionsAreDisplayed
[  PASSED  ]  SelectingPurchaseDisplaysBuyMenu
[  PASSED  ]  SelectingSellItemsDisplaysSellMenu
[  PASSED  ]  SelectingExitExitsSelector
Test executed: lib/tests/items/shopSelectorTest -> [  PASSED  ]
 -> built in 57 ms
Compiling file: /lib/tests/items/buyItemSelectorTest.c
Testing lib/tests/items/buyItemSelectorTest
[  PASSED  ]  TopLevelMenuDisplaysCorrectly
[  PASSED  ]  SelectingExitExitsTheMenu
[  PASSED  ]  DescribeItemDisplaysCorrectMessage
[  FAILED  ]  -> Actual: Buy Items - Select an item to buy:
-=-=-=-=-=-=-= Name =-=-=-=-=-=-=- Cost -=-=-= Item Details =-=-=-=-=-=-=-=-=-
[1]  - Bastard sword                650    Attack:  5, Damage: 12, Defense:  3
[2]  - Bastard sword of Good       5650    Attack:  5, Damage: 12, Defense:  3
[3]  - Broad sword of Good        20824    Attack:  4, Damage: 10, Defense:  5
[4]  - Broad sword                  500    Attack:  4, Damage: 10, Defense:  4
[5]  - Broad sword                 5500    Attack:  4, Damage: 10, Defense:  4
[6]  - Claymore                     750    Attack:  5, Damage: 16, Defense:  4
[7]  - Claymore of Energy         13674    Attack:  7, Damage: 18, Defense:  6
[8]  - Great sword of Energy      70312    Attack: 12, Damage: 22, Defense: 11
[9]  - Harpe of Poison            13375    Attack: 10, Damage: 14, Defense:  5
[10] - Katana of Air               3189    Attack: 11, Damage: 11, Defense:  3
[11] - Long sword                   450    Attack:  5, Damage: 10, Defense:  2
[12] - Long sword of Energy       12950    Attack:  5, Damage: 10, Defense:  2
[13] - Machete of Evil            12825    Attack:  4, Damage:  5, Defense:  2
[14] - Machete of Water           17825    Attack:  4, Damage:  5, Defense:  2
[15] - Sabre of Undead            26124    Attack:  7, Damage: 10, Defense:  6
[16] - Sabre of Acid              70724    Attack: 11, Damage: 14, Defense: 10
[17] - Scimitar of Water          17914    Attack:  6, Damage:  9, Defense:  2
[18] - Scimitar of Earth          34574    Attack:  7, Damage: 10, Defense:  4
[19] - Short sword                  350    Attack:  5, Damage:  6, Defense:  3
[20] - Short sword of Air          2839    Attack:  4, Damage:  5, Defense:  3
[21] - Return to previous menu
You must select a number from 1 to 21.
For details on a given choice, type 'describe X' (or '? X') where
X is the option about which you would like further details.
, Expected: You must select a number from 1 to 21
[  FAILED  ]  SelectSubMenuDisplaysBuyList
[  PASSED  ]  DescribeShowsItemDetails
[  PASSED  ]  PurchaseWithInsufficientFundsFails
[  PASSED  ]  PurchaseWithSufficientFundsSubtractsMoneyAndAddsItemToPlayerInventory
[  PASSED  ]  PurchaseOfPermanentItemDoesNotRemoveFromStore
[  PASSED  ]  PurchaseOfNonPermanentItemRemovesItFromStore
Test executed: lib/tests/items/buyItemSelectorTest -> [  FAILED  ]
 -> built in 527 ms

7
Design Lab / Testing in LPC
« on: November 26, 2017, 08:52:37 PM »
One of the (in my opinion) critical, but far too often overlooked aspects of software development lies in quality assurance - specifically, ensuring that that software you've written does what it's supposed to do and (even more importantly) when changes are made, things aren't inadvertently broken (ie: regression testing).

When I started rewriting the lib for RealmsMUD, one of the first things I did was implement a simple testing framework. Initially, I would run the tests manually whilst logged in, but it quickly became apparent that long-term, that was an untenable approach. I decided that it'd be nice to execute the tests outside of the running mud. Version 1 (which I'd suggest as a starting point for anyone who wants to try this) of this concept was to simply make use of preload_objects in the driver and call exit when this executed. Then, for any tests, I'd set up the init file with the list of everything I wanted to execute. The driver would load them (and in master.c, I changed the preload stuff to also call executeTests on anything that inherited my test fixture.). The one down side to this is that you will almost certainly have to give your driver's eval limit a hefty boost or it's going to puke if you have any meaningful number of tests in a single file.

I've since hacked the driver (well, my heavily hacked ldmud-3.2.17 driver) so that it can take a single file and evaluate it without having to fall to preload on master.c.

What I'm supplying should be looked at with the following caveat: I've only tested this on the ldmud 3.2.17 built in compat mode. Everything being done should be generic enough to work anywhere, but I make no promises.

Anyway, here's the test fixture:

Code: [Select]
//*****************************************************************************
// Copyright (c) 2017 - Allen Cummings, RealmsMUD, All rights reserved. See
//                      the accompanying LICENSE file for details.
//*****************************************************************************
#include <functionlist.h>

string Pass = "[  PASSED  ] ";
string Fail = "[  FAILED  ] ";

int CurrentTestPassed = 0;
int AnyFailure = 0;
string *ignoreList = ({ "__INIT", "Init", "Setup", "CleanUp" });

/////////////////////////////////////////////////////////////////////////////
public void Init()
{
}

/////////////////////////////////////////////////////////////////////////////
public void Setup()
{
}

/////////////////////////////////////////////////////////////////////////////
public void CleanUp()
{
}

/////////////////////////////////////////////////////////////////////////////
public int executeTests()
{
    Init();
    mixed *tests = functionlist(this_object(), RETURN_FUNCTION_NAME | NAME_INHERITED);
    tests -= ignoreList;

    debug_message(sprintf("\nTesting %s\n", file_name()));
    foreach(string test in tests)
    {
        Setup();
        CurrentTestPassed = 1;

        call_other(this_object(), test);
        debug_message(sprintf("%s %s\n", CurrentTestPassed ? Pass : Fail, test));
        CleanUp();
    }
    debug_message(sprintf("Test executed: %s -> %s\n", file_name(),
        AnyFailure ? Fail : Pass));

    return AnyFailure;
}

/////////////////////////////////////////////////////////////////////////////
public void validateExpect(mixed val1, mixed val2, string msg)
{
    if (!CurrentTestPassed)
    {
        AnyFailure = 1;
        debug_message(Fail + (stringp(msg) ? msg : "") + " -> Actual: " + val2 +
            ", Expected: " + val1 + "\n");
    }
}

/////////////////////////////////////////////////////////////////////////////
public int sortArray(mixed a, mixed b)
{
    string compA;
    string compB;

    if (mappingp(a) && mappingp(b))
    {
        compA = this_object()->convertDataToString(a);
        compB = this_object()->convertDataToString(b);
    }
    else
    {
        compA = to_string(a);
        compB = to_string(b);
    }

    return compA > compB;
}

/////////////////////////////////////////////////////////////////////////////
public string convertDataToString(mixed data)
{
    string ret = "";

    if (objectp(data))
    {
        ret += program_name(data);
    }
    else if (pointerp(data) && sizeof(data))
    {
        ret += "({ ";
        data = sort_array(data, "sortArray");
        foreach(mixed element in data)
        {
            ret += convertDataToString(element) + ", ";
        }
        ret += "})";
    }
    else if (mappingp(data))
    {
        ret += "([ ";
        mixed *indices = sort_array(m_indices(data), "sortArray");
        foreach(mixed index in indices)
        {
            ret += convertDataToString(index) + ": " + convertDataToString(data[index]) + ", ";
        }
        ret += "])";
    }
    else
    {
        ret += to_string(data);
    }
    return ret;
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectEq(mixed val1, mixed val2, string msg)
{
    string parsedVal1 = convertDataToString(val1);
    string parsedVal2 = convertDataToString(val2);

    CurrentTestPassed = parsedVal1 == parsedVal2;
    validateExpect(parsedVal1, parsedVal2, msg);
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectSubStringMatch(string val1, string val2, string msg)
{
    CurrentTestPassed = sizeof(regexp(({ val2 }), val1));
    validateExpect(val1, val2, msg);
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectNotEq(mixed val1, mixed val2, string msg)
{
    string parsedVal1 = convertDataToString(val1);
    string parsedVal2 = convertDataToString(val2);

    CurrentTestPassed = parsedVal1 != parsedVal2;
    validateExpect(parsedVal1, parsedVal2, msg);
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectTrue(mixed val1, string msg)
{
    CurrentTestPassed = val1;
    validateExpect("true", "false", msg);
}

/////////////////////////////////////////////////////////////////////////////
public varargs void ExpectFalse(mixed val1, string msg)
{
    CurrentTestPassed = !val1;
    validateExpect("false", "true", msg);
}

Building tests is pretty straightforward, but since it's caused some confusion with other wizzes on Realms, I figured I'd give an explanation of what you should do.
  • Try to avoid testing more than one thing in any given method and make sure you name you test methods in a way that it's immediately obvious what you're doing. Good: QuestIsInProgressReturnsFalseWhenQuestHasBeenCompleted, Bad: ThingDoesStuff, Worst: X
  • Make use of setup methods to keep your actual tests succinct. When you make tests, you need to set up a bunch of objects so that you can properly exercise them. If you need to clone a player, give them a sword, clone a monster, and make them fight, don't "muddy" the test with that - put it in a separate method and call it from the test.
  • You should have system tests that encompass interactions between many objects. You should also have unit tests that test a single thing in a vacuum. These unit tests should use fake/facade items "around them" to limit what you're testing and to simplify the test.

Here's a couple example tests. This first one exercises the quest module in the player:
Code: [Select]
//*****************************************************************************
// Copyright (c) 2017 - Allen Cummings, RealmsMUD, All rights reserved. See
//                      the accompanying LICENSE file for details.
//*****************************************************************************
inherit "/lib/tests/framework/testFixture.c";
#include "/lib/include/inventory.h"

object Quests;
object QuestItem;

/////////////////////////////////////////////////////////////////////////////
void Setup()
{
    Quests = clone_object("/lib/realizations/player");
    Quests->Name("Bob");

    QuestItem = clone_object("/lib/tests/support/quests/fakeQuestItem.c");
}

/////////////////////////////////////////////////////////////////////////////
void CleanUp()
{
    destruct(Quests);
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsInProgressReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->questIsInProgress("bad/quest.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsInProgressReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->questIsInProgress("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsInProgressReturnsTrueWhenQuestHasBeenStarted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->questIsInProgress("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsInProgressReturnsFalseWhenQuestHasBeenCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));

    // Progress the quest to completion
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));

    ExpectFalse(Quests->questIsInProgress("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->questIsActive("bad/quest.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsTrueWhenQuestHasBeenActivated()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsFalseWhenQuestHasBeenDeactivated()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsTrueWhenQuestHasBeenReactivated()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsActiveReturnsFalseWhenQuestHasBeenCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));

    // Progress the quest to completion
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));

    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsCompletedReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->questIsCompleted("bad/quest.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsCompletedReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->questIsCompleted("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsCompletedReturnsFalseWhenQuestHasBeenStartedButNotCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsCompleted("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestIsCompletedReturnsTrueWhenQuestHasBeenCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));

    // Progress the quest to completion
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));

    ExpectTrue(Quests->questIsCompleted("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestStateReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->questState("bad/quest.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestStateReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void QuestStateReturnsCorrectQuestState()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq("meet the king", Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));

    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectEq("met the king", Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));

    ExpectTrue(QuestItem->receiveEvent(Quests, "serveTheKing"));
    ExpectEq("serve the king", Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));

    ExpectTrue(QuestItem->receiveEvent(Quests, "maybeNobodyWillNotice"));
    ExpectEq("king is dead", Quests->questState("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void ActiveQuestsReturnsCorrectListOfQuests()
{
    ExpectEq(({}), Quests->activeQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c" }), Quests->activeQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/mockQuest.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/mockQuest.c" }),
        Quests->activeQuests());

    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/mockQuest.c" }), Quests->activeQuests());

    ExpectTrue(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/mockQuest.c" }),
        Quests->activeQuests());
}

/////////////////////////////////////////////////////////////////////////////
void CompletedQuestsReturnsCorrectListOfQuests()
{
    ExpectEq(({}), Quests->activeQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ }), Quests->completedQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/anotherQuest.c"));
    ExpectEq(({ "lib/tests/support/quests/anotherQuest.c" }), Quests->completedQuests());

    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/anotherQuest.c" }), Quests->completedQuests());

    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/anotherQuest.c" }),
        Quests->completedQuests());
}

/////////////////////////////////////////////////////////////////////////////
void QuestsInProgressReturnsCorrectListOfQuests()
{
    ExpectEq(({}), Quests->activeQuests());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c" }), Quests->questsInProgress());

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/mockQuest.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/mockQuest.c" }),
        Quests->questsInProgress());

    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(({ "lib/tests/support/quests/fakeQuestItem.c", "lib/tests/support/quests/mockQuest.c" }),
        Quests->questsInProgress());

    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectEq(({ "lib/tests/support/quests/mockQuest.c" }), Quests->questsInProgress());
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->activateQuest("bad/quest.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestReturnsTrueWhenQuestHasBeenStarted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"), "begun quest is active");
    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"), "quest deactivated");
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c", "deactivated quest returns not active"));
    ExpectTrue(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c", "activate the quest"));
    ExpectTrue(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"), "re-activated quest is active");
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestReturnsFalseWhenQuestHasBeenCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));

    ExpectFalse(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void DeactivateQuestReturnsFalseWhenQuestIsInvalid()
{
    ExpectFalse(Quests->activateQuest("bad/quest.c"));
    ExpectFalse(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void DeactivateQuestReturnsFalseWhenQuestIsNotStarted()
{
    ExpectFalse(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void DeactivateQuestReturnsTrueWhenQuestHasBeenStarted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"), "begun quest is active");
    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"), "quest deactivated");
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c", "deactivated quest returns not active"));
}

/////////////////////////////////////////////////////////////////////////////
void DeactivateQuestReturnsFalseWhenQuestHasBeenCompleted()
{
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));

    ExpectFalse(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(Quests->questIsActive("lib/tests/support/quests/fakeQuestItem.c"));
}

/////////////////////////////////////////////////////////////////////////////
void BeginQuestFiresOnQuestStartedEvent()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectEq(([ "onQuestStarted":"lib/tests/support/quests/fakeQuestItem.c"]),
        events->quests());
}

/////////////////////////////////////////////////////////////////////////////
void BeginQuestFiresProperEventsWhenInitialStateIsCompletionState()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);
    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/anotherQuest.c"));
    ExpectEq((["onQuestStarted":"lib/tests/support/quests/anotherQuest.c",
        "onQuestCompleted": "lib/tests/support/quests/anotherQuest.c",
        "onQuestSucceeded": "lib/tests/support/quests/anotherQuest.c"]),
        events->quests());
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestStateFiresEachTimeStateAdvances()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestAdvancedState"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(member(events->quests(), "onQuestAdvancedState"));

    events->clearEvents();
    ExpectFalse(member(events->quests(), "onQuestAdvancedState"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectTrue(member(events->quests(), "onQuestAdvancedState"));
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestToFailStateFiresOnQuestCompletedAndOnQuestFailed()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestCompleted"));
    ExpectFalse(member(events->quests(), "onQuestFailed"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectTrue(member(events->quests(), "onQuestCompleted"));
    ExpectTrue(member(events->quests(), "onQuestFailed"));
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestToFailStateDoesNotFireOnQuestSucceeded()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestSucceeded"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "ignoreTheKing"));
    ExpectFalse(member(events->quests(), "onQuestSucceeded"));
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestToSuccessStateFiresOnQuestCompletedAndOnQuestSucceeded()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestCompleted"));
    ExpectFalse(member(events->quests(), "onQuestSucceeded"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "serveTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "hailToTheKing"));
    ExpectTrue(member(events->quests(), "onQuestCompleted"));
    ExpectTrue(member(events->quests(), "onQuestSucceeded"));
}

/////////////////////////////////////////////////////////////////////////////
void AdvanceQuestToSuccessStateDoesNotFireOnQuestFailed()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestFailed"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "meetTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "serveTheKing"));
    ExpectTrue(QuestItem->receiveEvent(Quests, "hailToTheKing"));
    ExpectFalse(member(events->quests(), "onQuestFailed"));
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestFiresOnQuestActivatedWhenItSucceeds()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestActivated"));
    ExpectTrue(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(member(events->quests(), "onQuestActivated"));
}

/////////////////////////////////////////////////////////////////////////////
void ActivateQuestDoesNotFireOnQuestActivatedWhenItFails()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectFalse(member(events->quests(), "onQuestActivated"));
    ExpectFalse(Quests->activateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestActivated"));
}

/////////////////////////////////////////////////////////////////////////////
void DeactivateQuestFiresOnQuestActivatedWhenItSucceeds()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectTrue(Quests->beginQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestDeactivated"));
    ExpectTrue(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectTrue(member(events->quests(), "onQuestDeactivated"));
}

/////////////////////////////////////////////////////////////////////////////
void DeactivateQuestDoesNotFireOnQuestActivatedWhenItFails()
{
    object events = clone_object("/lib/tests/support/events/questEventsSubscriber.c");
    Quests->registerEvent(events);

    ExpectFalse(member(events->quests(), "onQuestDeactivated"));
    ExpectFalse(Quests->deactivateQuest("lib/tests/support/quests/fakeQuestItem.c"));
    ExpectFalse(member(events->quests(), "onQuestDeactivated"));
}

EDIT: It appears that I'm too verbose and my post was concatenated... doh!

8
General / Re: My Renewed Interest in LPMud stuff
« on: November 26, 2017, 07:07:18 PM »
I did a bit of digging and it looks like my choice of VSTS was kinda terrible as far as providing public read access goes. I can add an authenticated read-only user that's "anonymous", but there's no support for truly anonymous (ie: unauthenticated) access. I can do it via Jenkins, but the code browser through Jenkins kinda sucks.

I'll have to come up with a solution that doesn't involve me ditching the infrastructure I set up.

9
General / My Renewed Interest in LPMud stuff
« on: November 22, 2017, 12:00:29 AM »
First, a disclaimer: Way back in 1991 when I was in college, I started playing RealmsMUD. Soon after, I became a wiz, made an area, quickly shot up to "god", rewrote the core lib, graduated and found other pursuits. By about 20 years ago, I vanished from the scene. I've been a software engineer/architect for the last 22 years... I say this because:

In 2011, the mud was finally about to die. I came back on the scene and set it up to run on one of my servers. I decided that it'd be a fun project to modernize it (after all, too much of the lib was that horrible stuff I wrote before I knew what "responsible development" meant.) A bunch of the other wizards volunteered to help but never did. I wasn't surprised when everything failed, though I was a bit disappointed - I decided to not waste my time continuing to work on Realms.

In 2014, I began working on a Unity3D-based game based in a world I'd created. I spent the next couple years doing a small amount of development whilst herding voice actors and artists. Over the course of the next three years, I got a large portion of the roughly 800,000 lines of dialog I'd written acted out. I also decided to pay some artists for some amazing 3D assets. The easier parts of the game were done and "the world" was largely peopled. I'd resisted the urge to make it have any type of online presence but then at the beginning of this year, I decided to go ahead and do that.

But how?

I came back to the LPMud driver (no laughing - I specifically chose LDMUD 3.2.17 because that's what Realms still runs and I had this weird fantasy about being able to reuse the many tens of thousands of rooms, countless monsters, and all the other stuff that had been created over the years.) There were a lot of shortcomings about using an LPMUD backend, but as everyone knows, it also has a ton of really useful stuff - from the technical side, it already has all the interesting network interaction stuff and is a pretty decent virtual machine for the "programs" it runs (players, monsters, rooms, items, etc). I began to hack the driver to suit my purposes and decided, "what the heck..."

I won't go into all of the details about what I implemented in the lib, but some of the more interesting things I did include:
  • I wanted to practice sensible engineering practices. I set up a VSTS site (Visual Studio Team Services) to both use for source control (it's git under the hood) and project management (even as a team of one, it's useful to track features and their implementation progress). I also did a few minor hacks to the driver such that I could run it as a "compiler" instead of a driver. That way, I was able to set up a Jenkins server to do automated builds / execute and report a full suite of tests written for the driver.
  • To that end, I created a gtest-like testing framework for LPC.
  • I haven't kept up with what others are doing in the LP world, but I identified some core lib things that I needed - event handling and a UML state machine implementation, for example - and wrote them.
  • I wanted to be able to build detailed quests/stories akin to what one might find in games like the Witcher series, Dragon Age,
     Mass Effect, etc. This includes things like complex, state-based quests and conversation trees (without guessing what to 'say'),
     research (think Civ-style but for various crafting, player background, guild, etc stuff), Crusader Kings-style (well, sort of) character traits.
  • I implemented an ASN.1 lib for the driver (because, why not). I'm using it to transmit driver data to the 3D client I'm working on. The long-term vision is that people can either use telnet (or whatever text-based client they want) OR the 3D one.
  • To that end, I changed the way that rooms are created. Instead of writing descriptions, you add objects to the environment. For those who have used any of the game editors that came with Neverwinter Nights, the Bethesda games, and others - it's like that. In text-mode, this gets translated via a "mad libs" style message parser I created and converted into a description. For the other mode, the objects/positions get sent raw.

The down side is that I added a ton of executional complexity. The act of a player hitting a monster can now trickle down into a few dozen cascading function calls... Since the hardware it's running on is pretty good (32 cores, 128G RAM) and it's the only thing running on it, the only real concern is the limitations of the 3.2.17 driver. I suppose I'll find those when I find them.

So, what was my point in posting this? Well, I wanted to let people know what I'm up to because I think (not to be immodest) that I've done a few pretty cool things. When I do release everything, the license should be pretty easy to follow (ie: do whatever you want with it, just give me credit).

Assuming there's interest, I'm perfectly happy to do some show-and-tell - this just didn't seem the appropriate time or post location to do so... and if anyone wants to dive in and create some stuff with it, all the better (though that might require doing so on a RealmsMUD instance I stood up - I haven't tried to detangle to be portable to other drivers as I did add some simulated efuns and a couple real ones (primarily around git and zfs support).

Anyway, the images I've attached are a successful and a failed build on the Jenkins server.

Pages: [1]