ship

lib
OBJECT
	/obj/lib/ship


SYNOPSIS
	inherit "/obj/lib/ship";


LAST UPDATE
	Thragor, 12-Oct-95, 22:15 MET


DESCRIPTION
	/obj/lib/ship offers the general functions to handle something
	which acts like a ship, i. e. it may be a dragon, a carpet or
	a balloon, but all have to use these functions.
	A ship drives from harbour to harbour, carrying passengers or
	just waiting for new passengers. It may have different rooms,
	where the passengers may walk around and needs one object
	which represents the ship (normally inheriting /obj/ship).
	If the ship is at a harbour it waits 1/4 minutes before it
	continues its journey, but only if there are passengers aboard
	or if customers are waiting at other harbours. Otherwise it
	stays in the harbour.
	While the ship is in the harbour it waits by heart_beat, i. e.
	it depends on the lag how much time it spends in the harbour.
	But if the ship is on the ocean, it drives with call_outs,
	i. e. without depending on lag, so that players don't have to
	wait for hours while they are on the ocean.

	Functions to configure the ship:

	  mapping SetPorts(mapping p)
	  mapping QueryPorts()
	  mapping AddPort(string portname,string portfile)
	    These functions handle the ports a ship will drive to.
	    <portname> is the name of the port which is e. g. used to
	    do the standard announce at which harbour the ship just
	    arrived.
	    The file may be given as relative path. If the port
	    doesn't support the ship, i. e. the ship is not added to
	    the port with AddShip() an error-message occurs.
	    The ship will drive to the ports in the order they are
	    given in the create(), i. e. you may even define a ship
	    with the following course:
	      Nightfall City - Dark Isle - Nightfall City - England
	    by adding the ports like this:
	      AddPort("Nightfall City","/d/archwiz/...");
	      AddPort("Dark Isle","/d/shadows/...");
	      AddPort("Nightfall City","/d/archwiz/...");
	      AddPort("England","/d/avalon/...");

	  mixed SetShipEnterMsgs(closure|string* msg)
	  mixed QueryShipEnterMsgs()
	    These are the messages which are printed when a ship
	    arrives at a harbour. The default setting is:
	     ({"The captain announces: We arrive at @@Harbour@@!\n",
               <shipshort>+" docks at the quay.\n"})
	    i. e. the messages are hold in an array, where the first
	    one is the message displayed on the decks of the ship and
	    the second one is the message displayed at the harbour.
	    The messages will be parsed through process_string()
	    later, so that e. g. @@Harbour@@ will be replaced by the
	    harbour the ship actually docks at.
	    If the message is a closure, this closure will be executed
	    and if it returns an array it will be handled as mentioned
	    above, otherwise the ship assumes that the closure printed
	    a message.
	    The closure is called with the portfile the ship docks at
	    as argument.

	  mixed SetShipLeaveMsgs(closure|string msg)
	  mixed QueryShipLeaveMsgs()
	    These are the messages which are printed when a ship
	    leaves the harbour. The default setting is:
	      ({"The captain announces: We leave @@FromHarbour@@ and head\n"
	        "  towards @@Harbour@@!\n",
	        <shipshort>+" leaves the quay.\n"}).
	    It is handled just as the ShipEnterMsgs(), i. e. all is
	    parsed through process_string() and if <msg> is a closure
	    it is called, etc.
	    The closure is called with the portfile the ship is
	    heading to as argument.

	  string *SetRooms(string *files)
	  string *QueryRooms()
	  string *AddRoom(string file)
	    For several situations the ship needs to know, where
	    passengers might be on the ship, i. e. which rooms belong
	    to the ship. E. g. the ship wants to know if there are any
	    passengers aboard, or needs to save the souls of the
	    passengers when it sinks (i. e. the passengers will be
	    moved to the VOID).
	    The file might be a relative path. It seems to be useful
	    to put the rooms of a ship into a directory below the
	    directory where the ship is with the name just as the ship
	    has, i. e. if you have a ship /d/domain/ships/blackship.c
	    you create a room for the ship in
	    /d/domain/ships/blackship/ and you add this room with:
	      AddRoom("./blackship/room");
	    If the room is a deck-room (see below AddDeck()), you
	    don't need to call AddRoom() as it is automatically done
	    in AddDeck.

	  string *SetDecks(string *files)
	  string *QueryDecks()
	  string *AddDeck(string file)
	    The deck is a special room of the ship. Here the
	    move-messages of a ship are displayed, it is announced
	    when the ship arrives at a harbour or leaves a harbour and
	    a deck always knows if the ship is in the harbour or not,
	    as the soft-property P_HARBOUR is set to the name of the
	    harbour, if the ship is at the harbour. If the ship is on
	    the ocean, P_HARBOUR is set to 0.
	    Deck-rooms never clean_up except the ship gets cleaned up.
	    This is because otherwise several settings will be lost.
	    E. g. the ship adds an exit 'leave' to every deck-room, so
	    that the passenger might leave the ship at a harbour.
	    Decks are automatically added as rooms to the ship.
	    If you don't specify any deck, the ship only acts as a
	    transporter, i. e. if a player tries to enter the ship
	    (s)he just get: This ship isn't built to transport
	    passengers.
	    The first room you add as deck is a room where a player
	    will start, when (s)he enters the ship.

	  mapping SetMoveScenes(mapping sc)
	  mapping QueryMoveScenes()
	  mapping AddMoveScene(string from,string to,
	                       string|closure scene,
	                       int delay)
	    Move scenes will be displayed sequentially on all decks of
	    the ship. This way your ship may drive around Kap Horn,
	    pass the Sea of Lost Souls and then turning north where
	    the skyline of Nightfall City appears.
	    A journey takes as long as scenes are there to get
	    displayed. If no scenes are specified a journey only takes
	    five seconds (that's the time it takes until the first
	    scene gets displayed). So you should perhaps think about
	    some nice scenes.
	    As the scenes might differ depending on the harbour you
	    come from and the harbour you go to, the first things the
	    scene needs to know on which course to display. Therefore
	    you need to specify <from> and <to> to the paths of the
	    harbours.
	      from:  The path of the harbour the ship just left. The
	             path may be relative.
	      to:    The path of the harbour the ship drives to. The
	             path may be relative.
	      scene: The scene to display on all decks. If the scene
	             is a closure, the closure is called. If the
	             closure returns a string, it is displayed on all
	             decks.
	             The closure gets called with the following
	             arguments:
	               QueryFromPort(), QueryToPort(), QueryDecks().
	      delay: The time (in seconds) to wait until the next
	             scene is displayed. The first scene is displayed
	             5 seconds after the ship left the harbour. The
	             delay of the last scene says how long it will
	             take from displaying this scene until the ship
	             arrives at the harbour.
	    Example:
	      AddMoveScene("/d/archwiz/common/room/port/quai1",
	                   "/d/shadows/common/room/island/port",
	                   "At the horizon the tower of the Nightfall\n"
	                   "City Church dives into the ocean.\n",
	                   10);

	  mixed SetMoveMsgs(mixed mm)
	  mixed QueryMoveMsgs()
	  mixed AddMoveMsg(closure|string msg,int probability)
	    Move messages (different from scenes!) will be displayed
	    randomly on all decks of the ship. Water may splash
	    against the planks, or a seagull might shit you on the
	    head.
	      msg: If <msg> is a string it will be just displayed to
	           all decks (parsed through process_string).
	           <msg> might be a closure, too. In this case the
	           closure is called and if the closure returns a
	           string this is displayed to all decks, otherwise
	           nothing is printed.
	           The closure will be called with the following
	           arguments:
	             QueryFromPort(), QueryToPort(), QueryDecks().
	      probability:
	           The probability is the chance that this message
	           gets displayed. The messages get displayed every
	           2-11 seconds. The probability is a total free value.
	           It's just a probability compared to the others,
	           i. e. if there's only one message it doesn't matter
	           what probability you set for it. It's displayed
	           each 2-11 seconds.
	           It's as easy as complicated ;-)
	           All probabilities are summed up. Then a random() is
	           called with this sum, and evaluated which message
	           got hit with this random-number. Just imagine a
	           six-sided die where you have 3 times '1', 2 times
	           '2' and 1 time '3'. You'll get '1' much more often
	           than '3'.
	           Example of this die implemented with MoveMsg:
	             AddMoveMsg("You throw a 1.\n",3);
	             AddMoveMsg("You throw a 2.\n",2);
	             AddMoveMsg("You throw a 3.\n",1);
	           If you don't want such a 'chatty' ship just add an
	           empty message:
	             AddMoveMsg("",6);

	  mixed SetEnterMsgs(string*|closure msg)
	  mixed QueryEnterMsgs()
	    This sets the messages displayed when a player enters the
	    ship. A short description is, that the <msg> you define
	    here is just given as argument to move() with the
	    move-method M_SPECIAL, in other words:
	    - The first array entry is displayed to the port with
	      <playername> in front of it and ".\n" at the end.
	    - The second array entry is displayed to the deck where
	      the player is moved to with leading <playername> and
	      ".\n" appended at the end.
	    - The third entry is the message the player gets, starting
	      with "You " and ending with ".\n".
	    The default setting is:
	      ({"enters "+<shipshort>,
	        "arrives on board",
	        "enter "+<shipshort>})
	    If <msg> is a closure this is evaluated while moving. The
	    returned result is given as argument to move() just as
	    mentioned above. If you want to write the messages on your
	    own, just return 0.

	  mixed SetLeaveMsgs(string*|closure msg)
	  mixed QueryLeaveMsgs()
	    Just as EnterMsgs() the message set is parsed through
	    move() with M_SPECIAL, when the player leaves the ship.
	    So there's one difference in the order of the array
	    entries: The first one is displayed on the ship, the
	    second one at the harbour.
	    The default setting is:
	      ({"leaves the ship",
	        "leaves "+<shipshort>,
	        "leave "+<shipshort>})
	    Again if <msg> is a closure it's called and the result
	    used as argument for move().

	These are all functions you need to configure the ship. The
	following functions are used by the ship and as you might want
	to redefine them, their behaviour is mentioned here. If a
	function is only supported for the files inheriting
	/obj/lib/ship (i. e. you can't do a call_other() to it) it's
	marked with 'protected'.

	  object RegisterMe()
	    This functions is called by every ship, when it gets
	    created. It starts a call to the blueprint if the ship
	    which will store the ship which got just cloned as the
	    blueprint handles e. g. a call to the ship (see
	    CallShip()).
	    As this information is lost as soon as you destruct the
	    blueprint, there'll be given a warning message when you
	    destruct the blueprint. You have to destruct the clone of
	    the ship, too, otherwise errors might occur.
	    Don't be afraid of destructing players when destructing
	    the clone of a ship. All players actually on the ship will
	    get a message: "You escaped from a sinking ship.\n" and
	    get moved to the void.
	    Destructing the ship will destruct automatically all rooms
	    which belong to the ship.

	  object QueryShip()
	    Called in the blueprint, you'll get the ship which it has
	    stored.

	  object valid_port(string port)
	  (protected)
	    This function returns the object-pointer to the port given
	    in <port>. Previously it does some checks:
	    - If the file given as port is not existant a
	      runtime_error is produced: "Port not existant!\n" and
	      the port is not added to the ports the ship supports.
	    - If an error occurs loading the port a runtime_error is
	      raised starting with "Port error: " and the error which
	      got returned loading the port. Again the port is not
	      added to the ports the ship supports.
	    - Finally the ship checks, if the port knows that the ship
	      shall dock there. If not (you forgot AddShip() in the
	      port) the ship refuses to go there and an error is
	      raised: "Port doesn't support ship.\n".
	    If the port passed all these tests the object-pointer to
	    the port which is now loaded gets returned otherwise 0.

	  string QueryFromPort()
	    This returns the filename of the port the ship just comes
	    from.

	  string QueryToPort()
	    This returns the filename of the port the ship just drives
	    to or the ship is just at.

	  string Harbour()
	    Returns the name of the port the ship drives to or is just
	    at.

	  string FromHarbour()
	    Returns the name of the port the ship comes from or just
	    leaves.

	  object *QueryPassengers()
	    Scans all rooms of the ship, if there are any players and
	    returns all of them in an array.

	  object *QueryCustomers()
	    Returns all (possible) passengers which are waiting at the
	    ports. This is e. g. needed to calculate the next port to
	    drive to.

	  int calc_next_port()
	  (protected)
	    Each port has an internal number in the ship depending on
	    the order you add them to the ship. This is the number
	    returned by calc_next_port().
	    The number calculated depends on different circumstanzes:
	    - If the ship has a passenger the next port is the next
	      port in the list.
	    - If no passengers are aboard the next port is the first
	      port in the list where customers are waiting.
	    - If no passengers or no customers are present, the ship
	      will drive to the next port in the list (although most
	      of the time the ship stays at the harbour because it's
	      heart_beat gets switched of if there are no passengers
	      and no customers (see below).

	  int can_sleep()
	  (protected)
	    Sometimes the ship can rest and doesn't need to use the
	    resources of the MUD. That's the case when the ship has an
	    environment, the ship is not on the ocean, there are no
	    passengers and no customers.
	    In this case the ship (which is at the harbour now with
	    heart_beat turned on) switches off its heart_beat and only
	    reactivates it if one of the circumstanzes changes.

	  void DeckDisplay(string msg,void|int shore)
	    Generally this functions displays a text on all decks.
	    It's used by the move-messages and -scenes or when the
	    ship leaves or arrives at a harbour.
	    If <shore> is 1, all decks get the property P_HARBOUR set
	    to 0, i. e. the ship leaves the harbour and is now on the
	    ocean. Else if <shore> is -1 the property P_HARBOUR is set
	    to the name of the harbour the ship is currently at.
	    This feature can be used, to modify the IntLong of the
	    deck-rooms depending on the location of the ship.
	    Example:
	      string QueryIntLong()
	      {
	        return
	          "You're on a ship.\n"+
	          (Query(P_HARBOUR)?"The ship is at "+Query(P_HARBOUR):
	                            "The ship is on the wide ocean")+".\n";
	      }

	  void AnnounceEnter()
	    This function handles the arrival of a ship at a harbour.
	    It queries the ShipEnterMsgs and displays them to the
	    decks with DeckDisplay(). If a function handles all
	    messages (see SetShipEnterMsgs()) DeckDisplay() is just
	    called to set the P_HARBOUR property with an empty
	    message.

	  void AnnounceLeave()
	    Just as AnnounceEnter() this handles the ship leaving the
	    harbour.

	  void PrintMoveMsg()
	  (protected)
	    If move-messages are defined, this function chooses one of
	    them per random, displays the move-message and starts a
	    new call to PrintMoveMsg().

	  void StartJourney()
	    Well, the anchor is heaved and the ship heads towards the
	    next harbour. All this is handled in this function. The
	    heart_beat is switched off (the journey is done via
	    call_out), AnnounceLeave() is called, the ship is moved to
	    the ocean and the first random move-message is called in
	    2+random(10) seconds.

	  void EndJourney()
	    We had a nice journey over the ocean and now the ship
	    docks at the quay, waiting for new passengers. This is
	    what this function is doing.
	    First of all, it moves the ship to the port, calls
	    AnnounecEnter() and activates its heart_beat() to wait for
	    new passengers. The call_out to the random move-messages
	    is stopped.

	  void PrintScene(int scene)
	  (protected)
	    A nice journey over the ocean is sightseeing, too. So lets
	    tell the player what (s)he gets to see on the journey.
	    The scenes are displayed in the order they got defined
	    (see AddMoveScene()).
	    If there are no more scenes to display PrintScene() calls
	    EndJourney().

	  void NextPort()
	    If the time to wait at a port is over, heart_beat() calls
	    this function to start the journey to a new harbour.
	    calc_next_port() is called to see where to drive to and if
	    the result is another harbour as we're currently at, the
	    ship sets a new port it comes from, a new port it drives
	    to and calls StartJourney() and starts a call_out() to the
	    first scene which is displayed five seconds after the ship
	    left the harbour.

	  void heart_beat()
	    The heart_beat() of the ship is used to wait at a harbour
	    depending on the lag, so that players have a chance to
	    enter or leave the ship.
	    But first of all it is checked via can_sleep() (see above)
	    if someone needs the ship. Otherwise heart_beat is
	    switched off until there's a new customer.
	    If the ship waited long enough, the ship heads towards the
	    next harbour.
	    DANGER!!! If you use /obj/lib/ship in a NPC (as e. g. the
	              dragons will do some day) you need to call both
	              heart_beats:
	                void heart_beat()
	                {
	                  npc::heart_beat();
	                  ship::heart_beat();
	                }

	  void CallShip()
	    This function tells the ship that someone needs its
	    service. Normally this is called by the harbours. The call
	    is send to the blueprint of the ship, which checks if
	    there's a clone of it around (if not a new ship is cloned)
	    and then tells this ship that it's needed.
	    So CallShip() has a second part which is only valid for
	    the clones of the ship. This part switches the heart_beat
	    on, if it is necessary.

	  int cmd_leave(string str)
	    This function is called when a player types 'leave' on a
	    deck of the ship. 'leave' is added as exit to all decks.
	    Per default you leave the ship, if you give no argument or
	    if the argument is either 'ship' or 'boat' or if the ship
	    itself identifies to <str>.
	    If the ship is on the ocean the player gets the message:
	      'Sorry, you're not at a harbour.'
	    otherwise the player is moved to the harbour with
	    M_SPECIAL and QueryLeaveMsgs() as argument (see above).

	  void InitDecks()
	    When the ship gets cloned it needs to initialize its
	    decks, i. e. tell them that they shouldn't clean_up (they
	    clean_up when the ship cleanes up) and there's an exit
	    called 'leave' which is bound to a closure which points to
	    cmd_leave() (see above).

	  int cmd_enter(string str)
	    Of course you might enter the ship. As /obj/lib/ship has
	    no init, an object inheriting this needs to add the
	    necessary commands with add_action() as it is done for all
	    ships in /obj/ship:
	      void init()
	      {
	        (::init());
	        add_action("cmd_enter","enter"); // enter the ship
	        add_action("cmd_enter","board");
	      }
	    You just need to type 'enter' to board the ship or give
	    'ship' or 'boat' as argument or a string which identifies
	    the ship.
	    If the ship has no decks specified (see AddDeck()) the
	    player gets the message:
	      <shipshort>+" isn't built to transport passengers.\n"
	    otherwise the decks get initialized with InitDecks() and
	    the player gets moved to the first deck defined.

	  int remove()
	    Very difficult this thing. It's handled in different ways
	    depending if remove() is called in the blueprint or in the
	    clone of the ship.
	    Blueprint:
	      If the blueprint points to a clone of the ship, a
	      warning message is displayed and the blueprint won't get
	      removed. Otherwise the blueprint gets destructed.
	    Clone:
	      Are there any passengers aboard? SOS! Move all of them
	      to the /std/void.
	      Finally the ship sinks and with it all its rooms, i. e.
	      all loaded rooms get removed, too.
	      Then the ship destructs itself.

	If you write your own 'ship' using /obj/lib/ship and not
	/obj/ship, you need to specify your own clean_up() which has
	to look like this:
	  varargs int clean_up(int refcount)
	  {
	    if (!can_clean_up()) return 1;
	    return (thing::clean_up(refcount));
	  }
	i. e. if /obj/lib/ship allows the ship to clean up, the clean
	up of the inherited file (here: 'thing') has to be called.

	A special feature of the ships is that you might set a name
	for it with P_NAME. If you specify this, the timetable at the
	ports will not show its shortdescription but its name.


INHERITANCE TREE
	obj/lib/ship
	  `-/obj/lib/number


SEE ALSO