16 December 2018, 12:24 | #1 |
Registered User
Join Date: May 2017
Location: AmigaLand
Posts: 456
|
Ocs clxdat & clxcon
Hey,
-Qst for Amiga 500 OCS- I have pain in the butt understanding how works these hardware collision detections registers. I'm trying to detect collision between sprites and bobs. The screen is dualplayfield. I set all bits on in CLXCON once at start and then at each frame I read CLXDAT then I "AND.W" it with #%0000000111100000 It doesn't work. Any help because the mist is deep ^^ |
16 December 2018, 13:01 | #2 |
WinUAE developer
Join Date: Aug 2001
Location: Hämeenlinna/Finland
Age: 49
Posts: 26,502
|
If you set all bits, you only get collision if all 6 planes had bit set which probably isn't what you wanted?
Perhaps this helps: http://eab.abime.net/showpost.php?p=965074&postcount=2 |
16 December 2018, 17:19 | #3 |
Registered User
Join Date: May 2017
Location: AmigaLand
Posts: 456
|
When you start to input random values, that means you must pass.
Too bad because this hardware feature would have saved some raster time. Anyway, thanks for helping. |
20 December 2018, 09:27 | #4 |
68k
Join Date: Sep 2005
Location: Somewhere
Posts: 828
|
@LeCaravage
Please check my very simple game - there is used hardware collision. https://github.com/asman2000/grim If you have questions then just ask. |
21 December 2018, 15:45 | #5 |
Registered User
Join Date: May 2017
Location: AmigaLand
Posts: 456
|
@Asman
Thanks for the help. Much appreciated. I checked your code and it seems I use a similar method than yours. But I use a dualplayfield, may be it's what makes the pb. It's like it only detects a certain color or so. Anyway, as I lost too much time on this hardware method, I skipped to a cross box detection, it's not very accurate but it does the job. |
08 February 2019, 14:35 | #6 |
Moderator
Join Date: Nov 2004
Location: Eksjö / Sweden
Posts: 5,602
|
Dual Playfield is perfect for collision detection
|
06 October 2021, 08:29 | #7 |
Registered User
Join Date: Apr 2018
Location: Germany
Posts: 189
|
I run into a similar problem as the thread opener, so I post my question here.
I've taken the RKRM SimpleSprite example as inspiration and would like to see if it is possible to detects the collision of a sprite with a colored square (pen 14, plane 4) which is drawn into an Intuition Windows RastPort. So in initialization stage I enabled collision detection for plane 4 Code:
// Enable collision detection for all sprites vs plane 4 (pens 12..15) pCustom->clxcon = 0x104f; Code:
char charBuf[80]; ULONG clxDat = pCustom->clxdat; sprintf(charBuf, "CLXDAT = %lu", clxDat); SetWindowTitles(m_pWindow, charBuf, (STRPTR) ~0); Here's a video clip of the situation I uploaded to [ Show youtube player ] And this is the full source of my {experimantal|hacky} example: Code:
/** * Tests if hardware collission detection between sprite<->bitplanes is * possible with Amiga operating system enabled. * */ #include <stdlib.h> #include <stdio.h> #include <clib/exec_protos.h> #include <clib/intuition_protos.h> #include <clib/graphics_protos.h> #include <dos/dos.h> #include <graphics/gfxmacros.h> #include <graphics/sprite.h> #include <hardware/custom.h> #include <intuition/intuition.h> #include <intuition/screens.h> /** * Amiga chipset address */ extern struct Custom custom; volatile struct Custom* pCustom = &custom; /** * Function declarations */ void intuiEventLoop(); void handleRawKeys(struct IntuiMessage* pMsg); void displayCollisionState(); int createSprite(struct ViewPort* pViewPort, struct SimpleSprite* pSprite, int spriteNum); int cleanExit(int errorCode); /** * Global variables */ struct Screen* m_pScreen = NULL; struct Window* m_pWindow = NULL; UWORD* m_pSpriteData; int m_SpriteNum = -1; struct SimpleSprite m_Sprite = {0}; // Sprite data (RKRM SimpleSprite example) UWORD spriteDataArray[ ] = { 0, 0, // position control 0xffff, 0x0000, // image data line 1, color 1 0xffff, 0x0000, // image data line 2, color 1 0x0000, 0xffff, // image data line 3, color 2 0x0000, 0xffff, // image data line 4, color 2 0x0000, 0x0000, // image data line 5, transparent 0x0000, 0xffff, // image data line 6, color 2 0x0000, 0xffff, // image data line 7, color 2 0xffff, 0xffff, // image data line 8, color 3 0xffff, 0xffff, // image data line 9, color 3 0, 0 // reserved, must init to 0 0 }; ULONG m_BackgroundColors[] = { 0x000C0004, // 0x0010 - Load 12 colors, starting from 0x004 0xFBFBFBFB, 0xF1F1F1F1, 0xC7C7C7C7, 0x46464646, 0x85858585, 0x88888888, 0x83838383, 0xA5A5A5A5, 0x98989898, 0x68686868, 0x9D9D9D9D, 0x6A6A6A6A, 0xAEAEAEAE, 0xC0C0C0C0, 0x7C7C7C7C, 0x98989898, 0x97979797, 0x1A1A1A1A, 0xB8B8B8B8, 0xBBBBBBBB, 0x24242424, 0xD7D7D7D7, 0x99999999, 0x21212121, 0xFAFAFAFA, 0xBDBDBDBD, 0x2F2F2F2F, 0xD6D6D6D6, 0x5D5D5D5D, 0xE0E0E0E, 0xCCCCCCCC, 0x24242424, 0x1D1D1D1D, 0xFBFBFBFB, 0x49494949, 0x34343434, 0x00000000 // Termination }; int main(int argc, char** argv) { int i; ULONG spriteDataSize; m_pSpriteData = NULL; m_pScreen = OpenScreenTags(NULL, SA_LikeWorkbench, TRUE, SA_DisplayID, PAL_MONITOR_ID|LORES_KEY, SA_Depth, 4, SA_Width, 320, SA_Height, 256, SA_Title, "Use the cursor keys to move sprite.", SA_Type, CUSTOMSCREEN, SA_Exclusive, TRUE, TAG_DONE); if(m_pScreen == NULL) { printf("Failed to open screen.\n"); cleanExit(RETURN_FAIL); } m_pWindow = OpenWindowTags(NULL, WA_CustomScreen, m_pScreen, WA_Left, 0, WA_Top, 12, WA_Width, 600, WA_Height, 200, WA_Title, "", WA_SmartRefresh, TRUE, WA_NewLookMenus, TRUE, WA_Flags, WFLG_ACTIVATE|WFLG_CLOSEGADGET|WFLG_DRAGBAR|WFLG_GIMMEZEROZERO, WA_IDCMP, IDCMP_CLOSEWINDOW|IDCMP_INTUITICKS|IDCMP_RAWKEY, TAG_DONE); if(m_pWindow == NULL) { printf("Failed to open window.\n"); cleanExit(RETURN_FAIL); } // Load the colors for pens 4..16 into the viewport LoadRGB32(&m_pScreen->ViewPort, m_BackgroundColors); // Move given sprite data to CHIP memory spriteDataSize = sizeof(spriteDataArray) / sizeof(spriteDataArray[0]); m_pSpriteData = AllocVec(sizeof(UWORD) * spriteDataSize, MEMF_CHIP|MEMF_CLEAR); for(i = 0; i < spriteDataSize; i++) { m_pSpriteData[i] = spriteDataArray[i]; } // Create sprite #2 m_SpriteNum = createSprite(&m_pScreen->ViewPort, &m_Sprite, 2); if(m_SpriteNum < 0) { printf("Failed to create sprite.\n"); cleanExit(RETURN_FAIL); } // Draw a red square in the window using pen 14 (plane 4) SetAPen(m_pWindow->RPort, 14); RectFill(m_pWindow->RPort, 100, 50, 200, 150); // Enable collision detection for all sprites vs plane 4 (pens 12..15) pCustom->clxcon = 0x104f; intuiEventLoop(); cleanExit(RETURN_OK); } void intuiEventLoop() { struct IntuiMessage* pMsg; BOOL isExitRequested = FALSE; do { Wait(1L << m_pWindow->UserPort->mp_SigBit); while (NULL != (pMsg = (struct IntuiMessage*)GetMsg(m_pWindow->UserPort))) { switch(pMsg->Class) { case CLOSEWINDOW: { isExitRequested = TRUE; break; } case RAWKEY: { handleRawKeys(pMsg); break; } case INTUITICKS: { displayCollisionState(); break; } } ReplyMsg((struct Message*)pMsg); } } while(isExitRequested == FALSE); } void handleRawKeys(struct IntuiMessage* pMsg) { struct SimpleSprite* pSprite = &m_Sprite; switch (pMsg->Code) { case CURSORLEFT: MoveSprite(NULL, pSprite, pSprite->x - 1, pSprite->y); break; case CURSORRIGHT: MoveSprite(NULL, pSprite, pSprite->x + 1, pSprite->y); break; case CURSORUP: MoveSprite(NULL, pSprite, pSprite->x, pSprite->y - 1); break; case CURSORDOWN: MoveSprite(NULL, pSprite, pSprite->x, pSprite->y + 1); break; default: return; } // One move per video frame WaitTOF(); } void displayCollisionState() { char charBuf[80]; ULONG clxDat = pCustom->clxdat; sprintf(charBuf, "CLXDAT = %lu", clxDat); SetWindowTitles(m_pWindow, charBuf, (STRPTR) ~0); } int createSprite(struct ViewPort* pViewPort, struct SimpleSprite* pSprite, int spriteNum) { WORD sprite_num; SHORT color_reg; // Trying to get sprite sprite_num = GetSprite(pSprite, spriteNum); if(sprite_num < 0) { return -1; } // Calculate the correct base color register number, set up the color // registers. color_reg = 16 + ((sprite_num & 0x06) << 1); SetRGB4(pViewPort, color_reg + 1, 12, 3, 8); SetRGB4(pViewPort, color_reg + 2, 13, 13, 13); SetRGB4(pViewPort, color_reg + 3, 4, 4, 15); // Initialize position and size info to match that shown in // spriteDataArray so system knows layout of data later pSprite->x = 0; pSprite->y = 0; pSprite->height = 9; // Install sprite data and move sprite to start position. ChangeSprite(NULL, pSprite, m_pSpriteData); MoveSprite(NULL, pSprite, 30, 30); return sprite_num; } int cleanExit(int errorCode) { if(m_SpriteNum > -1) { // If you turn off the sprite at the wrong time (when it is being // displayed), the sprite will appear as a vertical bar on the // screen. To really get rid of the sprite, you must OFF_SPRITE // while it is not displayed. This is hard in a multi-tasking // system (the solution is not addressed in this program). ON_SPRITE ; // Just to be sure FreeSprite(m_SpriteNum); } if(m_pSpriteData != NULL) { FreeVec(m_pSpriteData); m_pSpriteData = NULL; } if(m_pWindow != NULL) { CloseWindow(m_pWindow); m_pWindow = NULL; } if(m_pScreen != NULL) { CloseScreen(m_pScreen); m_pScreen = NULL; } exit(errorCode); } So maybe anyone can help and spot my failure in CLXCON / CLXDAT handling? |
06 October 2021, 10:29 | #8 |
Registered User
Join Date: Sep 2019
Location: Essen/Germany
Age: 55
Posts: 463
|
Are you sure that the OS didn't read the collision before you? According to the docs, the register is cleared after a read operation, so if the OS does it before you, you wouldn't see it IMO.
http://amigadev.elowar.com/read/ADCD.../node015C.html |
06 October 2021, 10:47 | #9 |
Registered User
Join Date: Apr 2018
Location: Germany
Posts: 189
|
Yeah that is also a point I was unsure about. But for sprite-to-sprite collisions it works in my example.
|
06 October 2021, 11:29 | #10 |
Defendit numerus
Join Date: Mar 2017
Location: Crossing the Rubicon
Age: 53
Posts: 4,468
|
|
06 October 2021, 11:45 | #11 |
Registered User
Join Date: Apr 2018
Location: Germany
Posts: 189
|
I inteded to map/set it like this (see the 'Set' column)
Code:
CLXCON: Bit | Set | Collission -------------| ------------------------------------ 15 | 1 | ENSP7 Enable Sprite 7 (ORed with Sprite 6) 14 | 1 | ENSP5 Enable Sprite 5 (ORed with Sprite 4) 13 | 1 | ENSP3 Enable Sprite 3 (ORed with Sprite 2) 12 | 1 | ENSP1 Enable Sprite 1 (ORed with Sprite 0) 11 | 0 | ENBP6 Enable bit plane 6 (match reqd. for collision) 10 | 0 | ENBP5 Enable bit plane 5 (match reqd. for collision) 09 | 1 | ENBP4 Enable bit plane 4 (match reqd. for collision) 08 | 0 | ENBP3 Enable bit plane 3 (match reqd. for collision) 07 | 0 | ENBP2 Enable bit plane 2 (match reqd. for collision) 06 | 0 | ENBP1 Enable bit plane 1 (match reqd. for collision) 05 | 0 | MVBP6 Match value for bit plane 6 collision 04 | 0 | MVBP5 Match value for bit plane 5 collision 03 | 1 | MVBP4 Match value for bit plane 4 collision 02 | 0 | MVBP3 Match value for bit plane 3 collision 01 | 0 | MVBP2 Match value for bit plane 2 collision 00 | 0 | MVBP1 Match value for bit plane 1 collision So I thought, %0001000001001111 = 0x104f is the right value. Is this reversed? (Sorry if this might be a totally trivial question) Edit: If I think about it, it really looks reversed. This afternoon I'll try it the other way around:-) Last edited by thyslo; 06 October 2023 at 09:23. Reason: Fixed bit names |
06 October 2021, 11:53 | #12 |
Registered User
Join Date: Sep 2019
Location: Essen/Germany
Age: 55
Posts: 463
|
Yeah it's reversed and also wrong. AFAIK it should be $f204 according to your list.
Code:
15 .............. 0 1111 0010 0000 1000 f 2 0 4 |
06 October 2021, 12:16 | #13 |
Registered User
Join Date: Apr 2018
Location: Germany
Posts: 189
|
Yeah Bit 15 is the one with the highest significance, of course:-)
So I'll try it with $f204 this afternoon. Thank you! Last edited by thyslo; 06 October 2021 at 12:27. |
06 October 2021, 20:13 | #14 |
Registered User
Join Date: Apr 2018
Location: Germany
Posts: 189
|
So I tested it and can confirm that with CLXCON set to $2F4 the collision between sprites<->sprites and also between sprites<->bitplane4 works
I haven't evaluated CLXDAT yet, but I see that it changes in the collision moment. The only thing odd about it is that sprite collision is detected as soon as the bounding boxes overlap, what seem right. But the collision with bitplane 4 is not detected until a sprite is completely in the colored area, see the video [ Show youtube player ]. Maybe I first should evaluate the CLXDAT value to see whats going on.. |
07 October 2021, 21:06 | #15 |
Registered User
Join Date: Apr 2018
Location: Germany
Posts: 189
|
Finally it works for my purposes.
Changing the CLXDAT display in window border to bits and some thinking revealed that CLXCON=0xf20f is more what I'm after. Here's an updated video: [ Show youtube player ] The final code of my test program in case anyone wants to experiment with collision detection themselves: Code:
/** * Tests if hardware collission detection between sprite<->bitplanes is * possible with Amiga operating system enabled. * */ #include <stdlib.h> #include <stdio.h> #include <clib/exec_protos.h> #include <clib/intuition_protos.h> #include <clib/graphics_protos.h> #include <dos/dos.h> #include <graphics/gfxmacros.h> #include <graphics/sprite.h> #include <hardware/custom.h> #include <intuition/intuition.h> #include <intuition/screens.h> /** * Amiga chipset address */ extern struct Custom custom; volatile struct Custom* pCustom = &custom; /** * Function declarations */ void intuiEventLoop(); void handleRawKeys(struct IntuiMessage* pMsg); void displayCollisionState(); void convertNumberToBits(char* pDstBuf, size_t const size, void const* const pNumber); int createSprite(struct ViewPort* pViewPort, struct SimpleSprite* pSprite, int spriteNum); int cleanExit(int errorCode); /** * Global variables */ struct Screen* m_pScreen = NULL; struct Window* m_pWindow = NULL; UWORD* m_pSpriteData; int m_SpriteNum = -1; struct SimpleSprite m_Sprite = {0}; // Sprite data (RKRM SimpleSprite example) UWORD spriteDataArray[ ] = { 0, 0, // position control 0xffff, 0x0000, // image data line 1, color 1 0xffff, 0x0000, // image data line 2, color 1 0x0000, 0xffff, // image data line 3, color 2 0x0000, 0xffff, // image data line 4, color 2 0x0000, 0x0000, // image data line 5, transparent 0x0000, 0xffff, // image data line 6, color 2 0x0000, 0xffff, // image data line 7, color 2 0xffff, 0xffff, // image data line 8, color 3 0xffff, 0xffff, // image data line 9, color 3 0, 0 // reserved, must init to 0 0 }; ULONG m_BackgroundColors[] = { 0x000C0004, // 0x0010 - Load 12 colors, starting from 0x004 0xFBFBFBFB, 0xF1F1F1F1, 0xC7C7C7C7, 0x46464646, 0x85858585, 0x88888888, 0x83838383, 0xA5A5A5A5, 0x98989898, 0x68686868, 0x9D9D9D9D, 0x6A6A6A6A, 0xAEAEAEAE, 0xC0C0C0C0, 0x7C7C7C7C, 0x98989898, 0x97979797, 0x1A1A1A1A, 0xB8B8B8B8, 0xBBBBBBBB, 0x24242424, 0xD7D7D7D7, 0x99999999, 0x21212121, 0xFAFAFAFA, 0xBDBDBDBD, 0x2F2F2F2F, 0xD6D6D6D6, 0x5D5D5D5D, 0xE0E0E0E, 0xCCCCCCCC, 0x24242424, 0x1D1D1D1D, 0xFBFBFBFB, 0x49494949, 0x34343434, 0x00000000 // Termination }; int main(int argc, char** argv) { int i; ULONG spriteDataSize; m_pSpriteData = NULL; m_pScreen = OpenScreenTags(NULL, SA_LikeWorkbench, TRUE, SA_DisplayID, PAL_MONITOR_ID|LORES_KEY, SA_Depth, 4, SA_Width, 320, SA_Height, 256, SA_Title, "Use the cursor keys to move sprite.", SA_Type, CUSTOMSCREEN, SA_Exclusive, TRUE, TAG_DONE); if(m_pScreen == NULL) { printf("Failed to open screen.\n"); cleanExit(RETURN_FAIL); } m_pWindow = OpenWindowTags(NULL, WA_CustomScreen, m_pScreen, WA_Left, 0, WA_Top, 12, WA_Width, 600, WA_Height, 200, WA_Title, "", WA_SmartRefresh, TRUE, WA_NewLookMenus, TRUE, WA_Flags, WFLG_ACTIVATE|WFLG_CLOSEGADGET|WFLG_DRAGBAR|WFLG_GIMMEZEROZERO, WA_IDCMP, IDCMP_CLOSEWINDOW|IDCMP_INTUITICKS|IDCMP_RAWKEY, TAG_DONE); if(m_pWindow == NULL) { printf("Failed to open window.\n"); cleanExit(RETURN_FAIL); } // Load the colors for pens 4..16 into the viewport LoadRGB32(&m_pScreen->ViewPort, m_BackgroundColors); // Move given sprite data to CHIP memory spriteDataSize = sizeof(spriteDataArray) / sizeof(spriteDataArray[0]); m_pSpriteData = AllocVec(sizeof(UWORD) * spriteDataSize, MEMF_CHIP|MEMF_CLEAR); for(i = 0; i < spriteDataSize; i++) { m_pSpriteData[i] = spriteDataArray[i]; } // Create sprite #2 m_SpriteNum = createSprite(&m_pScreen->ViewPort, &m_Sprite, 2); if(m_SpriteNum < 0) { printf("Failed to create sprite.\n"); cleanExit(RETURN_FAIL); } // Draw a red square in the window using pen 14 (plane 4) SetAPen(m_pWindow->RPort, 14); RectFill(m_pWindow->RPort, 100, 50, 200, 150); // Enable collision detection for all sprites vs plane 4 (pens 12..15) pCustom->clxcon = 0xf20f; intuiEventLoop(); cleanExit(RETURN_OK); } void intuiEventLoop() { struct IntuiMessage* pMsg; BOOL isExitRequested = FALSE; do { Wait(1L << m_pWindow->UserPort->mp_SigBit); while (NULL != (pMsg = (struct IntuiMessage*)GetMsg(m_pWindow->UserPort))) { switch(pMsg->Class) { case CLOSEWINDOW: { isExitRequested = TRUE; break; } case RAWKEY: { handleRawKeys(pMsg); break; } case INTUITICKS: { displayCollisionState(); break; } } ReplyMsg((struct Message*)pMsg); } } while(isExitRequested == FALSE); } void handleRawKeys(struct IntuiMessage* pMsg) { struct SimpleSprite* pSprite = &m_Sprite; switch (pMsg->Code) { case CURSORLEFT: MoveSprite(NULL, pSprite, pSprite->x - 1, pSprite->y); break; case CURSORRIGHT: MoveSprite(NULL, pSprite, pSprite->x + 1, pSprite->y); break; case CURSORUP: MoveSprite(NULL, pSprite, pSprite->x, pSprite->y - 1); break; case CURSORDOWN: MoveSprite(NULL, pSprite, pSprite->x, pSprite->y + 1); break; default: return; } // One move per video frame WaitTOF(); } void displayCollisionState() { char charBuf[80]; UWORD clxDat = pCustom->clxdat; sprintf(charBuf, "CLXDAT = "); // Note: The Length of the text above is 9. This is text is skipped // in next line. convertNumberToBits(charBuf + 9, 2, &clxDat); SetWindowTitles(m_pWindow, charBuf, (STRPTR) ~0); } /** * Prints the bit representation of a number into a buffer * * @param pDstBuf Buffer to write bytes into * @param size Number of bytes to print * @param pNumber Pointer to the Number to print */ void convertNumberToBits(char* pDstBuf, size_t const size, void const* const pNumber) { unsigned char* p = (unsigned char*)pNumber; unsigned char byte; size_t i; int j; // Converted stackoverflow answer to BIG ENDIAN // https://stackoverflow.com/questions/111928/is-there-a-printf-converter-to-print-in-binary-format for (i = 0; i < size; i++) { for (j = 7; j >= 0; j--) { byte = (p[i] >> j) & 1; sprintf(pDstBuf++, "%u", byte); } sprintf(pDstBuf++, " "); } } int createSprite(struct ViewPort* pViewPort, struct SimpleSprite* pSprite, int spriteNum) { WORD sprite_num; SHORT color_reg; // Trying to get sprite sprite_num = GetSprite(pSprite, spriteNum); if(sprite_num < 0) { return -1; } // Calculate the correct base color register number, set up the color // registers. color_reg = 16 + ((sprite_num & 0x06) << 1); SetRGB4(pViewPort, color_reg + 1, 12, 3, 8); SetRGB4(pViewPort, color_reg + 2, 13, 13, 13); SetRGB4(pViewPort, color_reg + 3, 4, 4, 15); // Initialize position and size info to match that shown in // spriteDataArray so system knows layout of data later pSprite->x = 0; pSprite->y = 0; pSprite->height = 9; // Install sprite data and move sprite to start position. ChangeSprite(NULL, pSprite, m_pSpriteData); MoveSprite(NULL, pSprite, 30, 30); return sprite_num; } int cleanExit(int errorCode) { if(m_SpriteNum > -1) { // If you turn off the sprite at the wrong time (when it is being // displayed), the sprite will appear as a vertical bar on the // screen. To really get rid of the sprite, you must OFF_SPRITE // while it is not displayed. This is hard in a multi-tasking // system (the solution is not addressed in this program). ON_SPRITE ; // Just to be sure FreeSprite(m_SpriteNum); } if(m_pSpriteData != NULL) { FreeVec(m_pSpriteData); m_pSpriteData = NULL; } if(m_pWindow != NULL) { CloseWindow(m_pWindow); m_pWindow = NULL; } if(m_pScreen != NULL) { CloseScreen(m_pScreen); m_pScreen = NULL; } exit(errorCode); } |
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
Thread Tools | |
Similar Threads | ||||
Thread | Thread Starter | Forum | Replies | Last Post |
Monkey Island 1 & 2 - Remastered for OCS / ECS | HAM6_Video | Amiga scene | 22 | 07 April 2017 16:10 |
Morton Strikes Back & Trog [OCS & AGA] - Trained Versions | DamienD | support.Games | 7 | 13 October 2016 09:53 |
Alterego (+3) Trainer & Zerosphere (+5) OCS | warfalcon | Amiga scene | 8 | 12 January 2016 15:43 |
Pinball Dreams & Pinball Fantasies - Special Edition (OCS/ECS) | teh | HOL contributions | 3 | 23 March 2012 14:39 |
aminet & amiga Plus cds - floppy & cd software/games - hardware & magazines for SALE! | bastibs | MarketPlace | 1 | 07 May 2008 11:33 |
|
|