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.


Topics - Maeglin

Pages: [1]
1
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.

2
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.

3
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!

4
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]