SpriteMgr.c

/*
 
 
    © Copyright 1991 Ricardo Batista,  All Rights Reserved.
    
    
    Sprite Manager
    
    
    04/20/91
    
    
    HISTORY:  (Most Recent first)
    
    
    04/20/91    rb  New today
    
    
*/
 
 
 
 
#include <Types.h>
#include <QuickDraw.h>
#include <QDOffscreen.h>
#include <Memory.h>
#include <Resources.h>
#include <OSUtils.h>
#include <Retrace.h>
#include <Windows.h>
#include <ToolUtils.h>
 
#include "SpriteMgr.h"
 
#define     desiredPixSize      8
 
SpriteMgrRecPtr     Sprite_Mgr_Globals;     // global used to save our information
 
 
pascal short SetDepth(GDHandle gd, short newDepth, short whichFlags, short newFlags) =
                    {0x203C, 0x000A, 0x0013, 0xAAA2};
 
pascal short HasDepth(GDHandle gd, short newDepth, short whichFlags, short newFlags) =
                    {0x203C, 0x000A, 0x0014, 0xAAA2};
 
 
 
 
 
    /*
        This routine initializes the sprite manager, it must be called after dialogs,
        windows, resources, and quickdraw have been initialized.  This routine creates
        a record to memory to keep the sprite manager globals, it also finds the best
        monitor in which to display the color animation and sets the monitor to 4 bit
        color after saving the previous color depth.  This routine also installs a small
        vbl task used by the drawing routines.
        if cTable is not nil the given color table is made the current color table and the
        previous color table saved for later restoration.
        returns 0 if no error occured. 1 if no color monitor present or not enough memory
        to create memory record.
    */
 
short InitSpriteMgr(CTabHandle cTable)
{
    short err;
    GDHandle device;
    PixMapHandle pixH;
    Boolean done = false;
    short oldPixelSize;
    CTabHandle  colorTable = 0L;
    short mode;
    
    if (Sprite_Mgr_Globals)
        return(0);
    Sprite_Mgr_Globals = (SpriteMgrRecPtr) NewPtrClear(sizeof(SpriteMgrRec));
    if (!Sprite_Mgr_Globals)
        return(1);
    device = GetDeviceList();
    if (!device) {
        DisposPtr((Ptr) Sprite_Mgr_Globals);
        Sprite_Mgr_Globals = 0L;
        return(1);
    }
    while (device && !done) {
        pixH = (**device).gdPMap;
        oldPixelSize = (**pixH).pixelSize;
        if (oldPixelSize == desiredPixSize) {
            if (cTable) {
                colorTable = (**pixH).pmTable;
                (**pixH).pmTable = cTable;
                GDeviceChanged(device);
            }
            done = true;
        }
        else {
            mode = HasDepth(device, desiredPixSize, 1, 1);      /* color device */
            if (mode) {
                err = SetDepth(device, desiredPixSize, 1, 1);
                if (cTable) {
                    colorTable = (**pixH).pmTable;
                    (**pixH).pmTable = cTable;
                    GDeviceChanged(device);
                }
                done = true;
            }
        }
        if (!done)
            device = (GDHandle) (**device).gdNextGD;
    }
    if (!done)
        return(1);
    
    Sprite_Mgr_Globals->originalCTable = colorTable;
    Sprite_Mgr_Globals->currCTable = cTable;
    Sprite_Mgr_Globals->originalDepth = oldPixelSize;
    Sprite_Mgr_Globals->task.qType = vType;
    Sprite_Mgr_Globals->task.vblAddr = (ProcPtr) VTASK;
    Sprite_Mgr_Globals->task.vblCount = 1;
    Sprite_Mgr_Globals->task.vblPhase = 0;
    Sprite_Mgr_Globals->deviceUsed = device;
    Sprite_Mgr_Globals->active = false;
    Sprite_Mgr_Globals->changed = true;
    Sprite_Mgr_Globals->updateRects = 0;
    
    err = VInstall((QElemPtr) &Sprite_Mgr_Globals->task.qLink);
    return(err);
}
 
    
    
    
    
    
    
    
    
    
    
    /*
        This routine disposes of the window used by animation, destroys all of the graphic
        worlds created and deinstalls the vbl task.  It also re-enables any applications
        hidden by ActivateAnimation.
        Restores the color table if it had been changed when InitSpriteMgr was called.
        This routine should be called when the application quits.
    */
    
void CloseSpriteMgr(void)
{
    CTabHandle cTable;
    GDHandle device;
    short err;
    
    VRemove((QElemPtr) &Sprite_Mgr_Globals->task.qLink);
    device = Sprite_Mgr_Globals->deviceUsed;
    if (Sprite_Mgr_Globals->originalCTable) {
        cTable = (**(**device).gdPMap).pmTable;
        (**(**device).gdPMap).pmTable = Sprite_Mgr_Globals->originalCTable;
        GDeviceChanged(device);
        DisposHandle((Handle) cTable);
    }
    err = SetDepth(device, Sprite_Mgr_Globals->originalDepth, 1, 1);
    
    while (Sprite_Mgr_Globals->backSprites)
        KillSprite(Sprite_Mgr_Globals->backSprites->id);
    while (Sprite_Mgr_Globals->normSprites)
        KillSprite(Sprite_Mgr_Globals->normSprites->id);
    while (Sprite_Mgr_Globals->foreSprites)
        KillSprite(Sprite_Mgr_Globals->foreSprites->id);
 
    while (Sprite_Mgr_Globals->backgrounds)
        KillBackground(Sprite_Mgr_Globals->backgrounds->id);
    while (Sprite_Mgr_Globals->foregrounds)
        KillForeground(Sprite_Mgr_Globals->foregrounds->id);
    
    if (Sprite_Mgr_Globals->animationWindow)
        DisposeWindow((WindowPtr) Sprite_Mgr_Globals->animationWindow);
    if (Sprite_Mgr_Globals->animationGWorld)
        DisposeGWorld(Sprite_Mgr_Globals->animationGWorld);
    if (Sprite_Mgr_Globals->currCTable)
        DisposHandle((Handle) Sprite_Mgr_Globals->currCTable);
    DisposPtr((Ptr) Sprite_Mgr_Globals);
    Sprite_Mgr_Globals = 0L;
}
 
 
    
    
    
    
    /*
        This routine creates a new background by creating a new graphics world and drawing
        in it the picture with id pictID.  drawOrder is the background drawing priority 
        with 1 being the highest priority.  The highest priority gets drawn first.  copyMode
        should be srcCopy for the highest priority element and transparent for the rest.
        animationRect is the area within the animation screen where the background will be
        drawn.  The size of the picture must be at least as big as the animationRect and can
        be bigger if the area is to be scrolled later in the animation.  If the given pictID
        is already in use by another graphic entity then graphics world will be shared instead
        of creating a new graphics world.
        id is returned if no errors occur.  Otherwise 0 if there was not enough memory for the
        offscreen world or the picture could not be loaded.
    */
    
    
    
short NewBackground(short pictID, short drawOrder, short copyMode, Rect *animationRect, short id)
{
    return (NewScenery(pictID, drawOrder, copyMode, animationRect, id, true));
}
 
 
 
 
 
 
 
                    
 
 
    /*
        This routine creates a new foreground by creating a new graphics world and drawing
        in it the picture with id pictID.  drawOrder is the foreground drawing priority 
        with 1 being the highest priority.  The highest priority gets drawn first.  copyMode
        should be transparent in most cases.
        animationRect is the area within the animation screen where the foreground will be
        drawn.  The size of the picture must be at least as big as the animationRect and can
        be bigger if the area is to be scrolled later in the animation.  If the given pictID
        is already in use by another graphic entity then graphics world will be shared instead
        of creating a new graphics world.
        id is returned if no errors occur.  Otherwise 0 if there was not enough memory for the
        offscreen world or the picture could not be loaded.
    */
    
 
 
short NewForeground(short pictID, short drawOrder, short copyMode, Rect *animationRect, short id)
{
    return (NewScenery(pictID, drawOrder, copyMode, animationRect, id, true));
}
                    
 
    
 
 
    /*
        This routine changes the offsets where the image is to be drawn in the animation
        world and marks the background as changed so that is it redrawn again.
    */
 
 
 
void ScrollBackground(short id, short vOffset, short hOffset)
{
    SceneryInfoRecPtr sc;
    
    sc = Sprite_Mgr_Globals->backgrounds;
    while (sc) {
        if (sc->id == id) {
            sc->scrollOffset.v += vOffset;
            sc->scrollOffset.h += hOffset;
            sc->changed = true;
            return;
        }
        sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
}
 
 
 
 
 
 
 
 
 
    /*
        This routine changes the offsets where the image is to be drawn in the animation
        world and marks the foreground as changed so that is it redrawn again.
    */
 
 
void ScrollForeground(short id, short vOffset, short hOffset)
{
    SceneryInfoRecPtr sc;
    
    sc = Sprite_Mgr_Globals->foregrounds;
    while (sc) {
        if (sc->id == id) {
            sc->scrollOffset.v += vOffset;
            sc->scrollOffset.h += hOffset;
            sc->changed = true;
            return;
        }
        sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
}
 
 
 
 
 
 
 
 
 
 
 
    /*
        This routine sets an auto scroll timer and amount for a background to
        be scrolled automatically.  Autoscrolling has some limitations at the
        time.
    */
 
void AutoScrollBackground(short id, short vOffset, short hOffset, long scrollTicks)
{
    SceneryInfoRecPtr sc;
    
    sc = Sprite_Mgr_Globals->backgrounds;
    while (sc) {
        if (sc->id == id) {
            sc->nextTickCount = TickCount() + scrollTicks;
            sc->scrollTicks = scrollTicks;
            sc->autoScrollAmount.v = vOffset;
            sc->autoScrollAmount.h = hOffset;
            return;
        }
        sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
}
 
 
 
 
 
 
 
 
 
    /*
        This routine sets an auto scroll timer and amount for a foreground to
        be scrolled automatically.  Autoscrolling has some limitations at the
        time.
    */
 
void AutoScrollForeground(short id, short vOffset, short hOffset, long scrollTicks)
{
    SceneryInfoRecPtr sc;
    
    sc = Sprite_Mgr_Globals->foregrounds;
    while (sc) {
        if (sc->id == id) {
            sc->nextTickCount = TickCount() + scrollTicks;
            sc->scrollTicks = scrollTicks;
            sc->autoScrollAmount.v = vOffset;
            sc->autoScrollAmount.h = hOffset;
            return;
        }
        sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
}
 
 
 
 
 
 
 
 
 
 
 
    /*
        This routine disposes of the graphics world used by the graphic entity with
        id number id if the graphics world is not being shared by other graphic entities.
        It also deletes any information about this entity from the Sprite Manager
        animation record.
    */
 
 
 
void KillBackground(short id)
{
    SceneryInfoRecPtr sc, sc2;
    
    sc = Sprite_Mgr_Globals->backgrounds;
    if (sc && (sc->id == id)) {
        Sprite_Mgr_Globals->backgrounds = (SceneryInfoRecPtr)
                    sc->nextScenery;
        if (sc->shared) {
            sc2 = FindTwinScenery(sc->pictID);
            if (sc2)
                sc->sceneryWorld = 0L;
        }
        if (sc->sceneryWorld)
            DisposeGWorld(sc->sceneryWorld);
        DisposPtr((Ptr) sc);
        return;
    }
    sc2 = sc;
    sc = (SceneryInfoRecPtr) sc2->nextScenery;
    while (sc) {
        if (sc->id == id) {
            sc2->nextScenery = sc->nextScenery;
            if (sc->shared) {
                sc2 = FindTwinScenery(sc->pictID);
                if (sc2)
                    sc->sceneryWorld = 0L;
            }
            if (sc->sceneryWorld)
                DisposeGWorld(sc->sceneryWorld);
            DisposPtr((Ptr) sc);
            return;
        }
        sc2 = sc;
        if (sc)
            sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
}
 
 
 
 
 
 
 
 
 
 
 
    /*
        This routine disposes of the graphics world used by the graphic entity with
        id number id if the graphics world is not being shared by other graphic entities.
        It also deletes any information about this entity from the Sprite Manager
        animation record.
    */
 
 
void KillForeground(short id)
{
    SceneryInfoRecPtr sc, sc2;
    
    sc = Sprite_Mgr_Globals->foregrounds;
    if (sc && (sc->id == id)) {
        Sprite_Mgr_Globals->foregrounds = (SceneryInfoRecPtr)
                sc->nextScenery;
        if (sc->shared) {
            sc2 = FindTwinScenery(sc->pictID);
            if (sc2)
                sc->sceneryWorld = 0L;
        }
        if (sc->sceneryWorld)
            DisposeGWorld(sc->sceneryWorld);
        DisposPtr((Ptr) sc);
        return;
    }
    sc2 = sc;
    sc = (SceneryInfoRecPtr) sc2->nextScenery;
    while (sc) {
        if (sc->id == id) {
            sc2->nextScenery = sc->nextScenery;
            if (sc->shared) {
                sc2 = FindTwinScenery(sc->pictID);
                if (sc2)
                    sc->sceneryWorld = 0L;
            }
            if (sc->sceneryWorld)
                DisposeGWorld(sc->sceneryWorld);
            DisposPtr((Ptr) sc);
            return;
        }
        sc2 = sc;
        if (sc)
            sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
}
 
    
 
 
 
 
 
 
 
 
 
 
 
 
 
    /*
        This routine creates a graphics world for the new sprite.  If the same pictID is
        shared by another sprite then the graphics world will be shared between all the
        sprites that share the same picture.  Otherwise the picture range from pictID to
        pictID + totalPicts will be drawn in the new graphics world.  copyMode should be
        transparent in most cases but could be blend in some cases.  spriteProc is a
        procedure pointer that if given can be called every ProcTicks ticks
        (sixthieths of a second).  If ProcTicks is not zero and spriteProc is nil then
        the Sprite Manager will automatically change the current face of the sprite 
        every ProcTicks ticks to the next face.  drawOrder is the priority in which the
        sprite will be drawn with 1 being the highest priority.  collisionProc is a procedure
        pointer that will be called when a collision with another sprite is detected only if
        the parameter canColide is set to true and the collision occurs with another sprite.
        id is the id number that will be used to identify this sprite.
        
        
        Parameters to the collisionProc are the id of the sprite that collided with this
        sprite.
        
        pascal void CollisionProc(short spriteID);
        
        Parameters to the sprite procedure are the id of the sprite and the rectangle which
        encloses the sprite in the animation world.
        
        pascal void SpriteProc(short spriteID, Rect *locationRect);
    */
 
 
short NewBackgroundSprite(short pictID, short totalPicts, short copyMode,
                        ProcPtr spriteProc, long ProcTicks, short drawOrder,
                        ProcPtr collisionProc, Boolean canColide, short id)
{
    return (SMgrNewSprite(pictID, totalPicts, copyMode, spriteProc, ProcTicks, drawOrder,
            collisionProc, canColide, id, 1));
}
    
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
    /*
        This routine creates a graphics world for the new sprite.  If the same pictID is
        shared by another sprite then the graphics world will be shared between all the
        sprites that share the same picture.  Otherwise the picture range from pictID to
        pictID + totalPicts will be drawn in the new graphics world.  copyMode should be
        transparent in most cases but could be blend in some cases.  spriteProc is a
        procedure pointer that if given can be called every ProcTicks ticks
        (sixthieths of a second).  If ProcTicks is not zero and spriteProc is nil then
        the Sprite Manager will automatically change the current face of the sprite 
        every ProcTicks ticks to the next face.  drawOrder is the priority in which the
        sprite will be drawn with 1 being the highest priority.  collisionProc is a procedure
        pointer that will be called when a collision with another sprite is detected only if
        the parameter canColide is set to true and the collision occurs with another sprite.
        id is the id number that will be used to identify this sprite.
        
        
        Parameters to the collisionProc are the id of the sprite that collided with this
        sprite.
        
        pascal void CollisionProc(short spriteID);
        
        Parameters to the sprite procedure are the id of the sprite and the rectangle which
        encloses the sprite in the animation world.
        
        pascal void SpriteProc(short spriteID, Rect *locationRect);
    */
    
 
short NewSprite(short pictID, short totalPicts, short copyMode,
                        ProcPtr spriteProc, long ProcTicks, short drawOrder,
                        ProcPtr collisionProc, Boolean canColide, short id)
{
    return (SMgrNewSprite(pictID, totalPicts, copyMode, spriteProc, ProcTicks, drawOrder,
            collisionProc, canColide, id, 2));
}
    
    
    
 
 
 
 
 
 
 
 
 
 
 
 
 
 
    /*
        This routine creates a graphics world for the new sprite.  If the same pictID is
        shared by another sprite then the graphics world will be shared between all the
        sprites that share the same picture.  Otherwise the picture range from pictID to
        pictID + totalPicts will be drawn in the new graphics world.  copyMode should be
        transparent in most cases but could be blend in some cases.  spriteProc is a
        procedure pointer that if given can be called every ProcTicks ticks
        (sixthieths of a second).  If ProcTicks is not zero and spriteProc is nil then
        the Sprite Manager will automatically change the current face of the sprite 
        every ProcTicks ticks to the next face.  drawOrder is the priority in which the
        sprite will be drawn with 1 being the highest priority.  collisionProc is a procedure
        pointer that will be called when a collision with another sprite is detected only if
        the parameter canColide is set to true and the collision occurs with another sprite.
        id is the id number that will be used to identify this sprite.
        
        
        Parameters to the collisionProc are the id of the sprite that collided with this
        sprite.
        
        pascal void CollisionProc(short spriteID);
        
        Parameters to the sprite procedure are the id of the sprite and the rectangle which
        encloses the sprite in the animation world.
        
        pascal void SpriteProc(short spriteID, Rect *locationRect);
    */
 
short NewForegroundSprite(short pictID, short totalPicts, short copyMode,
                        ProcPtr spriteProc, long ProcTicks, short drawOrder,
                        ProcPtr collisionProc, Boolean canColide, short id)
{
    return (SMgrNewSprite(pictID, totalPicts, copyMode, spriteProc, ProcTicks, drawOrder,
            collisionProc, canColide, id, 3));
}
    
    
    
    
    
    
    
    
    
    
 
 
    /*
        This procedure creates a new animation world and creates a graphics world
        for it.  animationRect is the rectangle which will be used for the animation
        in a window to be created by this procedure.  The window will be created in the
        graphics device which supports 4 bit color.  windowH is the width of the window
        to be created and windowV is the height of the window.  wTitle is the title that
        will be given to the window.  The window created will be centered in the graphics
        device.  A color window pointer is returned to the caller so that the application
        can use the areas outside the animationRect for purposes other than animation such
        as scores.
    */
 
 
 
 
CWindowPtr NewAnimation(Rect *animationRect, short windowH, short windowV, Str255 wTitle)
{
    Rect box;
    short err;
    GWorldPtr world = 0L;
    CTabHandle cTable;
    short offsetV, offsetH;
    CGrafPtr savedGWorld;
    GDHandle savedGD;
    
    GetGWorld(&savedGWorld, &savedGD);
    box = (**(Sprite_Mgr_Globals->deviceUsed)).gdRect;
    offsetV = box.bottom - box.top - windowV;
    offsetH = box.right - box.left - windowH;
    if (offsetV > 0)
        offsetV /= 2;
    else
        offsetV = 0;
    if (offsetH > 0)
        offsetH /= 2;
    else
        offsetH = 0;
    box.right = box.left + windowH + offsetH;
    box.bottom = box.top + windowV + offsetV;
    box.top += offsetV;
    box.left += offsetH;
    Sprite_Mgr_Globals->animationWindow = (CWindowPtr) NewCWindow(0L, &box, wTitle,
                true, documentProc, (WindowPtr) -1L, false, 'Anim');
    Sprite_Mgr_Globals->animationRect = *animationRect;
    cTable = Sprite_Mgr_Globals->currCTable;
    err = NewGWorld(&world, desiredPixSize, animationRect, cTable, 0L, (long) useTempMem);
    if (!err) {
        SetGWorld(world, 0L);
        EraseRect(animationRect);
    }
    Sprite_Mgr_Globals->animationGWorld = world;
    if (!world) {
        DisposeWindow((WindowPtr) Sprite_Mgr_Globals->animationWindow);
        Sprite_Mgr_Globals->animationWindow = 0L;
    }
    SetGWorld(savedGWorld, savedGD);
    return(Sprite_Mgr_Globals->animationWindow);
}
 
 
 
 
 
 
 
    
 
 
 
 
 
 
 
 
 
 
 
    /*
        This procedure set the current face of the sprite with id number id to the
        given index.  The changed flag is set to true so that drawing is performed in
        the next chance by the Sprite Manager.
    */
 
 
 
void SetCurrentSpriteIndex(short id, short index)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        p->changed = true;
        p->currentFace = index;
    }
}
 
    
    
    
    
    
    
    
    
    
    
    
    
    
    /*
        This procedure puts the sprite in the animation world at the location given
        by top and left.  The changed flag is set to true so that drawing is performed
        as soon as possible.  A sprite should only be "put" in the animation world
        once unless it is first "removed" by calling RemoveSprite.
    */
    
void PutSprite(short id, short top, short left)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        p->changed = true;
        p->animationRect = p->spriteRect;
        p->animationRect.top += top;
        p->animationRect.bottom += top;
        p->animationRect.left += left;
        p->animationRect.right += left;
    }
}
 
 
    
    
    
    
 
 
 
 
 
 
 
 
    /*
        This procedure removes a sprite from the animation world by setting its
        animation rectangle to an empty rectangle and setting the sprite's changed
        flag so that the animation world is updated correctly.
    */
    
void RemoveSprite(short id)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        p->changed = true;
        p->animationRect.top = p->animationRect.left =
                    p->animationRect.bottom = p->animationRect.right = 0;
    }
}
 
    
    
    
    
    
 
 
 
 
 
 
 
 
 
 
 
 
    /*
        This procedure moves the sprite in the animation world first remembering the
        sprite's old location for further updating.  The sprite's location is offset by
        h and v.  A new face for the sprite can be given by newIndex if newIndex is not
        zero.  Once a sprite is "put" in the animation world it should
    */
    
    
void MoveSprite(short id, short h, short v, short newIndex)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        p->changed = true;
        p->oldAnimationRect = p->animationRect;
        p->animationRect.top += v;
        p->animationRect.left += h;
        p->animationRect.bottom += v;
        p->animationRect.right += h;
        if (newIndex)
            p->currentFace = newIndex;
    }
}
 
    
    
    
    
    
    
    
    
    
    
    
    
    
    /*
        This procedure moves a sprite with the given id to the location specified by h
        and v.  A new face for thr sprite may be given by specifying a newIndex other
        than zero.  The old location of the sprite is recorded for further updating.
    */
    
 
 
void MoveSpriteTo(short id, short h, short v, short newIndex)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        p->changed = true;
        p->oldAnimationRect = p->animationRect;
        p->animationRect = p->spriteRect;
        p->animationRect.top += v;
        p->animationRect.left += h;
        p->animationRect.bottom += v;
        p->animationRect.right += h;
        if (newIndex)
            p->currentFace = newIndex;
    }
}
 
 
        
 
 
 
 
 
 
 
 
 
 
 
 
    /*
        This procedure makes visible or invisible a sprite based on the show parameter.
        A "hiden" sprite is not drawn in the animation world but can still create
        collisions if its collision attribute is enabled.  If the new visibility state
        of the sprite is changed the "changed" attribute gets set to true so that the
        animation world is updated correctly.
    */
    
    
    
void ShowSprite(short id, Boolean show)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        if (show != p->hidden) {
            p->changed = true;
            p->oldAnimationRect = p->animationRect;
            p->hidden = show;
        }
    }
}
 
 
 
 
 
 
 
 
 
 
 
    
    
    
    
    
    
    /*
        This procedure returns the current location of the given sprite in the animation
        world or an empty rectangle if the sprite is not currently in the animation world.
        More detailed information can be obtained by calling GetSpriteInfo.
    */
 
    
    
void GetSpriteLocation(short id, Rect *location)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        *location = p->animationRect;
    }
    else {
        SetRect(location, 0, 0, 0, 0);
    }
}
 
 
 
 
 
 
 
 
 
 
 
 
    /*
        This procedure enables or disables collisions for a sprite based on the enable value.
        Sprites with collisions enabled can only collide with other sprites which
        have the collision attribute set to true.
    */
    
 
 
void EnableSpriteCollisions(short id, Boolean enable)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        p->canCollide = enable;
    }
}
 
    
    
    
    
    
    
    
    
    
    
    
 
    /*
        This is a special purpose call which "validates" the old rectangle which ussed to
        be occupied by a sprite.  Its purpose is to prevent redundant drawing from occuring
        when an specific sprite and face moves into the position previously occupied by
        an identical sprite and face.
    */
    
    
void ValidateOldSpriteLocation(short id)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        p->oldAnimationRect.top = p->oldAnimationRect.left = p->oldAnimationRect.bottom =
                p->oldAnimationRect.right = 0;
    }
}
 
 
 
 
 
 
    
    
    
    
    
 
 
 
 
    /*
        This call allows the programmer to specify a collision area for a sprite other than
        the sprite's own dimensions.  The collision rectangle is given in the sprite's
        local coordinate system.  By using this call collisions can be limited to a certain
        portion of th sprite's area.
    */
    
    
    
void SetSpriteCollisionRect(short id, Rect *colRect)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p) {
        p->collisionRect = *colRect;
    }
}
 
 
 
    
    
 
 
 
 
 
 
 
 
 
 
 
 
    /*
        This function returns a pointer to a sprite information record.  The pointer returned
        is valid for the life of the sprite.  That is until is the sprite is "killed" with
        a call to "KillSprite".  The data structure returned by this call should be used mostly
        for read-only purposes.  Altering values in the data structure could result in
        animation errors.
    */
    
    
    
SpriteInfoRecPtr GetSpriteInfo(short id)
{
    SpriteInfoRecPtr p;
    
    p = FindSprite(id);
    if (p)
        return(p);
    return(0L);
}
    
    
    
    
    
    
 
 
 
 
 
 
 
 
 
 
    /*
        This procedure destroys the graphics world used by the sprite with the given id
        if such sprite is not currently sharing a graphics world with another sprite.
        If the sprite is currently in the animaton screen its area is recorded so it
        can be updated as soon as possible by the Sprite Manager.
    */
 
    
    
void KillSprite(short id)
{
    SpriteInfoRecPtr p, p2, twin1, twin2;
    register short counter;
    
    for (counter = 0; counter < 3; counter++) {
        if (counter == 0)
            p = Sprite_Mgr_Globals->normSprites;
        if (counter == 1)
            p = Sprite_Mgr_Globals->backSprites;
        if (counter == 2)
            p = Sprite_Mgr_Globals->foreSprites;
        if (p) {
            if (p->id == id) {
                if (counter == 0)
                    Sprite_Mgr_Globals->normSprites = (SpriteInfoRecPtr) p->nextSprite;
                if (counter == 1)
                    Sprite_Mgr_Globals->backSprites = (SpriteInfoRecPtr) p->nextSprite;
                if (counter == 2)
                    Sprite_Mgr_Globals->foreSprites = (SpriteInfoRecPtr) p->nextSprite;
                if (p->sharedWorld) {
                    p->sharedWorld = false;                 // mark as unshared so FindTwin wont give us ourselves
                    twin1 = FindTwinSprite(p->pictID);      // find a twin
                    if (!twin1)
                        DisposeGWorld(p->spriteWorld);      // no twins ! weird..
                    else {
                        twin1->sharedWorld = false;         // disable this one temporarily
                        twin2 = FindTwinSprite(p->pictID);  // are there any more ?
                        if (twin2)
                            twin1->sharedWorld = true;      // restore shared status since there are more sharing
                    }
                }
                else {
                    DisposeGWorld(p->spriteWorld);
                }
                DisposPtr((Ptr) p);
                return;
            }
            p2 = p;
            p = (SpriteInfoRecPtr) p->nextSprite;
            while (p) {
                if (p->id == id) {
                    if (p->sharedWorld) {
                        p->sharedWorld = false;                 // mark as unshared so FindTwin wont give us ourselves
                        twin1 = FindTwinSprite(p->pictID);      // find a twin
                        if (!twin1)
                            DisposeGWorld(p->spriteWorld);      // no twins ! weird..
                        else {
                            twin1->sharedWorld = false;         // disable this one temporarily
                            twin2 = FindTwinSprite(p->pictID);  // are there any more ?
                            if (twin2)
                                twin1->sharedWorld = true;      // restore shared status since there are more sharing
                        }
                    }
                    else {
                        DisposeGWorld(p->spriteWorld);
                    }
                    p2->nextSprite = p->nextSprite;
                    DisposPtr((Ptr) p);
                    return;
                }
                p2 = p;
                p = (SpriteInfoRecPtr) p->nextSprite;
            }
        }
    }
}
 
    
    
    
    
    
    
    
    
    
    
    
    
    
    
 
 
 
 
 
 
 
 
    /*
        This routine activates the animation world so that animation will occur the next
        time DoAnimation is called.  The deafult state after calling NewAnimation is to have
        the animation world inactive.  All applications other than the current one are hidden
        so that the processing time is dedicated to the animation application.
    */
        
    
    
void ActivateAnimation(void)
{
    Sprite_Mgr_Globals->active = true;
}
 
    
    
    
 
 
 
 
 
 
 
 
    /*
        This routine prevents any animation from ocurring by deactivating the animation
        world.  This is the deafult state after calling NewAnimation.  This procedure should
        be called when the animation is to be suspended.  It also enabled all other applications
        so that the user can swith applications.
    */
    
    
void DeactivateAnimation(void)
{
    Sprite_Mgr_Globals->active = true;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
    /*
        This is the main driver for the animation.  If the parameter update is set to
        true then the animation world is simply redrawn.  Call DoAnimation with update
        set to true when you receive an update event.  When an update event occurs the
        application must bracket this call with BeginUpdate and EndUpdate and any
        application specific drawing ouside the animation area must be redrawn as well.
        When update is set to false this procedure animates the animation world by doing
        several things.
        
            1) Go trough the backgrounds updating the animation area as nessesary.
            2) Detect sprite collisions and calling collision procedures if nessesary.
            3) Check the tick counters and calling the tickle procedures if any.
            4) Updating automatic tickling sprites.
            5) Drawing any sprites that have changed their face.
            6) Updating foregounds.
            7) Updating foreground sprites that have changed.
    */
 
 
void DoAnimation(Boolean update)
{
    GrafPtr g1, g2;
    Rect sRect;
    register short counter, total;
    register Rect *boxPtr;
    KeyMap keys;
    
    if (update) {
        g1 = (GrafPtr) Sprite_Mgr_Globals->animationGWorld;
        g2 = (GrafPtr) Sprite_Mgr_Globals->animationWindow;
        sRect = Sprite_Mgr_Globals->animationRect;
        Sprite_Mgr_Globals->task.inVBL = 0;
        while (Sprite_Mgr_Globals->task.inVBL == 0)
            ;
        CopyBits(&(g1->portBits), &(g2->portBits), &sRect, &sRect, srcCopy, 0L);
        return;
    }
    if (Sprite_Mgr_Globals->active == false)
        return;
    Sprite_Mgr_Globals->changed = false;
    
    // next: update only changed rectangles on sceneries
    // Detect collisions
    
    
    do {
        Sprite_Mgr_Globals->redoCheck = false;
        CheckChangedScenery(Sprite_Mgr_Globals->backgrounds);   // backgrounds
        CheckChangedSprite(Sprite_Mgr_Globals->backSprites);    // back sprites
        CheckChangedSprite(Sprite_Mgr_Globals->normSprites);    // sprites
        CheckChangedScenery(Sprite_Mgr_Globals->foregrounds);   // foregrounds
        CheckChangedSprite(Sprite_Mgr_Globals->foreSprites);    // fore sprites
    } while (Sprite_Mgr_Globals->redoCheck);
    
    Sprite_Mgr_Globals->updateRects = 0;    // nothing to update
    
    UpdateScenery(Sprite_Mgr_Globals->backgrounds); // changed backgrounds
    UpdateSprite(Sprite_Mgr_Globals->backSprites);
    UpdateSprite(Sprite_Mgr_Globals->normSprites);      // changed sprites
    UpdateScenery(Sprite_Mgr_Globals->foregrounds); // changed foregrounds
    UpdateSprite(Sprite_Mgr_Globals->foreSprites);
    
    if (Sprite_Mgr_Globals->changed == false)
        return;
    g1 = (GrafPtr) Sprite_Mgr_Globals->animationGWorld;
    g2 = (GrafPtr) Sprite_Mgr_Globals->animationWindow;
    
    GetKeys(&keys[0]);
    if ((keys[1] & 0x4) == 0)
        Sprite_Mgr_Globals->updateRects = kMaxUR;
    if (Sprite_Mgr_Globals->updateRects == kMaxUR) {    // update full screen
        sRect = Sprite_Mgr_Globals->animationRect;
        Sprite_Mgr_Globals->task.inVBL = 0;
        while (Sprite_Mgr_Globals->task.inVBL == 0)
            ;
        CopyBits(&(g1->portBits), &(g2->portBits), &sRect, &sRect, srcCopy, 0L);
    }
    else {
        total = Sprite_Mgr_Globals->updateRects;
        boxPtr = &(Sprite_Mgr_Globals->changedRect[0]);
        for (counter = 0; counter < total; counter++) {
            Sprite_Mgr_Globals->task.inVBL = 0;
            while (Sprite_Mgr_Globals->task.inVBL == 0)
                ;
            CopyBits(&(g1->portBits), &(g2->portBits), boxPtr, boxPtr, srcCopy, 0L);
            boxPtr++;
        }
        Sprite_Mgr_Globals->updateRects = 0;
    }
}
 
    
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
    /*
        This procedure changes the current animation pallete used in the animation world
        to the new color table.
    */
 
 
 
void SetNewAnimationPallete(CTabHandle cTable)
{
    GDHandle dev;
    
    dev = Sprite_Mgr_Globals->deviceUsed;
    cTable = (**(**dev).gdPMap).pmTable;
    (**(**dev).gdPMap).pmTable = cTable;
    DisposHandle((Handle) cTable);
    GDeviceChanged(dev);
}
 
 
 
 
 
 
 
 
 
 
/************************************************************************************
 
    P R I V A T E       R O U T I N E S
    
*************************************************************************************/
 
 
 
    /*
        This is the common routine to create sceneries, we use this routine because we want
        to make things more readable for the programmer by providing "specialized" routines
        that allow them to distinguish between foregrounds and backgrounds.
        The basic process is to create the scenery data structure, fill in the default values,
        create the graphics world, load the picture and copy it into the g world, then
        the scenery data structure is inserted in the right place.
        I suppose that later on we could insert them in the linked list in their priority
        order.
    */
 
 
short NewScenery(short pictID, short drawOrder, short copyMode, Rect *animationRect,
                short id, Boolean back)
{
    SceneryInfoRecPtr sc, sc2, sc3;
    short err;
    PicHandle picH;
    Rect box;
    GWorldPtr world;
    CGrafPtr CsavePort;
    GDHandle gd;
    PixMapHandle pixH;
    
    sc = (SceneryInfoRecPtr) NewPtrClear(sizeof(SceneryInfoRec));
    if (!sc)
        return(0);
    sc->id = id;
    sc->pictID = pictID;
    sc->drawOrder = drawOrder;
    sc->copyMode = copyMode;
    sc->animationRect = *animationRect;
    sc->hidden = false;
    sc->changed = true;
    sc->shared = false;
    sc2 = FindTwinScenery(pictID);
    if (sc2) {
        sc->shared = true;
        sc2->shared = true;
        sc->sceneryWorld = sc2->sceneryWorld;
        sc->sceneryRect = sc2->sceneryRect;
    }
    else {
        picH = GetPicture(pictID);
        if (!picH) {
            DisposPtr((Ptr) sc);
            return(0);
        }
        LoadResource((Handle) picH);
        HLock((Handle) picH);
        box = (**picH).picFrame;
        box.right = box.right - box.left;
        box.bottom = box.bottom - box.top;
        box.top = box.left = 0;
        sc->sceneryRect = box;
        GetGWorld(&CsavePort, &gd);
        err = NewGWorld( &world, desiredPixSize, &box, 0L, 0L, 0L); // (long) useTempMem);
        if (err || (world->portPixMap == 0L)) {
            ReleaseResource((Handle) picH);
            DisposPtr((Ptr) sc);
            return(0);
        }
        pixH = world->portPixMap;
        HLock((Handle) pixH);
        LockPixels(pixH);
        NoPurgePixels(pixH);
        SetGWorld(world, 0L);
        sc->sceneryWorld = world;
        DrawPicture(picH, &box);
        ReleaseResource((Handle) picH);
        SetGWorld(CsavePort, gd);
    }
    if (back)
        sc2 = Sprite_Mgr_Globals->backgrounds;
    else
        sc2 = Sprite_Mgr_Globals->foregrounds;
    if (sc2->id > id) {
        if (back)
            Sprite_Mgr_Globals->backgrounds = sc;
        else
            Sprite_Mgr_Globals->foregrounds = sc;
        sc->nextScenery = (Ptr) sc2;
        return(id);
    }
    else {
        if (back)
            sc3 = sc2 = Sprite_Mgr_Globals->backgrounds;
        else
            sc3 = sc2 = Sprite_Mgr_Globals->foregrounds;
        while (sc2 && (sc2->id < id)) {
            sc3 = sc2;
            sc2 = (SceneryInfoRecPtr) sc2->nextScenery;
        }
        if (sc2) {
            sc3->nextScenery = (Ptr) sc;
            sc->nextScenery = (Ptr) sc2;
        }
        else {
            sc3->nextScenery = (Ptr) sc;
        }
    }
    return(id);
}
    
 
 
 
 
 
 
 
 
 
 
 
    /*
        This is the common routine to create sprites, we use this routine because we want
        to make things more readable for the programmer by providing "specialized" routines
        that allow them to distinguish between foreground, normal and background sprites.
        The basic process is to create the sprite data structure, fill in the default values,
        create the graphics world, load the pictures and copy them into the g world, then
        the sprite data structure is inserted in the right place.
        I suppose that later on we could insert them in the linked list in their priority
        order.
    */
 
 
 
short SMgrNewSprite(short pictID, short totalPicts, short copyMode,
                        ProcPtr spriteProc, long ProcTicks, short drawOrder,
                        ProcPtr collisionProc, Boolean canCollide, short id, short sType)
{
    SpriteInfoRecPtr p, p2, twin;
    short err, h, v;
    PicHandle picH;
    Rect box;
    GWorldPtr world;
    CGrafPtr CsavePort;
    GDHandle gd;
    PixMapHandle pixH;
    register short counter;
    
    p = (SpriteInfoRecPtr) NewPtrClear(sizeof(SpriteInfoRec));
    if (!p)
        return(0);
    p->id = id;
    p->pictID = pictID;
    p->drawOrder = drawOrder;
    p->copyMode = copyMode;
    p->oldAnimationRect = p->animationRect;
    p->tickWait = ProcTicks;
    p->currentFace = 1;
    p->sharedWorld = false;
    p->faces = totalPicts;
    p->tickProc = spriteProc;
    p->canCollide = canCollide;
    p->collisionProc = collisionProc;
    p->hidden = false;
    p->changed = true;
    twin = FindTwinSprite(pictID);
    if (twin) {
        twin->sharedWorld = true;
        p->spriteRect = twin->spriteRect;
        p->collisionRect = twin->collisionRect;
        p->spriteWorld = twin->spriteWorld;
        p->nextSprite = 0L;
    }
    else {
        picH = GetPicture(pictID);
        if (!picH) {
            DisposPtr((Ptr) p);
            return(0);
        }
        LoadResource((Handle) picH);
        HLock((Handle) picH);
        box = (**picH).picFrame;
        h = box.right - box.left;
        v = box.bottom - box.top;
        box.right = h;      // make zero offset based
        box.bottom = v;
        box.top = box.left = 0;
        p->spriteRect = box;
        p->collisionRect = box;
        box.right *= totalPicts;
        GetGWorld(&CsavePort, &gd);
        err = NewGWorld( &world, desiredPixSize, &box, 0L, 0L, 0L); // (long) useTempMem);
        if (err || (world->portPixMap == 0L)) {
            ReleaseResource((Handle) picH);
            DisposPtr((Ptr) p);
            return(0);
        }
        pixH = world->portPixMap;
        HLock((Handle) pixH);
        LockPixels(pixH);
        NoPurgePixels(pixH);
        SetGWorld(world, 0L);
        EraseRect(&world->portRect);
        p->spriteWorld = world;
        p->nextSprite = 0L;
        for (counter = 0; counter < totalPicts; counter++) {
            picH = GetPicture(pictID + counter);
            LoadResource((Handle) picH);
            HLock((Handle) picH);
            box = (**picH).picFrame;
            box.right = box.right - box.left;
            box.bottom = box.bottom - box.top;
            box.left = box.top = 0;
            box.right += h * counter;
            box.left += h * counter;
            DrawPicture(picH, &box);
            ReleaseResource((Handle) picH);
        }
        SetGWorld(CsavePort, gd);
    }
    if (sType == 1) {
        p2 = Sprite_Mgr_Globals->backSprites;
        Sprite_Mgr_Globals->backSprites = p;
    }
    if (sType == 2) {
        p2 = Sprite_Mgr_Globals->normSprites;
        Sprite_Mgr_Globals->normSprites = p;
    }
    if (sType == 3) {
        p2 = Sprite_Mgr_Globals->foreSprites;
        Sprite_Mgr_Globals->foreSprites = p;
    }
    p->nextSprite = (Ptr) p2;
    return(id);
}
 
 
 
 
 
 
 
 
 
 
 
    /*
        This is a common routine which is used to find a sprite by a given id, this routine
        gets called by most of the sprite interface routines that allow the programmer to
        change attributes on a sprite.  The lookup priority is by sprite, then foreground
        sprite and finally background sprites.  If no sprite is found we return nil.
    */
 
SpriteInfoRecPtr FindSprite(short id)
{
    SpriteInfoRecPtr p;
    
    p = Sprite_Mgr_Globals->normSprites;
    while (p) {
        if (p->id == id)
            return(p);
        p = (SpriteInfoRecPtr) p->nextSprite;
    }
    p = Sprite_Mgr_Globals->foreSprites;
    while (p) {
        if (p->id == id)
            return(p);
        p = (SpriteInfoRecPtr) p->nextSprite;
    }
    p = Sprite_Mgr_Globals->backSprites;
    while (p) {
        if (p->id == id)
            return(p);
        p = (SpriteInfoRecPtr) p->nextSprite;
    }
    return(0L);
}
 
 
 
 
 
 
 
 
 
 
 
    /*
        This routine is used to find a sprite that needs to share its gworld, since it
        doesn't make sense to duplicate a gworld which is going to use the same as
        another one.  This could happen in a "shotter" game for example.
    */
 
 
SpriteInfoRecPtr FindTwinSprite(short pictID)
{
    SpriteInfoRecPtr p;
    
    p = Sprite_Mgr_Globals->normSprites;
    while (p) {
        if (p->pictID == pictID)
            return(p);
        p = (SpriteInfoRecPtr) p->nextSprite;
    }
    p = Sprite_Mgr_Globals->foreSprites;
    while (p) {
        if (p->pictID == pictID)
            return(p);
        p = (SpriteInfoRecPtr) p->nextSprite;
    }
    p = Sprite_Mgr_Globals->backSprites;
    while (p) {
        if (p->pictID == pictID)
            return(p);
        p = (SpriteInfoRecPtr) p->nextSprite;
    }
    return(0L);
}
 
 
 
 
 
 
 
SceneryInfoRecPtr FindTwinScenery(short pictID)
{
    SceneryInfoRecPtr sc;
    
    sc = Sprite_Mgr_Globals->backgrounds;
    while (sc) {
        if (sc->pictID == pictID)
            return(sc);
        sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
    sc = Sprite_Mgr_Globals->foregrounds;
    while (sc) {
        if (sc->pictID == pictID)
            return(sc);
        sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
    return(0L);
}
 
 
 
 
 
 
void CheckChangedScenery(SceneryInfoRecPtr scenery)
{
    SceneryInfoRecPtr sc = scenery;
    Rect box, dRect;
 
    while (sc) {
        box = sc->sceneryWorld->portRect;
        if (sc->scrollTicks) {
            if (sc->nextTickCount <= TickCount()) {
                sc->scrollOffset.v += sc->autoScrollAmount.v;
                sc->scrollOffset.h += sc->autoScrollAmount.h;
                sc->nextTickCount = TickCount() + sc->scrollTicks;
                sc->changed = true;
                Sprite_Mgr_Globals->redoCheck = true;
                if (sc->scrollOffset.v >= box.bottom)
                    sc->scrollOffset.v = 0;
                if (sc->scrollOffset.h >= box.right)
                    sc->scrollOffset.h = 0;         // wrap around...
            }
        }
        if (sc->changed && (sc->hidden == false)) {
            
            dRect = sc->animationRect;
            MarkSpriteInRect(&dRect, Sprite_Mgr_Globals->backSprites);
            MarkSpriteInRect(&dRect, Sprite_Mgr_Globals->normSprites);
            MarkSpriteInRect(&dRect, Sprite_Mgr_Globals->foreSprites);
            MarkSceneryInRect(&dRect, Sprite_Mgr_Globals->foregrounds);
            MarkSceneryInRect(&dRect, Sprite_Mgr_Globals->backgrounds);
        }
        sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
}
 
 
 
 
 
 
 
 
 
 
 
 
void CheckChangedSprite(SpriteInfoRecPtr sprite)
{
    SpriteInfoRecPtr p = sprite;
    Rect dRect;
 
    while (p) {
        if (p->tickWait) {
            if (p->lastTick <= TickCount()) {
                p->currentFace++;
                if (p->currentFace > p->faces)
                    p->currentFace = 1;
                p->lastTick = TickCount() + p->tickWait;
                p->changed = true;
                Sprite_Mgr_Globals->redoCheck = true;
            }
        }
        if (p->changed && (p->hidden == false)) {
            dRect = p->animationRect;
            MarkSceneryInRect(&dRect, Sprite_Mgr_Globals->backgrounds);
            MarkSceneryInRect(&dRect, Sprite_Mgr_Globals->foregrounds);
            MarkSpriteInRect(&dRect, Sprite_Mgr_Globals->backSprites);
            MarkSpriteInRect(&dRect, Sprite_Mgr_Globals->normSprites);
            MarkSpriteInRect(&dRect, Sprite_Mgr_Globals->foreSprites);
            if (p->oldAnimationRect.right != 0) {
                dRect = p->oldAnimationRect;
                MarkSceneryInRect(&dRect, Sprite_Mgr_Globals->backgrounds);
                MarkSceneryInRect(&dRect, Sprite_Mgr_Globals->foregrounds);
                MarkSpriteInRect(&dRect, Sprite_Mgr_Globals->backSprites);
                MarkSpriteInRect(&dRect, Sprite_Mgr_Globals->normSprites);
                MarkSpriteInRect(&dRect, Sprite_Mgr_Globals->foreSprites);
                p->oldAnimationRect.right = 0;
            }
        }
        p = (SpriteInfoRecPtr) p->nextSprite;
    }
}
 
 
 
 
 
 
 
 
 
 
 
 
void UpdateScenery(SceneryInfoRecPtr scenery)
{
    SceneryInfoRecPtr sc = scenery;
    Rect sRect, dRect;
    WindowPtr g1, g2;
    short h, v;
    Boolean fits;
 
    while (sc) {
        if (sc->changed && (sc->hidden == false)) {
            sc->changed = false;
            fits = true;
            Sprite_Mgr_Globals->changed = true;
            
            sRect = sc->sceneryRect;
            dRect = sc->animationRect;
            if (Sprite_Mgr_Globals->updateRects < kMaxUR) {
                Sprite_Mgr_Globals->changedRect[Sprite_Mgr_Globals->updateRects] = dRect;
                Sprite_Mgr_Globals->updateRects++;
            }
            v = dRect.bottom - dRect.top;
            h = dRect.right - dRect.left;
            if (sRect.right > h)                // is destination smaller ?
                sRect.right = sRect.left + h;   // make source smaller
            if (sRect.bottom > v)
                sRect.bottom = sRect.top + v;
            
            sRect.top += sc->scrollOffset.v;
            sRect.bottom += sc->scrollOffset.v;     // add scroll offsets
            sRect.left += sc->scrollOffset.h;
            sRect.right += sc->scrollOffset.h;
            g1 = (GrafPtr) sc->sceneryWorld;
            g2 = (GrafPtr) Sprite_Mgr_Globals->animationGWorld;
            
            if ((sRect.bottom - sRect.top) < v)
                fits = false;
            if ((sRect.right - sRect.left) < h)
                fits = false;
            if (fits) {
                CopyBits(&(g1->portBits), &(g2->portBits), &sRect, &dRect, sc->copyMode, 0L);
            }
            else {          // source has scrolled too much, so compensate
                h -= sRect.right - sRect.left;
                v -= sRect.bottom - sRect.top;      // get difference
                
                dRect.right -= h;   // clip to area we already have
                dRect.bottom -= v;
                sRect.right -= h;
                sRect.bottom -= v;
                CopyBits(&(g1->portBits), &(g2->portBits), &sRect, &dRect, sc->copyMode, 0L);
                
                if (h) {
                    dRect.right += h;   // now get residue at right
                    dRect.left = dRect.right - h;
                    sRect.left = sc->sceneryRect.left;
                    sRect.right = sRect.left + h;
                    CopyBits(&(g1->portBits), &(g2->portBits), &sRect, &dRect, sc->copyMode, 0L);
                }
                if (v) {
                    dRect.bottom += v;  // now get residue at bottom
                    dRect.top = dRect.bottom - v;
                    sRect.top = sc->sceneryRect.top;
                    sRect.bottom = sRect.top + v;
                    CopyBits(&(g1->portBits), &(g2->portBits), &sRect, &dRect, sc->copyMode, 0L);
                }
            }
        }
        sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
}
 
 
 
 
 
 
 
 
 
 
 
 
void UpdateSprite(SpriteInfoRecPtr sprite)
{
    SpriteInfoRecPtr p = sprite;
    Rect sRect, dRect;
    WindowPtr g1, g2;
 
    while (p) {
        if (p->changed && (p->hidden == false)) {
            p->changed = false;
            Sprite_Mgr_Globals->changed = true;
            
            sRect = p->spriteRect;
            sRect.left = sRect.right * (p->currentFace - 1);  // get the right face...
            sRect.right *= p->currentFace;
            dRect = p->animationRect;
            if (Sprite_Mgr_Globals->updateRects < kMaxUR) {
                Sprite_Mgr_Globals->changedRect[Sprite_Mgr_Globals->updateRects] = dRect;
                Sprite_Mgr_Globals->updateRects++;
            }
            g1 = (GrafPtr) p->spriteWorld;
            g2 = (GrafPtr) Sprite_Mgr_Globals->animationGWorld;
            CopyBits(&(g1->portBits), &(g2->portBits), &sRect, &dRect, p->copyMode, 0L);
        }
        p = (SpriteInfoRecPtr) p->nextSprite;
    }
}
 
 
 
 
 
 
 
 
void MarkSceneryInRect(Rect *box, SceneryInfoRecPtr scenery)
{
    SceneryInfoRecPtr sc;
    Rect sRect;
    Boolean dirty;
    Point pixel;
 
    sc = scenery;
    while (sc) {
        if (sc->changed == false) {
            dirty = false;
            sRect = sc->animationRect;
            
            pixel.h = sRect.left;
            pixel.v = sRect.top;
            if (SMgrPtInRect(pixel, box))
                dirty = true;
            if (!dirty) {
                pixel.h = sRect.left;
                pixel.v = sRect.bottom;
                if (SMgrPtInRect(pixel, box))
                    dirty = true;
            }
            if (!dirty) {
                pixel.h = sRect.right;
                pixel.v = sRect.bottom;
                if (SMgrPtInRect(pixel, box))
                    dirty = true;
            }
            if (!dirty) {
                pixel.h = sRect.right;
                pixel.v = sRect.top;
                if (SMgrPtInRect(pixel, box))
                    dirty = true;
            }
            if (!dirty) {
                if (RectIntersect(&sRect, box))
                    dirty = true;
            }
            if (dirty) {
                sc->changed = true;
                Sprite_Mgr_Globals->redoCheck = true;
            }
        }
        sc = (SceneryInfoRecPtr) sc->nextScenery;
    }
}
 
 
 
 
 
 
void MarkSpriteInRect(Rect *box, SpriteInfoRecPtr sprite)
{
    SpriteInfoRecPtr p = sprite;
    Rect sRect;
    Boolean dirty;
    Point pixel;
 
    while (p) {
        if (p->changed == false) {
            dirty = false;
            sRect = p->animationRect;
            pixel.h = sRect.left;
            pixel.v = sRect.top;
            if (SMgrPtInRect(pixel, box))
                dirty = true;
            if (!dirty) {
                pixel.h = sRect.left;
                pixel.v = sRect.bottom;
                if (SMgrPtInRect(pixel, box))
                    dirty = true;
            }
            if (!dirty) {
                pixel.h = sRect.right;
                pixel.v = sRect.bottom;
                if (SMgrPtInRect(pixel, box))
                    dirty = true;
            }
            if (!dirty) {
                pixel.h = sRect.right;
                pixel.v = sRect.top;
                if (SMgrPtInRect(pixel, box))
                    dirty = true;
            }
            if (!dirty) {
                if (RectIntersect(&sRect, box))
                    dirty = true;
            }
            if (dirty) {
                p->changed = true;
                Sprite_Mgr_Globals->redoCheck = true;
            }
        }
        p = (SpriteInfoRecPtr) p->nextSprite;
    }
}
 
 
 
 
 
 
 
 
 
void CopyGToScreen(void)
{
    short id;
    SpriteInfoRecPtr p = 0L;
    SceneryInfoRecPtr sc = 0L;
    Boolean done = false;
    GrafPtr g1, g2;
    Rect sRect, dRect;
    short mode;
    
    DebugStr("\pSet ID?");
    id = 0;
    p = FindSprite(id);
    if (!p) {
        sc = Sprite_Mgr_Globals->backgrounds;
        while (sc && !done) {
            if (sc->id == id)
                done = true;
            else
                sc = (SceneryInfoRecPtr) sc->nextScenery;
        }
        if (!sc) {
            sc = Sprite_Mgr_Globals->foregrounds;
            while (sc && !done) {
                if (sc->id == id)
                    done = true;
                else
                    sc = (SceneryInfoRecPtr) sc->nextScenery;
            }
        }
        if (!done)
            return;
    }
    if (p) {
        g1 = (GrafPtr) p->spriteWorld;
        dRect = sRect = p->spriteRect;
        mode = p->copyMode;
    }
    else {
        g1 = (GrafPtr) sc->sceneryWorld;
        dRect = sRect = sc->sceneryWorld->portRect;
        mode = sc->copyMode;
    }
    g2 = (GrafPtr) Sprite_Mgr_Globals->animationWindow;
    CopyBits(&(g1->portBits), &(g2->portBits), &sRect, &dRect, mode, 0L);
 
}
 
 
 
 
 
 
 
 
 
Boolean SMgrPtInRect(Point pixel, Rect *box)
{
    if (pixel.h < box->left)
        return(false);
    if (pixel.h > box->right)
        return(false);
    if (pixel.v < box->top)
        return(false);
    if (pixel.v > box->bottom)
        return(false);
    return(true);
}
 
 
 
 
 
 
 
Boolean RectIntersect(Rect *r, Rect *box)
{
    Rect inter; /* register short d;
    
    d = 0;
    if ((r->left > box->left) && (r->left < box->right))
        d = r->left;
    if ((box->left > r->left) && (box->left < r->right))
        d = box->left;
    if (d == 0)
        return(false);
    
    d = 0;
    if ((r->top > box->top) && (r->top < box->bottom))
        d = r->top;
    if ((box->top > r->top) && (box->top < r->bottom))
        d = box->top;
    if (d == 0)
        return(false);
    
    d = 0;
    if ((r->right < box->right) && (r->right > box->left))
        d = r->right;
    if ((box->right < r->right) && (box->right > r->left))
        d = box->right;
    if (d == 0)
        return(false);
    
    d = 0;
    if ((r->bottom < box->bottom) && (r->bottom > box->top))
        d = r->bottom;
    if ((box->bottom < r->bottom) && (box->bottom > r->top))
        d = box->bottom;
    if (d == 0)
        return(false); */
    
    if (SectRect(r, box, &inter))       // make my own
        return(true);
    return(false);
}