Quest of Love is an Amiga project that I never finished, but always wanted to create…
The project started as an assembly language project, but it was too much work, so I switched to C language. The video below shows, what I managed to do:
For curiosity and historical — on the other hand educational — reasons, I’ve decided to publish the C language source I have for this project. The assembly version is published in this old blog post..
The comments and names of some variables are in Finnish..
#include <math.h> #include <exec/types.h> #include <exec/memory.h> #include <graphics/gfx.h> #include <graphics/displayinfo.h> #include <graphics/modeid.h> #include <intuition/intuition.h> #include <dos/stdio.h> #include <dos/dos.h> #include <proto/exec.h> #include <clib/dos_protos.h> #include <clib/graphics_protos.h> #include <clib/intuition_protos.h> #include <libraries/iff.h> #define INTUI_V38_NAMES_ONLY #define LIBVER 39 #define TURN 83 struct IntuitionBase *IntuitionBase=NULL; struct GfxBase *GfxBase=NULL; struct Library *IFFBase=NULL; struct Screen *screen=NULL; struct Window *window=NULL; struct ViewPort *viewport; struct RastPort *rastport; ULONG errorcode; unsigned char *BM_block=NULL, *BM0, *BM1; unsigned char *source0, *source1, *source2, *source3, *source4; unsigned char *dest0, *dest1, *dest2, *dest3, *dest4; struct BitMap blockbitmap = {2, 1088, 0, 5, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; struct BitMap myBitMap0 = {40, 256, 0, 5, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; struct BitMap myBitMap1 = {40, 256, 0, 5, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; struct BitMap *DrawBitMap=NULL,*ShowBitMap=NULL; struct Monster { int x,y; WORD block; WORD vital; WORD exp; char frame; char delay; char elossa; }; struct Monster Monsters[21]; struct Monster MonType[1]; int Blocked(int x, int y, short suunta); void DoubleBuffering(short *); void DrawMap(UWORD *Map); void DrawBlock(int,int,int); void Cycle(void); void HandleMonsters(void); void InitMonsters(void); void SetDelay(WORD,WORD); void DeAllocateResources(); char ReadMapFile(char [], UWORD *buffer, int lkm); int cind, block, k, j; UWORD colortable[32]; UWORD copytable[4]; UWORD *buffer=NULL; LONG count; UWORD *Map = NULL; UWORD *WorldMap = NULL; int map_width, map_heigth; WORD MapX=0, MapY=0,isoX=0,isoY=0; char delay=-1; char turn=TURN; int main(void) { short keepgoing=TRUE; struct IntuiMessage *msg; ULONG class; UWORD code,qualifier; IFFL_HANDLE iff=NULL; short *Which; if((GfxBase=(struct GfxBase *) OpenLibrary("graphics.library",LIBVER))==NULL) { printf("graphics open failed (V39 kickstart required)\n"); DeAllocateResources(); exit(1); } if((IntuitionBase=(struct IntuitionBase *) OpenLibrary("intuition.library",LIBVER))==NULL) { printf("intuition open failed (V39 mkickstart required)\n"); DeAllocateResources(); exit(1); } IFFBase=(struct Library*)OpenLibrary(IFFNAME, IFFVERSION); if (IFFBase == NULL) { printf("Can not open iff.library\n"); DeAllocateResources(); exit(1); } BM_block = (unsigned char *)AllocMem(11520, MEMF_CHIP); if (!BM_block) { printf("Memory allocation failed\n"); DeAllocateResources(); exit(1); } BM0 = (unsigned char *)AllocMem(51200,MEMF_CHIP|MEMF_CLEAR); if (!BM0) { printf("Memory allocation failed\n"); DeAllocateResources(); exit(1); } BM1 = (unsigned char *)AllocMem(51200,MEMF_CHIP|MEMF_CLEAR); if (!BM1) { printf("Memory allocation failed\n"); DeAllocateResources(); exit(1); } buffer=(UWORD *)AllocMem(40, MEMF_CHIP); if (!buffer) { printf("Memory allocation failed\n"); DeAllocateResources(); exit(1); } InitMonsters(); blockbitmap.Planes[0]=&BM_block[0]; blockbitmap.Planes[1]=&BM_block[2176]; blockbitmap.Planes[2]=&BM_block[4352]; blockbitmap.Planes[3]=&BM_block[6528]; blockbitmap.Planes[4]=&BM_block[8704]; myBitMap0.Planes[0]=&BM0[0]; myBitMap0.Planes[1]=&BM0[10240]; myBitMap0.Planes[2]=&BM0[20480]; myBitMap0.Planes[3]=&BM0[30720]; myBitMap0.Planes[4]=&BM0[40960]; myBitMap1.Planes[0]=&BM1[0]; myBitMap1.Planes[1]=&BM1[10240]; myBitMap1.Planes[2]=&BM1[20480]; myBitMap1.Planes[3]=&BM1[30720]; myBitMap1.Planes[4]=&BM1[40960]; *Which=1; if((screen=OpenScreenTags(NULL, SA_Width, 320, SA_Height, 256, SA_Depth, 5, SA_Overscan, FALSE, SA_AutoScroll, TRUE, SA_Type, CUSTOMSCREEN|CUSTOMBITMAP, SA_DisplayID,PAL_MONITOR_ID, SA_PubName, "My Screen", SA_BitMap, &myBitMap0, TAG_END, NULL) )==NULL) { printf("Screen open failed\n"); DeAllocateResources(); exit(1); } if((window=OpenWindowTags(NULL, WA_Left, 0, WA_Top, 0, WA_Width, screen->Width, WA_Height, screen->Height, WA_IDCMP,IDCMP_VANILLAKEY, WA_Flags,WFLG_ACTIVATE|WFLG_RMBTRAP|WFLG_BORDERLESS, WA_ReportMouse, TRUE, WA_MinWidth, 50, WA_MinHeight, 20, WA_MaxWidth, ~0, WA_MaxHeight, ~0, WA_PubScreen, screen, TAG_END, NULL) )==NULL) { printf("Window open failed\n"); DeAllocateResources(); exit(1); } viewport=&screen->ViewPort; rastport=window->RPort; /* Bittikartoille on aiemmin varattu tila, mikä pitää laskea uudestaan, jos koko muuttuu */ iff = IFFL_OpenIFF("Work:MyProjects/QoL/New/gfx/blocks16x1152.iff",IFFL_MODE_READ); if (!iff) { printf("IFF file open failed\n"); DeAllocateResources(); exit(1); } if (!(IFFL_DecodePic(iff, &blockbitmap))) { printf("IFF file decoding failed\n"); DeAllocateResources(); exit(1); } count = IFFL_GetColorTab(iff, colortable); LoadRGB4(&(screen->ViewPort), colortable, count); IFFL_CloseIFF(iff); iff = IFFL_OpenIFF("Work:MyProjects/QoL/IFFs/NewFrame.iff",IFFL_MODE_READ); if (!iff) { printf("IFF file open failed\n"); DeAllocateResources(); exit(1); } if (!(IFFL_DecodePic(iff,&myBitMap0))) { printf("IFF file decoding failed\n"); DeAllocateResources(); exit(1); } IFFL_CloseIFF(iff); for (int i=0; i<4; i++) copytable[i]=colortable[16+i]; BltBitMap(&myBitMap0,0,0,&myBitMap1,0,0,320,256,0xc0,0x1f,&buffer); source0 = blockbitmap.Planes[0]; source1 = blockbitmap.Planes[1]; source2 = blockbitmap.Planes[2]; source3 = blockbitmap.Planes[3]; source4 = blockbitmap.Planes[4]; dest0 = screen->BitMap.Planes[0]+1001; dest1 = screen->BitMap.Planes[1]+1001; dest2 = screen->BitMap.Planes[2]+1001; dest3 = screen->BitMap.Planes[3]+1001; dest4 = screen->BitMap.Planes[4]+1001; WorldMap=(UWORD *)AllocMem(9920,MEMF_PUBLIC); if (WorldMap == NULL) { printf("Could not allocate memory for WorldMap\n"); DeAllocateResources(); exit(1); } if ((ReadMapFile("Work:Myprojects/QoL/Gamefield/Map11.gamefield",WorldMap,9920)) == 1) { DeAllocateResources(); exit(1); } Map = WorldMap; map_width = 80; map_heigth = 62; /* -------------------------------------------------------------------------- */ while(keepgoing) { WaitTOF(); DoubleBuffering(&Which); DrawMap(Map); HandlePlayer(); HandleMonsters(); GeneralRoutines(); Cycle(); /* printf("Delay %d ; Turn %d\n",delay,turn); */ /* printf("Blocked %d\n",Blocked(MapX, MapY, 8)); */ while(msg=(struct IntuiMessage *)GetMsg(window->UserPort)) { class=msg->Class; code=msg->Code; qualifier=msg->Qualifier; ReplyMsg((struct Message *)msg); /* printf("%d\n",code); */ switch(class) { case IDCMP_VANILLAKEY : switch (code) { case 27 : /* ESC */ keepgoing=FALSE; DeAllocateResources(); break; /*** * Pelaajan ohjaus pääkartalla. Aluksi katsotaan, onko kulkusuunta blokattu; * jos ei ole ja delay == -1 (eli maastotyypin viive on suoritettu), niin * liikutaan * painettuun kulkusuuntaan ja asetetaan uusi delay. Lisäksi tulostetaan * joko kulkusuunta tai "Slow progress", jos viiveen takia kulkusuuntaan * ei voinut mennä. Jos tulostettiin "Slow progress", niin delay-arvoa * vähennetään yhdellä. ***/ case '2' : if (!Blocked(MapX, MapY, 2) && delay<0) { MapY++; isoY++; if (MapY> map_heigth - 1) MapY=0; SetDelay(MapX,MapY); Print("South.",6); } else if (delay>-1) { Print("Slow progress.",14); delay--; } turn=TURN; break; case '8' : if (!Blocked(MapX, MapY, 8) && delay<0) { MapY--; isoY--; if (MapY<0) MapY= map_heigth - 1; SetDelay(MapX,MapY); Print("North.",6); } else if (delay>-1) { Print("Slow progress.",14); delay--; } turn=TURN; break; case '6' : if (!Blocked(MapX, MapY, 6) && delay<0) { MapX++; isoX++; if (MapX> map_width - 1) MapX=0; SetDelay(MapX,MapY); Print("East.",5); } else if (delay>-1) { Print("Slow progress.",14); delay--; } turn=TURN; break; case '4' : if (!Blocked(MapX, MapY, 4) && delay<0) { MapX--; isoX--; if (MapX<0) MapX= map_width - 1; SetDelay(MapX,MapY); Print("West.",5); } else if (delay>-1) { Print("Slow progress.",14); delay--; } turn=TURN; break; case ' ' : turn=TURN; Print("Pass.",5); break; } } } } } /* END main */ void DeAllocateResources() { if (buffer) FreeMem(buffer,40); /* if (iff) IFFL_CloseIFF(iff); */ /* if (BM0) FreeMem(BM0,51200); if (BM1) FreeMem(BM1,51200); */ if (BM_block) FreeMem(BM_block,11520); if (window) CloseWindow(window); if (screen) CloseScreen(screen); if (BM0) FreeMem(BM0,51200); if (BM1) FreeMem(BM1,51200); if (GfxBase) CloseLibrary((struct Library *)GfxBase); if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase); if (IFFBase) CloseLibrary(IFFBase); if (WorldMap) FreeMem(WorldMap,9920); } void HandlePlayer(void) { DrawBlock(5,6,10); } void SetDelay(WORD x,WORD y) { x+=5; y+=6; if (x > map_width - 1) x=x - map_width; if (y > map_heigth - 1) y=y - map_heigth; /* printf("%d;%d; %d\n",x,y,Map[x+y*80]); */ switch(Map[x+y*map_width]) { case 5 : delay=(char)Random(0,4); break; case 16: delay=(char)Random(0,2); break; default: delay=-1; } /* printf("%d\n",delay); */ } void DoubleBuffering(short *Which) { switch(*Which) { case 0 : viewport->RasInfo->BitMap=&myBitMap0; *Which=1; rastport->BitMap=&myBitMap1; dest0 = myBitMap1.Planes[0]+1001; dest1 = myBitMap1.Planes[1]+1001; dest2 = myBitMap1.Planes[2]+1001; dest3 = myBitMap1.Planes[3]+1001; dest4 = myBitMap1.Planes[4]+1001; DrawBitMap=&myBitMap1; ShowBitMap=&myBitMap0; break; case 1 : viewport->RasInfo->BitMap=&myBitMap1; *Which=0; rastport->BitMap=&myBitMap0; dest0 = myBitMap0.Planes[0]+1001; dest1 = myBitMap0.Planes[1]+1001; dest2 = myBitMap0.Planes[2]+1001; dest3 = myBitMap0.Planes[3]+1001; dest4 = myBitMap0.Planes[4]+1001; DrawBitMap=&myBitMap0; ShowBitMap=&myBitMap1; break; } ScrollVPort(viewport); } /*** * Blocked-rutiini: Parametreina saadaan näkymän vasen yläkulma, mihin lisätään * arvot, joilla saadan pelaajan sijainti eli näkymän keskikohta. Jos suunnassa, * johon ollaan menossa on blocki, jonka päälle ei voi mennä palautetaan 1, * mikä on merkkinä siitä, että hahmoa ei liikuteta, muutoin 0. ***/ int Blocked (int x, int y, short suunta) { int arvo; x+=5; y+=6; if (x > map_width - 1) x=x-map_width; if (y> map_heigth - 1) y=y- map_heigth; /* printf("Block: %d\n", Map[x+y*map_width]); */ switch (suunta) { case 2 : y+=1; break; case 8 : y-=1; break; case 6 : x+=1; break; case 4 : x-=1; break; } /* printf("Block: %d\n", Map[x+y*map_width]); */ switch (Map[x+y*map_width]) { case 3 : arvo = 1; break; case 8 : arvo = 1; break; case 12 : arvo = 1; break; case 14 : arvo = 1; break; default : arvo = 0; } return arvo; } void DrawMap (UWORD *Map) { /* printf("DrawMap"); */ int x,y,xg=0,yg=0,xx,yy; for (y=MapY,yg=0;y<MapY+14;y++,yg++) { for (x=MapX,xg=0;x<MapX+11;x++,xg++) { /* printf("xx: %d", xx); */ if (x > map_width - 1) xx=x-map_width; else xx=x; if (y> map_heigth - 1) yy=y-map_heigth; else yy=y; if (x > map_width - 1 || y>map_heigth - 1) block = Map[xx+yy*map_width]; else block = Map[x+y*map_width]; block = block << 5; j=yg*640; for (int i=block; i<32+block; i++) { k=xg*2; dest0[k+j]=source0[i]; i++; k++; dest0[k+j]=source0[i]; j+=40; } j=yg*640; for (int i=block; i<32+block; i++) { k=xg*2; dest1[k+j]=source1[i]; i++; k++; dest1[k+j]=source1[i]; j+=40; } j=yg*640; for (int i=block; i<32+block; i++) { k=xg*2; dest2[k+j]=source2[i]; i++; k++; dest2[k+j]=source2[i]; j+=40; } j=yg*640; for (int i=block; i<32+block; i++) { k=xg*2; dest3[k+j]=source3[i]; i++; k++; dest3[k+j]=source3[i]; j+=40; } j=yg*640; for (int i=block; i<32+block; i++) { k=xg*2; dest4[k+j]=source4[i]; i++; k++; dest4[k+j]=source4[i]; j+=40; } } } } /* Koordinaatit blockeina */ void DrawBlock (int x, int y, int block) { block = block << 5; /* kerrotaan 32:lla */ j=y*640; /* kerrottaan 40 x 16:a */ for (int i=block; i<32+block; i++) { k=x*2; dest0[k+j]=source0[i]; i++; k++; dest0[k+j]=source0[i]; j+=40; } j=y*640; for (int i=block; i<32+block; i++) { k=x*2; dest1[k+j]=source1[i]; i++; k++; dest1[k+j]=source1[i]; j+=40; } j=y*640; for (int i=block; i<32+block; i++) { k=x*2; dest2[k+j]=source2[i]; i++; k++; dest2[k+j]=source2[i]; j+=40; } j=y*640; for (int i=block; i<32+block; i++) { k=x*2; dest3[k+j]=source3[i]; i++; k++; dest3[k+j]=source3[i]; j+=40; } j=y*640; for (int i=block; i<32+block; i++) { k=x*2; dest4[k+j]=source4[i]; i++; k++; dest4[k+j]=source4[i]; j+=40; } } void Cycle (void) { int Rs = cind; for (int i=0; i<4; i++, Rs++) { if (Rs > 3) Rs=0; colortable[i+16]=copytable[Rs]; } cind++; if (cind > 3) cind = 0; LoadRGB4(&(screen->ViewPort), colortable, count); } void Print(char teksti[], int pituus) { ScrollRaster(rastport,0,8,195,119,300,245); Move(rastport,195,243); Text(rastport,teksti,pituus); BltBitMap(DrawBitMap,195,119,ShowBitMap,195,119,105,127,0xc0,0x1f,NULL); } char BlockedMonster(WORD x, WORD y, WORD count) { char i; for (i=0; i<21; i++) { if (Monsters[i].x==x && count!=i && Monsters[i].elossa==1) if (Monsters[i].y==y) return 1; } switch (Map[x+y*map_width]) { case 3 : return 1; case 8 : return 1; case 12 : return 1; case 14 : return 1; default : return 0; } } void HandleMonsters(void) { int i,apuX,apuY,RS; if (turn==TURN) { for(i=0; i<21; i++) { if (Monsters[i].elossa) { /* 75% TN:llä lähdetään liikkeelle */ RS=Random(0,4); if (RS>=1) { apuX=Monsters[i].x; apuY=Monsters[i].y; RS=Random(0,2); if (RS==0) { if (isoX+5>apuX) apuX++; else apuX--; if (!BlockedMonster(apuX, apuY, i)) Monsters[i].x=apuX; } else if (isoY+6>apuY) apuY++; else apuY--; if (!BlockedMonster(apuX,apuY, i)) Monsters[i].y=apuY; } } } } /* Laskeskellaan, piirretäänkö monsteria näkyviin */ for(i=0; i<21; i++) { apuX=Monsters[i].x-isoX; apuY=Monsters[i].y-isoY; Monsters[i].frame=Monsters[i].block+Random(0,4); if (apuX<=10 && apuX>=0) if (apuY<=13 && apuY>=0) { /* printf("%d;%d\n",apuX,apuY); */ DrawBlock(apuX,apuY,Monsters[i].frame); } } } void InitMonsters (void) { short type; MonType[0].block=64; MonType[0].vital=10; MonType[0].exp=10; MonType[0].delay=0; MonType[0].frame=64; for (int i=0; i<21; i++) { Monsters[i].x=Random(0,map_width); Monsters[i].y=Random(0,map_heigth); switch (Map[Monsters[i].x+Monsters[i].y*map_width]) { case 3,8,12,14 : Monsters[i].elossa=0; break; default : Monsters[i].elossa=1; } Monsters[i].block=64; type=Monsters[i].block; Monsters[i].vital=MonType[type-64].vital; Monsters[i].exp=MonType[type-64].exp; Monsters[i].delay=0; Monsters[i].frame=0; } } void GeneralRoutines() { turn--; if (turn<=0) { delay--; /* printf("%d\n",delay); */ if (delay<-1) delay=-1; turn=TURN; Print("Pass.",5); } } char ReadMapFile(char nimi[], UWORD *buffer, int koko) { BPTR file; LONG length; /* printf("%s\n",nimi); */ file=Open(nimi,MODE_OLDFILE); length=Read(file,buffer,koko); if (length != koko) { printf("Error in reading mapfile\n"); Close(file); return 1; } return 0; } int Random (int alaraja, int ylaraja) { int Luku; Luku=rand(); Luku=alaraja+Luku%(ylaraja-alaraja); return Luku; }