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