namespace ScrollBG{
//CONFIG SCROLLINGLAYER_DEBUG = 0; //Set to 1 to show positions of onscreen layers
//int this->Data[272]; //Size should be at least _SBG_START + 6 * _SBG_BLOCK
//Global array indices
enum {_SBG_LINKX =0,_SBG_LINKY, _SBG_VIEWPORTX, _SBG_VIEWPORTY, _SBG_OLD_SCREEN_ID};
//Start index of the 2D part of the global array and block size
const int _SBG_START = 16;
const int _SBG_BLOCK = 16;
//Indices for scrolling layer properties
enum{_SBG_X=0,_SBG_Y, _SBG_XSTEP, _SBG_YSTEP, _SBG_MAP, _SBG_SCREEN, _SBG_LAYER, _SBG_WIDTH, _SBG_HEIGHT, _SBG_OPACITY, _SBG_FLAGS, _SBG_DMAP};
//Flag constants
@Bitflags("int")
enum{_SBGF_NORESET = 00000001b,_SBGF_NOPOSITION, _SBGF_TRACKX, _SBGF_TRACKY, _SBGF_TRACKXSCROLL, _SBGF_TRACKYSCROLL, _SBGF_TRACK_VIEWPORT, _SBGF_FORCEDRAWSCREEN};
//Generic script that runs the main code. Must run at quest start and restart on Level/DMap change, F6 and Reload.
@InitD0("Number of Slots"), @InitDHelp0("Number of scrolling Background Slots. \n Fewer slots - better performance."),
@InitD1("Debug scrolling"), @InitDHelp1("Set to 1 to show various debug information. \n See lines 181 - 195 for more info.")
generic script ScrollingLayer{
void run(int NumSlots, int SCROLLINGLAYER_DEBUG){
Screen->DrawOrigin = DRAW_ORIGIN_PLAYING_FIELD;
this->DataSize = _SBG_START + _SBG_BLOCK*NumSlots;
ScrollingLayer_Init(this);
while(true){
ScrollingLayer_Update(this, SCROLLINGLAYER_DEBUG);
Waitframe();
}
}
}
void ScrollingLayer_Init(genericdata this){
int i; int j; int k;
for(i=0; i<this->InitD[0]; ++i){
j = _SBG_START+i*_SBG_BLOCK;
//If the layer hasn't been assigned by an FFC this frame, clear it on F6
if((this->Data[j+_SBG_FLAGS]&_SBGF_NORESET)==0){
for(k=0; k<_SBG_BLOCK; ++k){
this->Data[j+k] = 0;
}
}
}
this->Data[_SBG_LINKX] = GlobalHeroX(this);
this->Data[_SBG_LINKY] = GlobalHeroY(this);
this->Data[_SBG_VIEWPORTX] = GlobalViewportX(this);
this->Data[_SBG_VIEWPORTY] = GlobalViewportY(this);
}
void ScrollingLayer_Update(genericdata this, int SCROLLINGLAYER_DEBUG){
int i; int j; int k;
int x; int y; int w; int h;
bool trackLinkX; bool trackScrollX;
bool trackLinkY; bool trackScrollY;
int dX = 0;
int dY = 0;
int dvX = 0;
int dvY = 0;
int dhX = 0;
int dhY = 0;
//Get delta for viewport and hero coordinates.
if (Game->Scrolling[SCROLL_DIR]==-1)this->Data[_SBG_OLD_SCREEN_ID] = Game->CurScreen;
int NewViewportX = GlobalViewportX(this);
int NewViewportY = GlobalViewportY(this);
int NewHeroX = GlobalHeroX(this);
int NewHeroY = GlobalHeroY(this);
dvX = (NewViewportX-this->Data[_SBG_VIEWPORTX]);
dvY = (NewViewportY-this->Data[_SBG_VIEWPORTY]);
this->Data[_SBG_VIEWPORTX] =NewViewportX;
this->Data[_SBG_VIEWPORTY] =NewViewportY;
dhX = (NewHeroX-this->Data[_SBG_LINKX]);
dhY = (NewHeroY-this->Data[_SBG_LINKY]);
this->Data[_SBG_LINKX] = NewHeroX;
this->Data[_SBG_LINKY] = NewHeroY;
for(i=0; i<this->InitD[0]; ++i){
j = _SBG_START+i*_SBG_BLOCK;
//Skip drawing, if source map is ivalid, opacity is set to 0 (invisible), DMap is not vurrent DMap, or Hero in in passageway or item cellar (Screens>=0x80).
if (!this->Data[j+_SBG_MAP]) continue;
if (!this->Data[j+_SBG_OPACITY]) continue;
if (this->Data[j+_SBG_DMAP]!=Game->CurDMap) continue;
if (Game->HeroScreen>= 0x80)continue;
//determine if this layer tracks player or viewport coordinates.
if(this->Data[j+_SBG_FLAGS]&_SBGF_TRACK_VIEWPORT){
dX = dvX;
dY = dvY;
}
else {
dX = dhX;
dY = dhY;
}
//Check the rest of scrolling layer flag.
if(this->Data[j+_SBG_FLAGS]&_SBGF_TRACKX)
trackLinkX = true;
if(this->Data[j+_SBG_FLAGS]&_SBGF_TRACKY)
trackLinkY = true;
if(this->Data[j+_SBG_FLAGS]&_SBGF_TRACKXSCROLL)
trackScrollX = true;
if(this->Data[j+_SBG_FLAGS]&_SBGF_TRACKYSCROLL)
trackScrollY = true;
w = 256*this->Data[j+_SBG_WIDTH];
h = 176*this->Data[j+_SBG_HEIGHT];
//Process scrolling animation
//X-axis
if(Game->Scrolling[SCROLL_DIR]>=0){
//During the scroll animation, move the background if set to track scrolling
if(trackScrollX){
this->Data[j+_SBG_X] += this->Data[j+_SBG_XSTEP]*dX;
}
//Scroll automatically if neither Link nor scrolling are being tracked
else if(!trackLinkX){
this->Data[j+_SBG_X] += this->Data[j+_SBG_XSTEP];
}
}
else{
//Only move when Link does if tracking him
if(trackLinkX){
this->Data[j+_SBG_X] += this->Data[j+_SBG_XSTEP]*dX;
}
else if(!trackScrollX) //Otherwise scroll automatically
this->Data[j+_SBG_X] += this->Data[j+_SBG_XSTEP];
}
//Y-axis
if(Game->Scrolling[SCROLL_DIR]>=0){
//During the scroll animation, move the background if set to track scrolling
if(trackScrollY){
this->Data[j+_SBG_Y] += this->Data[j+_SBG_YSTEP]*dY;
}
//Scroll automatically if neither Link nor scrolling are being tracked
else if(!trackLinkY){
this->Data[j+_SBG_Y] += this->Data[j+_SBG_YSTEP];
}
}
else{
//Only move when Link does if tracking him
if(trackLinkY){
this->Data[j+_SBG_Y] += this->Data[j+_SBG_YSTEP]*dY;
}
else if(!trackScrollY)
this->Data[j+_SBG_Y] += this->Data[j+_SBG_YSTEP];
}
//Keep the X and Y positions of the background wrapped based on size
if(this->Data[j+_SBG_X]<0)
this->Data[j+_SBG_X] += w;
if(this->Data[j+_SBG_X]>=w)
this->Data[j+_SBG_X] -= w;
if(this->Data[j+_SBG_Y]<0)
this->Data[j+_SBG_Y] += h;
if(this->Data[j+_SBG_Y]>=h)
this->Data[j+_SBG_Y] -= h;
x = this->Data[j+_SBG_X];
y = this->Data[j+_SBG_Y];
//All set. Time to perform actual drawing.
ScrollingLayer_DrawLayer(this->Data[j+_SBG_LAYER], this->Data[j+_SBG_MAP], this->Data[j+_SBG_SCREEN], x, y, this->Data[j+_SBG_WIDTH], this->Data[j+_SBG_HEIGHT], this->Data[j+_SBG_OPACITY], this->Data[j+_SBG_FLAGS]&_SBGF_FORCEDRAWSCREEN);
//debug rendering
if(SCROLLINGLAYER_DEBUG){
Screen->DrawInteger(6, 24*i, 0, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, x, 0, OP_OPAQUE);
Screen->DrawInteger(6, 24*i, 8, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, y, 0, OP_OPAQUE);
Screen->DrawInteger(6, 24*i, 16, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, this->Data[j+_SBG_FLAGS], 0, OP_OPAQUE);
Screen->DrawInteger(6, 12, 24, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, NewViewportX, 0, OP_OPAQUE);
Screen->DrawInteger(6, 12, 32, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, NewViewportY, 0, OP_OPAQUE);
Screen->DrawInteger(6, 36, 24, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, Viewport->X, 0, OP_OPAQUE);
Screen->DrawInteger(6, 36, 32, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, Viewport->Y, 0, OP_OPAQUE);
Screen->DrawInteger(6, 60, 24, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, NewHeroX, 0, OP_OPAQUE);
Screen->DrawInteger(6, 60, 32, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, NewHeroY, 0, OP_OPAQUE);
Screen->DrawInteger(6, 84, 24, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, dX, 0, OP_OPAQUE);
Screen->DrawInteger(6, 84, 32, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, dY, 0, OP_OPAQUE);
Screen->DrawInteger(6, 108, 24, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, Game->Scrolling[SCROLL_NX], 0, OP_OPAQUE);
Screen->DrawInteger(6, 108, 32, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, Game->Scrolling[SCROLL_NY], 0, OP_OPAQUE);
Screen->DrawInteger(6, 12, 40, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, Region->OriginScreenIndex, 0, OP_OPAQUE);
Screen->DrawInteger(6, 36, 40, FONT_Z3SMALL, 0x01, 0x0F, -1, -1, Game->Scrolling[SCROLL_DIR], 0, OP_OPAQUE);
}
//Unmark flags telling the global not to reset it and the FFC not to reposition it
// if(this->Data[j+_SBG_FLAGS]&_SBGF_NORESET)
// this->Data[j+_SBG_FLAGS] &= ~_SBGF_NORESET;
// if(this->Data[j+_SBG_FLAGS]&_SBGF_NOPOSITION)
// this->Data[j+_SBG_FLAGS] &= ~_SBGF_NOPOSITION;
}
}
int ScreenAt(int X, int Y){
if (Game->Scrolling[SCROLL_DIR]>=0) return Game->HeroScreen;
int screenX = (X - X%256)/256;
int screenY = (Y - Y%176)/176;
int origScreen = Game->CurScreen;
return origScreen + 16*screenY + screenX;
}
int GlobalHeroX(genericdata this){
if (Game->Scrolling[SCROLL_DIR]>=0) return this->Data[_SBG_LINKX];
int X = CenterLinkX()%256;
int screenID = ScreenAt(CenterLinkX(), CenterLinkY());
int ScreenX = screenID%16;
return ScreenX*256 + X;
}
int GlobalHeroY(genericdata this){
if (Game->Scrolling[SCROLL_DIR]>=0) return this->Data[_SBG_LINKY];
int Y = CenterLinkY() % 176;
int screenID = ScreenAt(CenterLinkX(), CenterLinkY());
int ScreenY = Floor(screenID/16);
return ScreenY*176+Y;
}
int GlobalViewportX(genericdata this){
return ((this->Data[_SBG_OLD_SCREEN_ID]%16)*256)+Viewport->X;
}
int GlobalViewportY(genericdata this){
return (Floor(this->Data[_SBG_OLD_SCREEN_ID]/16)*176)+Viewport->Y;
}
void ScrollingLayer_DrawLayer(int layer, int srcMap, int srcScreen, int x, int y, int w, int h, int op, bool forceDrawScreen){
bool useDrawScreen = false;
if(forceDrawScreen) useDrawScreen = true;
int tmpScreen;
int scrnX;
int scrnY;
for(int i=0; i<4; ++i){
scrnX = Floor(x/256)+(i%2);
scrnY = Floor(y/176)+Floor(i/2);
if(scrnX<0)
scrnX += w;
if(scrnX>w-1)
scrnX -= w;
if(scrnY<0)
scrnY += h;
if(scrnY>h-1)
scrnY -= h;
tmpScreen = srcScreen+scrnX+scrnY*16;
if(useDrawScreen){
Screen->DrawScreen(layer, srcMap, tmpScreen, -(x%256)+256*(i%2), -(y%176)+176*Floor(i/2), 0);
}
else{
Screen->DrawLayer(layer, srcMap, tmpScreen, 0, -(x%256)+256*(i%2), -(y%176)+176*Floor(i/2), 0, op);
}
}
}
//This combo script registers scrolling layer to given slot, overwriting previous data in this slot.
@Attribute0("Scroll Slot"), @AttributeHelp0("Which scroll slot this scrolling layer uses. (0-5)"),
@Attribute1("Draw Layer"), @AttributeHelp1("Which layer used to render scrolling background. (0-7)"),
@Attribute2("Source Map"), @AttributeHelp2("Which map used to draw scrolling layer from."),
@Attribute3("Source Screen"), @AttributeHelp3("Which screen ID used to draw scrolling layer from. \n In case of using larger than 1x1 block of screens, ID is top left corner of that block."),
@Attribute4("X Speed"), @AttributeHelp4("Background horizontal scrolling speed, in pixels per frame.\n In case of tracking scrolling, hero or viewport coordinates, this attribute is multiple of object speed."),
@Attribute5("Y Speed"), @AttributeHelp5("Background vertical scrolling speed, in pixels per frame.\n In case of tracking scrolling, hero or viewport coordinates, this attribute is multiple of object speed."),
@Attribute6("Opacity"), @AttributeHelp6("Background opacity. \n 0 - Invisible, 64 - Translucent, 128 - Opaque."),
@Attribute7("Init X"), @AttributeHelp7("Background scroll ininial X coordinate, relative to top left corner of the screen."),
@Attribute8("Init Y"), @AttributeHelp8("Background scroll ininial Y coordinate, relative to top left corner of the screen."),
@Attribute9("Width"), @AttributeHelp9("Background scroll width, in screens."),
@Attribute10("Height"), @AttributeHelp10("Background scroll height, in screens."),
@Flag0("Track Hero X"), @FlagHelp0("If set, this scroll slot tracks Hero`s X coordinate and adjusts scroll position accorfing to that."),
@Flag1("Track Hero Y"), @FlagHelp1("If set, this scroll slot tracks Hero`s Y coordinate and adjusts scroll position accorfing to that."),
@Flag2("Track Scroll X"), @FlagHelp2("If set, this scroll slot tracks X coordinate during trans-region scrolling and adjusts scroll position according to that."),
@Flag3("Track Scroll Y"), @FlagHelp3("If set, this scroll slot tracks Y coordinate during trans-region scrolling and adjusts scroll position according to that."),
@Flag4("Use Screen->DrawScreen"), @FlagHelp4("If set, this scroll slot always uses Screen->DrawScreen to render backround."),
@Flag5("No reposition"), @FlagHelp5("If set, don`t reposition background, if it is already assigned and is running, \n when player re-enters screen with this combo placed without exiting DMap."),
@Flag6("Track Viewport"), @FlagHelp6("If set, tracks viewport, instead of hero`s actual coordinates. \n Best used in scrolling regions."),
@Flag7("Continue Point Offset"), @FlagHelp7("If set, initial position of scrolling background will be offset depending on delta between warp coordinates.\n Does not work unless Screen->Data->No Continue Here After Warp flag is set.")
combodata script RegisterScrollingLayer{
void run(){
int whichScrollingLayer = this->Attributes[0];
whichScrollingLayer = Clamp(whichScrollingLayer, 0, 5);
int drawLayer = this->Attributes[1];
int srcMap = this->Attributes[2];
int srcScreen = this->Attributes[3];
int xStep = this->Attributes[4];
int yStep = this->Attributes[5];
int opacity = this->Attributes[6];
int initX = this->Attributes[7];
int initY = this->Attributes[8];
int width = this->Attributes[9];
int height = this->Attributes[10];
bool trackX = this->Flags[0];
bool trackY = this->Flags[1];
bool trackScrollX = this->Flags[2];
bool trackScrollY = this->Flags[3];
bool ForceDrawScreen = this->Flags[4];
bool NoReposition = this->Flags[5];
bool TrackViewport = this->Flags[6];
bool OffsetContinuePoint = this->Flags[7];
int str[] = "ScrollingLayer";
int scr = Game->GetGenericScript(str);
genericdata gd = Game->LoadGenericData(scr);
//Trace(gd-> DataSize);
if (gd->DataSize<(_SBG_START+(whichScrollingLayer+1)*_SBG_BLOCK)){
int err[] = "ScrollingLayer: Slot number (Combo Attribute 0) exceeds number of available slots (0-indexed). See InitD[0] in Scrolling Layer generic script. Also that script must have Run from Start checkbox on and Reload states checkboxes set for Reload, Continue, Change DMap and Change Level.";
TraceS(err);
Quit();
}
dmapdata dm = Game->LoadDMapData(Game->CurDMap);
int X = GlobalHeroX(gd);
int Y = GlobalHeroY(gd);
int origScreen = MapToDMap(dm->Continue, Game->CurDMap);
mapdata md = Game->LoadMapData(Game->CurMap,origScreen);
int origX = (origScreen%16)*256+md->WarpReturnX[0];
int origY = (Floor(origScreen/16))*176+md->WarpReturnY[0];
int deltaX = X - origX;
int deltaY = Y - origY;
int i = _SBG_START+whichScrollingLayer*_SBG_BLOCK;
int j = 0;
if (!NoReposition || gd->Data[i+_SBG_MAP]!=srcMap){
if (Screen->Flag[SFL_NO_CONTINUE_HERE] && OffsetContinuePoint){
initX += deltaX*xStep;
initY += deltaY*yStep;
}
gd->Data[i+_SBG_X] = initX;
gd->Data[i+_SBG_Y] = initY;
}
gd->Data[i+_SBG_XSTEP] = xStep;
gd->Data[i+_SBG_YSTEP] = yStep;
gd->Data[i+_SBG_MAP] = srcMap;
gd->Data[i+_SBG_SCREEN] = srcScreen;
gd->Data[i+_SBG_LAYER] = drawLayer;
gd->Data[i+_SBG_OPACITY] = opacity;
srcMap = Clamp(srcMap, 1, 256);
width = Clamp(width, 1, 16);
height = Clamp(height, 1, 8);
gd->Data[i+_SBG_WIDTH] = width;
gd->Data[i+_SBG_HEIGHT] = height;
//Assign the flag that tells the global not to reset gd index, as well as any others specified
gd->Data[i+_SBG_FLAGS]=0;
if(trackX)
gd->Data[i+_SBG_FLAGS] |= _SBGF_TRACKX;
if(trackY)
gd->Data[i+_SBG_FLAGS] |= _SBGF_TRACKY;
if(trackScrollX)
gd->Data[i+_SBG_FLAGS] |= _SBGF_TRACKXSCROLL;
if(trackScrollY)
gd->Data[i+_SBG_FLAGS] |= _SBGF_TRACKYSCROLL;
if(ForceDrawScreen)
gd->Data[i+_SBG_FLAGS] |= _SBGF_FORCEDRAWSCREEN;
if (TrackViewport)
gd->Data[i+_SBG_FLAGS] |= _SBGF_TRACK_VIEWPORT;
gd->Data[i+_SBG_DMAP] = Game->CurDMap;
}
}
//Clears the giver srolling background slot
@Attribute0("Scroll Slot"), @AttributeHelp0("Which scroll slot to clear. (0-5)")
combodata script ClearScrollingLayer{
void run(){
int whichScrollingLayer = this->Attributes[0];
int i = _SBG_START+whichScrollingLayer*_SBG_BLOCK;
int j = 0;
int str[] = "ScrollingLayer";
int scr = Game->GetGenericScript(str);
genericdata gd = Game->LoadGenericData(scr);
gd->Data[i+_SBG_X] = 0;
gd->Data[i+_SBG_Y] = 0;
gd->Data[i+_SBG_XSTEP] = 0;
gd->Data[i+_SBG_YSTEP] = 0;
gd->Data[i+_SBG_MAP] = 0;
gd->Data[i+_SBG_SCREEN] = 0;
gd->Data[i+_SBG_LAYER] = 0;
gd->Data[i+_SBG_OPACITY] = 0;
gd->Data[i+_SBG_WIDTH] = 1;
gd->Data[i+_SBG_HEIGHT] = 1;
gd->Data[i+_SBG_FLAGS] = 0;
gd->Data[i+_SBG_DMAP] = 0;
}
}
}