Birthday banner

The birthday banner is automatically updated every day to reflect whose birthday it is.
It doesn't choke on several people sharing the same birthday.
On days with no birthdays, it changes name to blank banner.

First, the banner is created: @create bdb. It got the ID #14166.

Then, we add two name fields, to make the name change easier:
@field bdb = name0 : blank banner;banner;bb;bdb

@field bdb = name1 : birthday banner;birthday;banner;birth day banner;birth;day;birth day;bdb;bdc

Birthdays are stored on the banner-object, in this format: bd#PLAYER: DDMMYYYY.
For instance, my ID is 12456, and my birthday is October 4, 1985: bd12456: 04101985.

Furthermore, for display purposes, all the month names have been encoded on the banner:

@field #14166 = mn1 : Jan
@field #14166 = mn2 : Feb
@field #14166 = mn3 : Mar
@field #14166 = mn4 : Apr
@field #14166 = mn5 : May
@field #14166 = mn6 : Jun
@field #14166 = mn7 : Jul
@field #14166 = mn8 : Aug
@field #14166 = mn9 : Sep
@field #14166 = mn10 : Oct
@field #14166 = mn11 : Nov
@field #14166 = mn12 : Dec

Nasty update code

@field bdb = update : @s("cnt", 0);
 // This clears all fields starting with ply:
 @fieldloop("%!", "ply", @s("%f", ""));

 // Build a date string with two digits for the day and month:
 @let("x", @print(
   @let("i", @time("mday"), @switch(@strlen("%i"), 1, "0%i", "%i")),
   @let("i", @add(@time("mon"), 1), @switch(@strlen("%i"), 1, "0%i", "%i"))
  ),
  @print(
   @tell(12456, "[ BDB update %x ]"),
 // Find all players with matching birthdays
   @fieldloop("%!", "bd",
    @switch(@substr("%v", 0, 4),
     "%x", @let("c", @g("cnt"), @print(
      @s("ply%c", @shortname(@substr("%f", 2))),
      @s("cnt", @add("%c", 1)),
     )),
     ""
    )
   )
  )
 );

Now we have a 'cnt' field, containing the number of people who has birthday today, and ply0, ply1, etc. fields with their names.
We'll now run through them all, building a nice namelist and putting it in the field 'strbd'.

update continued:
 @s("i", 1);
 @let("c", @g("cnt"), @print(
  @s("strbd", @fieldloop("%!", "ply",
   @let("i", @g("i"), @print(
    @switch("%i", 1, "", "%c", " and ", ", "),
    "%v",
    @s("i", @add("%i", 1))
   ))
  )),

  // We also set the 'text' field to the banner text:
  @s("text", @print("Happy birthday ", @g("strbd"), "!")),

  // Finally, we set the name of the banner:
  @switch("%c",
   0, @s("name", @g("name0")),
   @s("name", @print("banner reading \"", @g("text"), "\";", @g("name1")))
  )
 )

Banner description

@field bdb = note : %c(Use 'write YYYY-MM-DD on banner' to add or change your birthday. Also note that the listing order is arbitrary, and that sorting it would be painfull.)

@desc bdb = @print(
 @switch(@g("cnt"),
  0, "Today isn't the birthday of any MUDder.",
  @print("It reads, \"", @g("text"), "\"")
 ),
 @call("%!", "note")
)

Letting players add themselves to the birthday list

@set bdb = expert

@action bdb = write * on bdc = write * on banner;write * on b;write * on bdb;w * on b;w * on bdb

@set write * on banner = puzzle

@success write * on bdb = @switch(@strlen("%0"),
 10, @let("d", @add(@substr("%0", 8, 2), 0),
          "m", @add(@substr("%0", 5, 2), 0),
          "y", @substr("%0", 0, 4),
  @print(
   // If the player hasn't already been added, display a message.
   @null(@strcheck(
    @getfield(14166, "bd%#"), // If this is non-empty, we say nothing.
    @tellroom(@location(14166), "%#", "%n added %r to the banner.")
   )),

   // Store the birthdate.   
   @setfield(14166, "bd%#",
    @print(@substr("%0", 8, 2), @substr("%0", 5, 2), "%y")),

   "Okay, your birthday is ", @getfield(14166, "mn%m"), " %d, %y.",
   @call(14166, "update")
  )
 ),
 "Please specify the year with four digits,
  and the month and day with two digits."
)

Listing all birthdays

@action list all birthdays;list all;list birthdays;list all birth days = bdb : nowhere

@success list all birthdays = @fieldloop(14166, "bd", @print(
 @shortname(@substr("%f", 2)),
 ": ",
 @getfield(14166, @print("mn", @add(@substr("%v", 2, 2), 0))),
 " ",
 @add(@substr("%v", 0, 2), 0),
 ", ",
 @substr("%v", 4, 4),
 ".%c"
))

Listing specific birthdays

@action find birthday of *;list birthday of *;find b of *;b of * = bdb : nowhere

@set find b of * = puzzle

@success find b of * = @let("o", @player("%4"), @switch("%o",
 -1, "There is no player called '%4' on this MUD.",
 @print(
  "The birthday of ",
  @shortname("%o"),
  " is ",
  @let("v", @getfield(14166, "bd%o"), @switch("%v",
   "", "unknown.",
   @print(
    @getfield(14166, @print("mn", @add(@substr("%v", 2, 2), 0))),
    " ",
    @add(@substr("%v", 0, 2), 0),
    ", ",
    @substr("%v", 4, 4),
    "."
   )
  ))
 )
))

Updating the banner every day

We calculate how many seconds have passed of the day so far, subtract that from 87000 (24 hours and 10 minutes), then create an event that fires shortly after midnight.
The event should be set to fire some time after midnight, to ensure it doesn't fire before midnight (causing the banner to be wrong for a whole day.)
We also keep track of the ID, and make sure to kill the old event if it still exists (in case updtimer is called manually.)

@field bdb = updtimer : @call("%!", "update");
 @killevent(@g("updeventid"));
 @s("updeventid", @event("%!", "updtimer",
   @sub(87000, @add(@mul(@time("min"), 60), @mul(@time("hour"), 3600))),
   "Update birthday banner"
 ))

Sometimes this fails, leaving the banner without any automatic update event. To handle this, we have a restart banner action.

@action restart banner = bdb : nowhere
@success restart banner =
    @print("Curse this Microsoft Birthday Banner vermin!");
    @null(@call(14166,"updtimer"))

And that's it.