Contain.h

SuperContainer class (library contribution)

A “supercontainer” is an object that works as both a container and a platform. For example, Hugo’s default library has no way of handling a desk that lets you put objects both on it and inside of it. It is instances like this where the SuperContainer class is useful.

This contribution was written by Kent Tessman and updated with clothing checks by Robb Sherwin.

Usage notes

As pointed out by the comments:

 - include after including "hugolib.h"  - Always use MoveInto() and MoveOnto() to move objects into or onto a SuperContainer.  - add the specified code from below to the player object

The code itself

!\---------------------------------------------------------------------------
    container.h

    SuperContainer class

    The SuperContainer class can be a platform and/or a container,
    and has its own routines for handling objects, for describing
    its contents, etc.

    Include this file after including "hugolib.h" (requires library
    version 3.0.04.1 or later).

    Always use MoveInto() and MoveOnto() to move objects into or
    onto a SuperContainer.

    Also, the player object must have this block in a before routine:

    before
    {
        actor
        {
            if parent(object).type = SuperContainer
            {
                if Inlist(parent(object), contents_in, object) and
                    parent(object) is not open
                {
                    print capital player.pronoun #1;
                    MatchPlural(player, "doesn't", "don't")
                    " see that."
                    return true
                }
            }
            return false
        }
    }

    Version 1.01 - Robb Sherwin. Added code to check if an item is worn, in addition to being clothing.
    Version 1.00 - Kent Tessman. An implementation of a new, relatively untested SuperContainer container/platform combo class.

---------------------------------------------------------------------------\!

! These can be modified in objects derived from SuperContainer:
property capacity_in alias n_to
property capacity_on alias ne_to

attribute actually_transparent

! You can change define this constant beforehand if you want more (or less)
! than the default maximum:
#if undefined MAX_SUPERCONTAINER_CONTENTS
constant MAX_SUPERCONTAINER_CONTENTS 32
#endif


!----------------------------------------------------------------------------
! The SuperContainer class itself
!----------------------------------------------------------------------------

! For SuperContainer internal use:
property contents_in
property count_in
property contents_on
property count_on
property reset_contents
attribute supposed_to_be_closed

class SuperContainer
{
    type SuperContainer

    capacity_in 100
    capacity_on 100

    preposition "in", "on"  ! can be changed, but both are needed

    ! To disable either container or platform behavior, use
    ! "is not [container or platform]" in the object definition
    is container
    is platform

! Contents display:

    list_contents
    {
        run self.reset_contents
        if self.count_in and self is container and
            (self is open or self is transparent)
        {
            local temp_word
            ! So we can hijack object.before:DoLookIn
            temp_word = word[1]
            word[1] = "in"
            Indent
            Perform(&DoLookIn, self)
            word[1] = temp_word
        }
        if self.count_on
        {
            Indent
            Perform(&DoLookIn, self)
        }
    }

! SuperContainer internals:

    contents_in #MAX_SUPERCONTAINER_CONTENTS
    count_in 0
    contents_on #MAX_SUPERCONTAINER_CONTENTS
    count_on 0

    react_before
    {
        if self = parent(object)
        {
            if InList(self, contents_on, object) and self is not open
            {
                self is open
                self is supposed_to_be_closed
            }
        }
        return false
    }

    before
    {
        xobject     ! anytime we're an xobject
        {
            run self.reset_contents
            return false
        }
        object      ! anytime we're an object
        {
            run self.reset_contents
            return false
        }
        xobject DoPutIn
        {
            ! "in"
            if PrepWord("in") or PrepWord("into") or PrepWord("inside")
            {
                if self is openable and self is not open
                {
                    CThe(self)
                    " is closed."
                    return true
                }
                elseif object is clothing and object is worn
                {
                    VMessage(&DoDrop, 1)     ! "Have to take it off first..."
                }
                elseif not MoveInto(xobject, object)
                {
                    "There is no room for ";
                    print The(object); " ";
                    print self.prep #1; " "; The(self); "."
                }
                else
                {
                    print CThe(player); " put"; MatchSubject(player); \
                        " "; The(object);
                    print " "; xobject.prep #1; " ";
                    print The(xobject); "."
                }
            }

            ! "on"
            else
            {
                if not MoveOnto(xobject, object)
                {
                    "There is no room for ";
                    print The(object); " ";
                    print self.prep #2; " "; The(self); "."
                }
                elseif object is clothing and object is worn
                {
                    VMessage(&DoDrop, 1)     ! "Have to take it off first..."
                }
                else
                {
                    print CThe(player); " put"; MatchSubject(player); \
                        " "; The(object);
                    print " "; xobject.prep #2; " ";
                    print The(xobject); "."
                }
            }
        }
        object DoLookIn
        {
            ! "in"
            if PrepWord("in") or PrepWord("into") or PrepWord("inside")
            {
                if self is openable and self is not open
                {
                    CThe(self)
                    " is closed."
                    return true
                }

                CThe(self)
                if self.count_in = 0
                {
                    " is empty."
                }
                else
                {
                    " contains ";
                    PropertyList(self, contents_in)
                    print "."
                }
            }

            ! "on"
            else
            {
                CThe(self)
                " has ";
                if self.count_on = 0
                {
                    "nothing";
                }
                else
                    PropertyList(self, contents_on)
                print " "; self.prep #2; " it."
            }
        }
    }
    after
    {
        xobject     ! anytime we're an xobject
        {
            run self.reset_contents
            return false
        }
        object      ! anytime we're an object
        {
            run self.reset_contents
            return false
        }
    }

    reset_contents
    {
        local i, obj
        self.count_in = 0
        for (i=1; i<=self.#contents_in; i++)
        {
            obj = self.contents_in #i
            if obj and obj not in self
            {
                self.contents_in #i = 0
            }
            elseif obj
            {
                self.count_in++
                if obj is plural
                    self.count_in++
            }
        }
        self.count_on = 0
        for (i=1; i<=self.#contents_on; i++)
        {
            obj = self.contents_on #i
            if obj and obj not in self
            {
                self.contents_on #i = 0
            }
            elseif obj
            {
                self.count_on++
                if obj is plural
                    self.count_on++
            }
        }
        if self is supposed_to_be_closed
            self is not open
        self is not supposed_to_be_closed
    }
}


!----------------------------------------------------------------------------
! Always use MoveInto() and MoveOnto() to move objects into or onto a
! SuperContainer.  Returns false if there's no room to do so.
!----------------------------------------------------------------------------

routine MoveInto(p, obj)
{
    if not p.#contents_in:  return false
    run p.reset_contents

    ! Search for an empty "in" slot
    local i, slot, bulk
    for (i=1; i<=p.#contents_in; i++)
    {
        if p.contents_in #i = 0
        {
            if not slot
                slot = i
        }
        else
            bulk += (p.contents_in #i).size
    }

    ! If we didn't find an empty slot, there's no room
    if not slot:  return false
    if bulk > p.capacity_in:  return false

    ! Put the object in the contents_in list
    p.contents_in #slot = obj
    move obj to p
    return true
}

routine MoveOnto(p, obj)
{
    if not p.#contents_on:  return false
    run p.reset_contents

    ! Search for an empty "on" slot
    local i, slot, bulk
    for (i=1; i<=p.#contents_on; i++)
    {
        if p.contents_on #i = 0
        {
            if not slot
                slot = i
        }
        else
            bulk += (p.contents_in #i).size
    }

    ! If we didn't find an empty slot, there's no room
    if not slot:  return false
    if bulk > p.capacity_on:  return false

    ! Put the object in the contents_on list
    p.contents_on #slot = obj
    move obj to p
    return true
}


! PrepWord(str) return true if the word "str" is the preposition used
! in the player input.

routine PrepWord(str)
{
    local i
    for (i=1; i<=words; i++)
    {
        if not ObjWord(word[i], object)
        {
            if word[i] = str
                return true
            if word[i] = ""
                break
        }
    }
}