/*
 * Amiga MUD
 *
 * Copyright (c) 1993 by Chris Gray
 */

/*
 * machines.m - code to add two simple machines to the world.
 *	NOTE: rooms referenced directly:
 *		r_mallEntrance - 3 times for the entry-exit recorder
 *		r_crossSW - starting point for Caretaker
 *		r_pearField - used in packratStep to force pear picking
 *		r_esEnd - starting point for Packrat
 *	We also reference directly: p_oPlayAction, p_oEraseAction
 *	We also require DoGet and DoDrop from verbs.m
 */

use t_streets

private tp_machines CreateTable().
use tp_machines

/*****************************************************************************\
*									      *
*   NOTE: This entry-exit recorder should be commented out if the MUD is      *
*	running on a floppy disk. Since it continually modifies its record    *
*	when the automatic machines go by, it is continually creating	      *
*	database entries that must be written out, hence there will be	      *
*	continual disk I/O even if no-one is playing. A big database cache    *
*	can minimize this effect, but will not stop it completely.	      *
*									      *
\*****************************************************************************/

/* first, stuff in the entry-exit recorder (not a true machine) */

define tp_machines o_recorder CreateThing(nil).
SetThingStatus(o_recorder, ts_readonly).
SetupObject(o_recorder, r_mallEntrance,
    "machine,player,recorder;enter-exit.machine,player,recorder;enter,exit,"
	"enter-exit.exit;enter",
    "This is a very strange machine. It's purpose, as indicated by a small "
    "instruction label, is to record the comings and goings of people. "
    "It will respond to 'play' and 'erase' to play its current record "
    "and to erase it, respectively.").
define tp_machines p_recorderRecord CreateStringProp().
o_recorder@p_recorderRecord := "".

/*
 * recorderEnter - the recorder's player enter action.
 */

define tp_machines proc recorderEnter()status:
    string s;

    s := o_recorder@p_recorderRecord;
    if Length(s) >= 3500 then
	/* we are obviously pretty busy, and getting close to the current
	   arbitrary string maximum of 4K, so just nuke the record. */
	s := "";
    else
	s := s + "    " + FormatName(Me()@p_pName) + " arrived.\n";
    fi;
    o_recorder@p_recorderRecord := s;
    continue
corp;

/*
 * recorderLeave - the recorder's player leave action.
 */

define tp_machines proc recorderLeave()status:
    string s;

    s := o_recorder@p_recorderRecord;
    if Length(s) >= 3500 then
	 s := "";
    else
	s := s + "    " + FormatName(Me()@p_pName) + " left.\n";
    fi;
    o_recorder@p_recorderRecord := s;
    continue
corp;

/* prevent players from picking up the recorder */

define tp_machines proc recorderGet(thing th)status:
    Print("Sorry, but the machine is bolted to the floor!\n");
    if not Me()@p_pHidden then
	OPrint(FormatName(Me()@p_pName) +
	       " tries to pick up the enter-exit machine.\n");
    fi;
    fail
corp;

/* play/erase functions */

define tp_machines proc recorderPlay()status:
    string s;

    s := o_recorder@p_recorderRecord;
    if s = "" then
	Print("The machine's record is empty.\n");
    else
	Print("The machine's record contains:\n" + s);
    fi;
    if not Me()@p_pHidden then
	OPrint(FormatName(Me()@p_pName) + " fiddles with the machine.\n");
    fi;
    succeed
corp;

/* recorderErase is in tp_misc since it is needed by the Postman */
define tp_misc proc recorderErase()status:
    string s;

    s := FormatName(Me()@p_pName);
    o_recorder@p_recorderRecord := "    " + s + " erased this record.\n";
    Print("OK, the machine's record is erased.\n");
    if not Me()@p_pHidden then
	OPrint(s + " fiddles with the machine.\n");
    fi;
    succeed
corp;

/* attach the functions to the room the recorder is in */

AddAnyEnterChecker(r_mallEntrance, recorderEnter, false).
AddAnyLeaveChecker(r_mallEntrance, recorderLeave, false).

/* and to the recorder itself */

o_recorder@p_oGetChecker := recorderGet.
o_recorder@p_oPlayChecker := recorderPlay.
o_recorder@p_oEraseChecker := recorderErase.

/* Exercise for the reader: remove the limitation of the recorder not
   being portable. Do it without leaving a lot of properties hanging around
   that aren't being used. */



/* Now the true machines */


define tp_machines p_mLastDir CreateIntProp().
define tp_machines p_mWantDelay CreateBoolProp().
define tp_machines p_mArrived CreateBoolProp().
define tp_machines p_mCommand CreateStringProp().
define tp_machines p_mWho CreateStringProp().


/* First, Caretaker */

define tp_machines Caretaker CreateThing(nil).

/* caretakerGreet - let the caretaker greet everyone in a room he enters. */

define tp_machines proc caretakerGreet(thing who)void:
    string name;

    name := who@p_pName;
    if name ~= "Caretaker" then
	DoSay("Good day " + FormatName(name) + "!");
    fi;
corp;

/* caretakerStep - the central action routine for the caretaker */

define tp_machines proc caretakerStep()void:
    thing here, me, object, home;
    list thing lt;
    int dir, firstNum, count, n;
    bool found, lost;

    if not ClientsActive() then
	/* no clients (humans) are playing - do little */
	After(60, caretakerStep);
    else
	me := Me();
	if me@p_mArrived then
	    /* this phase - greet everyone */
	    me@p_mArrived := false;
	    ForEachAgent(Here(), caretakerGreet);
	    After(20, caretakerStep);
	else
	    /* this phase - move */
	    me@p_mArrived := true;
	    firstNum := me@p_mLastDir;
	    if Random(3) ~= 0 then
		dir := firstNum;
	    else
		firstNum := DirBack(firstNum);
		while
		    dir := Random(12);
		    dir = firstNum
		do
		od;
		firstNum := dir;
	    fi;
	    found := false;
	    lost := false;
	    while not found do
		if TryToMove(dir) then
		    found := true;
		else
		    dir := (dir + 1) % 12;
		    if dir = firstNum then
			lost := true;
			found := true;
		    fi;
		fi;
	    od;
	    if lost then
		Log("Caretaker stuck, " + Here()@p_rName + "\n");
		Say("I'm stuck! Heeeeelp!!");
		After(600, caretakerStep);
	    else
		me@p_mLastDir := dir;
		MachineMove(dir);
		here := Here();

		/* Go through the objects in the room we just entered. Pick
		   each normal object up. If the object is not where it
		   "belongs", then keep it, otherwise drop it right away. */
		lt := here@p_rContents;
		count := Count(lt);
		n := 0;
		while n < count do
		    object := lt[n];
		    if not object@p_oInvisible and not object@p_oNotGettable
		    then
			/* DoGet removes an object from the list */
			count := count - 1;
			if not DoGet(here, me, object) then
			    /* something happened - abort out */
			    n := count;
			else
			    home := object@p_oHome;
			    if home = here or home = nil then
				/* object belongs here - drop it right away */
				if not DoDrop(here, me, object) then
				    /* something happened - abort out */
				    n := count;
				fi;
				/* otherwise, object is put on the end of the
				   contents list, and we do not need to look at
				   it again. */
			    fi;
			    /* otherwise, he just keeps it for now */
			fi;
		    else
			n := n + 1;
		    fi;
		od;

		/* Go through the list of stuff he is carrying. If anything
		   in that list "belongs" here, then drop it. */
		lt := me@p_pCarrying;
		count := Count(lt);
		n := 0;
		while n < count do
		    object := lt[n];
		    if object@p_oHome = here then
			count := count - 1;
			if not DoDrop(here, me, object) then
			    n := count;
			fi;
		    else
			n := n + 1;
		    fi;
		od;

		After(20, caretakerStep);
	    fi;
	fi;
    fi;
corp;

/* have the caretaker do the command we want him to do */

define tp_machines proc caretakerCommand()void:
    thing me;
    string command;

    me := Me();
    command := me@p_mCommand;
    me -- p_mCommand;
    ignore Parse(G, command);
corp;

/* the caretaker's "understanding" of things told to him */

define tp_machines proc caretakerTold()void:
    thing me;
    string w, np;

    me := Me();
    w := GetTail();
    SetTail(w);
    w := GetWord();
    if w == "drop" or w == "put" or w == "p" then
	/* someone is telling the caretaker to drop something */
	w := GetTail();
	np := GetNounPhrase(G, w, 0);
	if np = "" or FindName(me@p_pCarrying, p_oName, np) ~= succeed then
	    /* he is not carrying something matching what they want him to
	       drop */
	    if MatchName("flashlight.light;flash", np) >= 0 then
		/* there really isn't any such thing */
		DoSay("No way! It's mine!");
	    else
		OPrint("Caretaker shakes his head in confusion.\n");
	    fi;
	else
	    /* He is carrying something that matches. Say 'Certainly' and
	       try to drop it after 1 second. */
	    DoSay("Certainly.");
	    me@p_mCommand := "drop " + w;
	    After(1, caretakerCommand);
	fi;
    else
	/* Dropping things is the only thing he will do */
	DoSay("Sorry, I've got my work to see to.");
    fi;
corp;

define tp_machines proc caretakerHear(string what)void:
    string word;

    word := SetSay(what);
    if word ~= "" and word ~= "Packrat" and word ~= "Caretaker" then
	/* ignore things said by the packrat or by himself */
	word := GetWord();
	if word == "Caretaker" or word == "Caretaker," or word == "Caretaker:"
	then
	    /* only act if the speech started with his name */
	    caretakerTold();
	elif word == "hello" or word == "hi" then
	    /* minimal conversationalist! */
	    OPrint("Caretaker grunts an acknowledgement.\n");
	fi;
    fi;
corp;

/* called by the system when someone whispers to him */

define tp_machines proc caretakerWhispered(string what)void:

    if SetWhisperMe(what) ~= "" then
	caretakerTold();
    fi;
corp;

/* called as the first step of someone giving something to him */

define tp_machines proc caretakerPre()status:

    SPrint(TrueMe(), "Caretaker accepts the gift.\n");
    continue
corp;

/* used for several other possible actions */

define tp_machines proc caretakerNoNo()status:

    DoSay("Watch it!");
    fail
corp;

define tp_machines proc caretakerCreate()void:

    Caretaker@p_pStandard := true;
    Caretaker@p_mLastDir := D_SOUTH;
    Caretaker@p_mArrived := true;
    SetupMachine(Caretaker);
    Caretaker@p_pDesc :=
"The caretaker is a simple soul. He wears faded overalls, a green check "
"shirt, heavy work boots and a grimy cap, on backwards. His goal in life is "
"to put things back where they belong. One of his most prized possessions is "
"a small handheld flashlight, which he keeps securely hidden in an inside "
"pocket until he needs it.";
    Caretaker@p_oLight := true;
    CreateMachine("Caretaker", Caretaker, r_crossSW, caretakerStep);
    ignore SetMachineActive(Caretaker, caretakerStep);
    ignore SetMachineSay(Caretaker, caretakerHear);
    ignore SetMachineWhisperMe(Caretaker, caretakerWhispered);
    GNewIcon(Caretaker, makeCaretakerIcon());
    Caretaker@p_pGivePre := caretakerPre;
    Caretaker@p_oTouchChecker := caretakerNoNo;
    Caretaker@p_oSmellChecker := caretakerNoNo;
    Caretaker@p_oPushChecker := caretakerNoNo;
    Caretaker@p_oPullChecker := caretakerNoNo;
    Caretaker@p_oTurnChecker := caretakerNoNo;
    Caretaker@p_oLiftChecker := caretakerNoNo;
    Caretaker@p_oLowerChecker := caretakerNoNo;
    Caretaker@p_oEatChecker := caretakerNoNo;
corp;
caretakerCreate().
ignore DeleteSymbol(tp_machines, "caretakerCreate").


/* Now Packrat */

define tp_machines Packrat CreateThing(nil).

/* packratStep - the central action routine for the packrat */

define tp_machines proc packratStep()void:
    thing here, me, object;
    list thing lt;
    int dir, firstNum, n;
    bool found, lost;

    if not ClientsActive() then
	After(60, packratStep);
    else
	me := Me();
	if me@p_mWantDelay then
	    me@p_mWantDelay := false;
	    After(10 + Random(20), packratStep);
	    me@p_mArrived := true;
	elif me@p_pPosition ~= 0 then
	    ignore Parse(G, "stand up");
	    After(10 + Random(20), packratStep);
	elif me@p_mArrived then
	    me@p_mArrived := false;
	    if Here() = r_pearField then
		if FindName(me@p_pCarrying, p_oName, "pear") ~= fail then
		    ignore Parse(G, "eat pear");
		else
		    ignore Parse(G, "pick pear");
		    me@p_mArrived := true;
		fi;
	    fi;
	    After(10 + Random(20), packratStep);
	else
	    me@p_mArrived := true;
	    firstNum := me@p_mLastDir;
	    if Random(2) ~= 0 then
		dir := firstNum;
	    else
		firstNum := DirBack(firstNum);
		while
		    dir := Random(12);
		    dir = firstNum
		do
		od;
		firstNum := dir;
	    fi;
	    found := false;
	    lost := false;
	    while not found do
		if TryToMove(dir) then
		    found := true;
		else
		    dir := (dir + 1) % 12;
		    if dir = firstNum then
			lost := true;
			found := true;
		    fi;
		fi;
	    od;
	    if lost then
		Log("Packrat is stuck, " + Here()@p_rName + "\n");
		Say("I'm stuck! Waaaahhhh!!");
		After(600, packratStep);
	    else
		me@p_mLastDir := dir;
		MachineMove(dir);
		here := Here();
		lt := here@p_rContents;
		n := Count(lt);
		if n ~= 0 then
		    object := lt[Random(n)];
		    if not object@p_oInvisible and not object@p_oNotGettable
		    then
			if DoGet(here, me, object) then
			    lt := me@p_pCarrying;
			    n := Count(lt);
			    if n = 4 then
				ignore DoDrop(here, me, lt[Random(3)]);
			    fi;
			fi;
		    fi;
		else
		    lt := me@p_pCarrying;
		    n := Count(lt);
		    if n ~= 0 and Random(20) = 0 then
			ignore DoDrop(here, me, lt[Random(n)]);
		    fi;
		fi;
		After(10 + Random(20), packratStep);
	    fi;
	fi;
    fi;
corp;

/*
 * This kind of delay is a wise thing to do, since it avoids many possible
 *	cases of recursion that could be troublesome. Consider the possibility
 *	of multiple packrat's in one room, and someone waves...
 */

define tp_machines proc packratCommand()void:
    thing me;
    string command, who;

    me := Me();
    who := me@p_mWho;
    if who ~= "" then
	SetMeString(who);
	me -- p_mWho;
    fi;
    command := me@p_mCommand;
    me -- p_mCommand;
    SetTail(command);
    if GetWord() == "pose" then
	Pose(GetTail());
    else
	if Parse(G, command) = 0 then
	    DoSay("Sorry luv, you've confused me.");
	fi;
    fi;
corp;

define tp_machines proc packratTold()void:
    thing me;
    string command;
    int direction;

    me := Me();
    /* need to save the tail, since doing 'DoSay' can change it! */
    command := GetTail();
    me@p_mWantDelay := true;
    DoSay("Right you are luv.");
    direction := DirMatch(command);
    if direction ~= -1 then
	me@p_mLastDir := direction;
    fi;
    me@p_mCommand := command;
    After(1, packratCommand);
corp;

define tp_machines proc packratHear(string what)void:
    string word;

    word := SetSay(what);
    if word ~= "" and word ~= "Packrat" and word ~= "Caretaker" then
	Me()@p_mWho := word;
	word := GetWord();
	if word == "Packrat" or word == "Packrat," or word == "Packrat:" then
	    packratTold();
	elif word == "hello" or word == "hi" or word == "greetings" then
	    DoSay(word + " to you too, luv!");
	elif word == "good" or word == "nice" then
	    word := GetWord();
	    DoSay("You have a good'n too luv!");
	fi;
    fi;
corp;

define tp_machines proc packratWhispered(string what)void:
    string who;

    who := SetWhisperMe(what);
    if who ~= "" then
	Me()@p_mWho := who;
	packratTold();
    fi;
corp;

define tp_machines proc packratOverhear(string what)void:
    string who, target;

    who := SetWhisperOther(what);
    if who ~= "" then
	target := GetWord();
	if target ~= "" then
	    what := GetTail();
	    if what ~= "" then
		DoSay("Luv, if you want " + target + " to " + what +
		    ", you'd better speak up!");
	    fi;
	fi;
    fi;
corp;

define tp_machines proc packratSaw(string what)void:

    SetTail(what);
    ignore GetWord();
    what := GetTail();
    Me()@p_mCommand := "pose " + what;
    After(1, packratCommand);
corp;

define tp_machines proc packratNoNo()status:

    DoSay("'ere now - don't get fresh w' me!");
    fail
corp;

/* This is needed so that Packrat can thank you AFTER you have seen the
   message about giving. */

define tp_machines proc packratThank()void:

    DoSay("ta, luv!");
corp;

define tp_machines proc packratPost()status:

    After(1, packratThank);
    continue
corp;

define tp_machines proc packratCreate()void:

    Packrat@p_pStandard := true;
    Packrat@p_mLastDir := D_SOUTH;
    Packrat@p_mArrived := false;
    SetupMachine(Packrat);
    Packrat@p_pQuestDoneList := CreateThingList();
    Packrat@p_pDesc :=
"The packrat is a small woman with a wizened face, stringy brown hair, and "
"beady little eyes. She is wearing a very old blue dress and an indeterminate "
"number of sweaters. A battered felt hat with a drab daisy perches "
"precariously on her head.";
    CreateMachine("Packrat", Packrat, r_es2, packratStep);
    ignore SetMachineActive(Packrat, packratStep);
    ignore SetMachineSay(Packrat, packratHear);
    ignore SetMachineWhisperMe(Packrat, packratWhispered);
    ignore SetMachineWhisperOther(Packrat, packratOverhear);
    ignore SetMachinePose(Packrat, packratSaw);
    GNewIcon(Packrat, makePackratIcon());
    Packrat@p_pGivePost := packratPost;
    Packrat@p_oTouchChecker := packratNoNo;
    Packrat@p_oSmellChecker := packratNoNo;
    Packrat@p_oPushChecker := packratNoNo;
    Packrat@p_oPullChecker := packratNoNo;
    Packrat@p_oTurnChecker := packratNoNo;
    Packrat@p_oLiftChecker := packratNoNo;
    Packrat@p_oLowerChecker := packratNoNo;
    Packrat@p_oEatChecker := packratNoNo;
corp;
packratCreate().
ignore DeleteSymbol(tp_machines, "packratCreate").

unuse tp_machines
unuse t_streets
