How to implement multi-tile mobs.


Multi-tile mobs consist of several mobs occupying a pattern of tiles. One of the mobs is the "head" mob; this mob creates the "part" mobs, controls movement, handles events, and may have a client connected to it. The others are "part" mobs; when the head mob moves, they move with it, maintaining a constant delta x and delta y. The icons and CreateParts proc for the red smiley in Smileys of Unusual Size are given as an example. The icons (click the links to download them as .dmi files) are named as follows and placed in the directory mob/sous under the project's icons directory:

red12.dmired22.dmi
red11.dmired21.dmi

The following code defines the smiley to occupy four tiles in a 2x2 square, with the bottom left mob as the head:

/mob/multi/head/sous/red
    CreateParts()
    icon = 'mob/sous/red11.dmi'
    density = 1
    CreateParts()
        CreatePart('mob/sous/red21.dmi',1,0,1)
        CreatePart('mob/sous/red12.dmi',0,1,1)
        CreatePart('mob/sous/red22.dmi',1,1,1)
After the head's New proc has called the CreateParts proc, the smiley will look like this:


The code for the multi-tile mob library itself is as follows. Note that the changes to /turf/Enter are fully compatible with the changes to /turf/Enter made in the Dense Corner Cutting Prevention example, and they can both be included in the same proc.


/turf
    Enter(O)
        var/turf/t
        var/b

        // Only dense objects can be blocked.

        if (O:density)

            // A dense turf always blocks a dense object.

            if (density)
                return 0

            // Dense objects are blocked by other dense objects at the
            // destination unless both objects are part of the same multipart
            // mob.

            if (istype(O,/mob/multi))
                for (b in contents)
                    if ((!(istype(b,/mob/multi)&&(b:head==O:head)))&&b:density)
                        return 0
            else
                for (b in contents)
                    if (b:density)
                        return 0

        // The object can enter if it wasn't blocked.

        return 1


// /mob/multi defines vars common to both "head" mobs and "part" mobs.

/mob/multi
	var/mob/multi/head/head
	var/delta_x
	var/delta_y


// Part mobs are created by the CreateParts proc of a head mob.

/mob/multi/part
    New(Loc,mob/multi/head/h,i,dx,dy,d)

        // Create the part.

        ..(Loc)

        // The following vars must be given as arguments to New.

        head=h
        icon=i
        delta_x=dx
        delta_y=dy
        density=d

        // The following vars must be copied from the head.

        name=head.name
        icon_state=head.icon_state
        visibility=head.visibility
        luminosity=head.luminosity

        // Add this part to the head's parts list.

        head.parts.Add(src)
    Move(Loc,Dir)

        // If the head has checked the move, perform it.

        if (head.move_ok)
            return ..(Loc,Dir)

        // If the head hasn't checked the move, tell it to move to the location
        // that would cause it to tell this part to move to the location given
        // to this Move proc

        else
            if (isturf(Loc))
                return head.Move(locate(Loc:x-delta_x,Loc:y-delta_y,Loc:z),Dir)
            else
                return head.Move(Loc,Dir)

    // The following events must be handled by the head.

    Click(Location)
        return head.Click(Location)
    DblClick(Location)
        return head.DblClick(Location)


// Head mobs handle movement verification and mouse events

/mob/multi/head
    var/list/parts
    var/move_ok
    New(Loc)

        // Create the head.

        ..(Loc)

        // Add it to the parts list.

        parts=list(src)

        // Set head pointer to refer to self.

        head=src

        // delta_x and delta_y must be zero for the head.

        delta_x=0
        delta_y=0

        // Initialize move_ok flag.

        move_ok=0

        // Create parts.

        CreateParts()
    Del()
        var/mob/multi/part/p

        // Delete parts.

        for (p in parts-src)
            del p

        // Delete head.

        ..()
    Move(Loc,Dir)
        var/mob/multi/o
        var/turf/t
        var/area/a

        // Perform the move if it has already been checked.

        if (move_ok)
            return ..(Loc,Dir)

        // Fail if any part cannot exit its current location.

        for (o in parts)
            if (o.loc)
                if (!o.loc:Exit(o))
                    return 0

        // If entering a turf, parts must move to turfs determined by their
        // delta_x & delta_y.

        if (isturf(Loc))
            for (o in parts)

                // Find the destination turf for this part.

                t=locate(Loc:x+o.delta_x,Loc:y+o.delta_y,Loc:z)

                // If this part must cross an area boundary, check that it can
                // leave its current area and enter its destination area.

                a=GetArea(o)
                if (a!=t.loc)
                    if (!((isnull(a)||a.Exit(o))&&t.loc:Enter(o)))
                        return 0

                // Can this part enter its destination turf?

                if (!t.Enter(o))
                    return 0

            // All parts can move to their destination turfs. Set the move_ok
            // flag to signal that parts should move themselves instead of
            // asking the head to verify the move.

            move_ok=1

            // Move the parts, including the head.

            for (o in parts)
                t=locate(Loc:x+o.delta_x,Loc:y+o.delta_y,Loc:z)
                o.Move(t,Dir)

        // If entering a non-turf, parts must move to null.

        else

            // Can the head enter its destination?

            if (!Loc:Enter(src))
                return 0

            // Set the move_ok flag.

            move_ok=1

            // Perform the move.

            Move(Loc,Dir)

            // Move other parts to null.

            for (o in parts-src)
                if (o.loc)
                    o.Move(null,Dir)

        // Clear the move_ok flag.

        move_ok=0

        // Indicate success to caller.

        return 1
    Login()
        var/mob/multi/o
        .=..()

        // The name var has now been changed to match the key, so the name var
        // must be updated for each part.

        for (o in parts)
            o.name=name
    proc/CreateParts()

        // Stub.
        //
        // The parts to be created will be defined in derived classes.

    proc/CreatePart(i,dx,dy,d)

        // If the head is in a turf, parts must be created in turfs determined
        // by their delta_x and delta_y vars.

        if (isturf(loc))
            new /mob/multi/part(locate(loc:x+dx,loc:y+dy,loc:z),src,i,dx,dy,d)

        // Otherwise they must be created in null.

        else
            new /mob/multi/part(null,src,i,dx,dy,d)

Back to BYOND Programming Examples