summaryrefslogtreecommitdiff
path: root/src/wallclock.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallclock.c')
-rw-r--r--src/wallclock.c596
1 files changed, 596 insertions, 0 deletions
diff --git a/src/wallclock.c b/src/wallclock.c
new file mode 100644
index 000000000..56ace1c27
--- /dev/null
+++ b/src/wallclock.c
@@ -0,0 +1,596 @@
+#include "gba/io_reg.h"
+#include "gba/macro.h"
+#include "global.h"
+#include "main.h"
+#include "menu.h"
+#include "palette.h"
+#include "rtc.h"
+#include "songs.h"
+#include "sprite.h"
+#include "task.h"
+#include "text.h"
+#include "trig.h"
+
+//Functions that need to be put in headers
+void LZ77UnCompVram(const void *src, void *dest);
+void SetMainCallback2(MainCallback callback);
+void SetVBlankCallback(IntrCallback callback);
+void remove_some_task(void);
+void LoadCompressedObjectPic(void *);
+void PlaySE(u16);
+
+extern u16 gUnknown_0202E8CC;
+extern u16 gMiscClockMale_Pal[];
+extern u16 gMiscClockFemale_Pal[];
+extern u8 gMiscClock_Gfx[];
+extern u8 gUnknown_083F7A90[];
+extern struct SpritePalette gUnknown_083F7AA0;
+extern u8 gUnknown_08E95774[];
+extern u8 gUnknown_08E954B0[];
+extern u8 gOtherText_CorrectTimePrompt[];
+extern u8 * const gUnknown_08376D74[][2];
+extern s8 gClockHandCoords[][2];
+
+extern struct WindowConfig gWindowConfig_81E6C3C;
+extern struct WindowConfig gWindowConfig_81E6CE4;
+extern struct SpriteTemplate gSpriteTemplate_83F7AD8;
+extern struct SpriteTemplate gSpriteTemplate_83F7AF0;
+extern struct SpriteTemplate gSpriteTemplate_83F7B28;
+extern struct SpriteTemplate gSpriteTemplate_83F7B40;
+
+static void WallClockVblankCallback(void);
+static void LoadWallClockGraphics(void);
+static void WallClockMainCallback(void);
+static void WallClockInit(void);
+static void Task_SetClock1(u8 taskId);
+static void Task_SetClock2(u8 taskId);
+static void Task_SetClock3(u8 taskId);
+static void Task_SetClock4(u8 taskId);
+static void Task_SetClock5(u8 taskId);
+static void Task_SetClock6(u8 taskId);
+static void Task_ViewClock1(u8 taskId);
+static void Task_ViewClock2(u8 taskId);
+static void Task_ViewClock3(u8 taskId);
+static void Task_ViewClock4(u8 taskId);
+static u8 CalcMinHandDelta(u16 speed);
+static u16 CalcNewMinHandAngle(u16 angle, u8 direction, u8 speed);
+static u8 AdvanceClock(u8 taskId, u8 direction);
+static void UpdateClockPeriod(u8 taskId, u8 direction);
+static void InitClockWithRtc(u8 taskId);
+
+//Task data
+enum {
+ TD_MHAND_ANGLE,
+ TD_HHAND_ANGLE,
+ TD_HOURS,
+ TD_MINUTES,
+ TD_SETDIRECTION, //Movement direction when setting the clock
+ TD_PERIOD, //Whether the time is AM or PM
+ TD_SETSPEED, //Movement speed when setting the clock
+};
+
+enum {
+ AM,
+ PM
+};
+
+enum {
+ BACKWARD = 1,
+ FORWARD
+};
+
+static void WallClockVblankCallback(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+}
+
+static void LoadWallClockGraphics(void)
+{
+ u8 *addr;
+ u32 size;
+
+ SetVBlankCallback(0);
+ REG_DISPCNT = 0;
+ REG_BG3CNT = 0;
+ REG_BG2CNT = 0;
+ REG_BG1CNT = 0;
+ REG_BG0CNT = 0;
+ REG_BG3HOFS = 0;
+ REG_BG3VOFS = 0;
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = 0;
+ REG_BG1HOFS = 0;
+ REG_BG1VOFS = 0;
+ REG_BG0HOFS = 0;
+ REG_BG0VOFS = 0;
+
+ addr = (void *)VRAM;
+ size = 0x18000;
+ while(1)
+ {
+ DmaFill16(3, 0, addr, 0x1000);
+ addr += 0x1000;
+ size -= 0x1000;
+ if(size <= 0x1000)
+ {
+ DmaFill16(3, 0, addr, size);
+ break;
+ }
+ }
+
+ {
+ void *oam = (void *)OAM;
+ u32 oamSize = OAM_SIZE;
+ DmaFill32(3, 0, oam, oamSize);
+ }
+
+ {
+ void *pltt = (void *)PLTT;
+ u32 plttSize = PLTT_SIZE;
+ DmaFill16(3, 0, pltt, plttSize);
+ }
+
+ LZ77UnCompVram(gMiscClock_Gfx, (void *)VRAM);
+ if(gUnknown_0202E8CC == MALE)
+ LoadPalette(gMiscClockMale_Pal, 0, 32);
+ else
+ LoadPalette(gMiscClockFemale_Pal, 0, 32);
+ remove_some_task();
+ ResetTasks();
+ ResetSpriteData();
+ ResetPaletteFade();
+ FreeAllSpritePalettes();
+ LoadCompressedObjectPic(gUnknown_083F7A90);
+ LoadSpritePalettes(&gUnknown_083F7AA0);
+ SetUpWindowConfig(&gWindowConfig_81E6C3C);
+ InitMenuWindow(&gWindowConfig_81E6CE4);
+}
+
+static void WallClockInit(void)
+{
+ u16 ime;
+
+ BeginNormalPaletteFade(-1, 0, 0x10, 0, 0);
+ ime = REG_IME;
+ REG_IME = 0;
+ REG_IE |= 0x1;
+ REG_IME = ime;
+ REG_DISPSTAT |= 0x8;
+ SetVBlankCallback(WallClockVblankCallback);
+ SetMainCallback2(WallClockMainCallback);
+ REG_BLDCNT = 0;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 0;
+ REG_BG3CNT = 0x701;
+ REG_BG0CNT = 0x1F08;
+ REG_DISPCNT = 0x1940;
+}
+
+//Allow player to set the clock
+void Cb2_StartWallClock(void)
+{
+ u8 taskId;
+ u8 spriteId;
+
+ LoadWallClockGraphics();
+ LZ77UnCompVram(&gUnknown_08E954B0, (void *)(VRAM + 0x3800));
+
+ taskId = CreateTask(Task_SetClock1, 0);
+ gTasks[taskId].data[TD_HOURS] = 10;
+ gTasks[taskId].data[TD_MINUTES] = 0;
+ gTasks[taskId].data[TD_SETDIRECTION] = 0;
+ gTasks[taskId].data[TD_PERIOD] = AM;
+ gTasks[taskId].data[TD_SETSPEED] = 0;
+ gTasks[taskId].data[TD_MHAND_ANGLE] = 0;
+ gTasks[taskId].data[TD_HHAND_ANGLE] = 300;
+
+ spriteId = CreateSprite(&gSpriteTemplate_83F7AD8, 0x78, 0x50, 1);
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].oam.affineMode = 1;
+ gSprites[spriteId].oam.matrixNum = 0;
+
+ spriteId = CreateSprite(&gSpriteTemplate_83F7AF0, 0x78, 0x50, 0);
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].oam.affineMode = 1;
+ gSprites[spriteId].oam.matrixNum = 1;
+
+ spriteId = CreateSprite(&gSpriteTemplate_83F7B28, 0x78, 0x50, 2);
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].data1 = 45;
+
+ spriteId = CreateSprite(&gSpriteTemplate_83F7B40, 0x78, 0x50, 2);
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].data1 = 90;
+
+ WallClockInit();
+}
+
+//View, but don't set, the clock
+void Cb2_ViewWallClock(void)
+{
+ u8 taskId;
+ s16 angle1;
+ s16 angle2;
+ u8 spriteId;
+
+ LoadWallClockGraphics();
+ LZ77UnCompVram(gUnknown_08E95774, (void *)(VRAM + 0x3800));
+
+ taskId = CreateTask(Task_ViewClock1, 0);
+ InitClockWithRtc(taskId);
+ if(gTasks[taskId].data[TD_PERIOD] == 0)
+ {
+ angle1 = 45;
+ angle2 = 90;
+ }
+ else
+ {
+ angle1 = 90;
+ angle2 = 135;
+ }
+
+ spriteId = CreateSprite(&gSpriteTemplate_83F7AD8, 120, 80, 1);
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].oam.affineMode = 1;
+ gSprites[spriteId].oam.matrixNum = 0;
+
+ spriteId = CreateSprite(&gSpriteTemplate_83F7AF0, 120, 80, 0);
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].oam.affineMode = 1;
+ gSprites[spriteId].oam.matrixNum = 1;
+
+ spriteId = CreateSprite(&gSpriteTemplate_83F7B28, 120, 80, 2);
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].data1 = angle1;
+
+ spriteId = CreateSprite(&gSpriteTemplate_83F7B40, 120, 80, 2);
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].data1 = angle2;
+
+ WallClockInit();
+}
+
+static void WallClockMainCallback(void)
+{
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+}
+
+static void Task_SetClock1(u8 taskId)
+{
+ if(!gPaletteFade.active)
+ gTasks[taskId].func = Task_SetClock2;
+}
+
+//Handle keypresses when setting clock
+static void Task_SetClock2(u8 taskId)
+{
+ if(gTasks[taskId].data[TD_MHAND_ANGLE] % 6)
+ {
+ gTasks[taskId].data[TD_MHAND_ANGLE] = CalcNewMinHandAngle(
+ gTasks[taskId].data[TD_MHAND_ANGLE],
+ gTasks[taskId].data[TD_SETDIRECTION],
+ gTasks[taskId].data[TD_SETSPEED]);
+ }
+ else
+ {
+ gTasks[taskId].data[TD_MHAND_ANGLE] = gTasks[taskId].data[TD_MINUTES] * 6;
+ gTasks[taskId].data[TD_HHAND_ANGLE] = (gTasks[taskId].data[TD_HOURS] % 12) * 30 + (gTasks[taskId].data[TD_MINUTES] / 10) * 5;
+ if(gMain.newKeys & A_BUTTON)
+ {
+ gTasks[taskId].func = Task_SetClock3;
+ return;
+ }
+ else
+ {
+ gTasks[taskId].data[TD_SETDIRECTION] = gMain.newKeys & A_BUTTON;
+ if(gMain.heldKeys & DPAD_LEFT)
+ gTasks[taskId].data[TD_SETDIRECTION] = BACKWARD;
+ if(gMain.heldKeys & DPAD_RIGHT)
+ gTasks[taskId].data[TD_SETDIRECTION] = FORWARD;
+ if(gTasks[taskId].data[TD_SETDIRECTION])
+ {
+ if(gTasks[taskId].data[TD_SETSPEED] <= 0xFE)
+ gTasks[taskId].data[TD_SETSPEED]++;
+ gTasks[taskId].data[TD_MHAND_ANGLE] = CalcNewMinHandAngle(
+ gTasks[taskId].data[TD_MHAND_ANGLE],
+ gTasks[taskId].data[TD_SETDIRECTION],
+ gTasks[taskId].data[TD_SETSPEED]);
+ AdvanceClock(taskId, gTasks[taskId].data[TD_SETDIRECTION]);
+ }
+ else
+ {
+ gTasks[taskId].data[TD_SETSPEED] = 0;
+ }
+ }
+ }
+}
+
+//Ask player "Is this the correct time?"
+static void Task_SetClock3(u8 taskId)
+{
+ MenuDrawTextWindow(2, 16, 27, 19);
+ MenuPrint(gOtherText_CorrectTimePrompt, 3, 17);
+ MenuDrawTextWindow(23, 8, 29, 13);
+ PrintMenuItems(24, 9, 2, gUnknown_08376D74);
+ InitMenu(0, 24, 9, 2, 1, 5);
+ gTasks[taskId].func = Task_SetClock4;
+}
+
+//Get menu selection
+static void Task_SetClock4(u8 taskId)
+{
+ switch(ProcessMenuInputNoWrap_())
+ {
+ case 0: //YES
+ PlaySE(SE_SELECT);
+ gTasks[taskId].func = Task_SetClock5; //Move on
+ return;
+ case -1: //B button
+ case 1: //NO
+ sub_8072DEC();
+ PlaySE(SE_SELECT);
+ MenuZeroFillWindowRect(23, 8, 29, 13);
+ MenuZeroFillWindowRect(2, 16, 27, 19);
+ gTasks[taskId].func = Task_SetClock2; //Go back and let player adjust clock
+ }
+}
+
+//Set the time offset based on the wall clock's time
+static void Task_SetClock5(u8 taskId)
+{
+ RtcInitLocalTimeOffset(gTasks[taskId].data[TD_HOURS], gTasks[taskId].data[TD_MINUTES]);
+ BeginNormalPaletteFade(-1, 0, 0, 16, 0);
+ gTasks[taskId].func = Task_SetClock6;
+}
+
+static void Task_SetClock6(u8 taskId)
+{
+ if(!gPaletteFade.active)
+ SetMainCallback2((MainCallback)gMain.field_8);
+}
+
+static void Task_ViewClock1(u8 taskId)
+{
+ if(!gPaletteFade.active)
+ gTasks[taskId].func = Task_ViewClock2;
+}
+
+//Wait for A or B press
+static void Task_ViewClock2(u8 taskId)
+{
+ InitClockWithRtc(taskId);
+ if(gMain.newKeys & (A_BUTTON | B_BUTTON))
+ gTasks[taskId].func = Task_ViewClock3;
+}
+
+static void Task_ViewClock3(u8 taskId)
+{
+ BeginNormalPaletteFade(-1, 0, 0, 16, 0);
+ gTasks[taskId].func = Task_ViewClock4;
+}
+
+static void Task_ViewClock4(u8 taskId)
+{
+ if(!gPaletteFade.active)
+ SetMainCallback2((MainCallback)gMain.field_8);
+}
+
+static u8 CalcMinHandDelta(u16 speed)
+{
+ if(speed > 60)
+ return 6;
+ else if(speed > 30)
+ return 3;
+ else if(speed > 10)
+ return 2;
+ else
+ return 1;
+}
+
+//Calculates the new position of the minute hand when setting the clock
+static u16 CalcNewMinHandAngle(u16 angle, u8 direction, u8 speed)
+{
+ u8 delta = CalcMinHandDelta(speed);
+
+ switch(direction)
+ {
+ case BACKWARD:
+ if(angle)
+ angle = angle - delta;
+ else
+ angle = 360 - delta;
+ break;
+ case FORWARD:
+ if(angle < 360 - delta)
+ angle = angle + delta;
+ else
+ angle = 0;
+ break;
+ }
+ return angle;
+}
+
+//Advances clock forward or backward by 1 minute
+static u8 AdvanceClock(u8 taskId, u8 direction)
+{
+ switch(direction)
+ {
+ case BACKWARD:
+ if(gTasks[taskId].data[TD_MINUTES] > 0)
+ gTasks[taskId].data[TD_MINUTES]--;
+ else
+ {
+ gTasks[taskId].data[TD_MINUTES] = 59;
+ if(gTasks[taskId].data[TD_HOURS] > 0)
+ gTasks[taskId].data[TD_HOURS]--;
+ else
+ gTasks[taskId].data[TD_HOURS] = 23;
+ UpdateClockPeriod(taskId, direction);
+ }
+ break;
+ case FORWARD:
+ if(gTasks[taskId].data[TD_MINUTES] <= 58)
+ gTasks[taskId].data[TD_MINUTES]++;
+ else
+ {
+ gTasks[taskId].data[TD_MINUTES] = 0;
+ if(gTasks[taskId].data[TD_HOURS] <= 22)
+ gTasks[taskId].data[TD_HOURS]++;
+ else
+ gTasks[taskId].data[TD_HOURS] = 0;
+ UpdateClockPeriod(taskId, direction);
+ }
+ break;
+ }
+ return 0;
+}
+
+//Determines the clock period (AM/PM) based on the hour
+static void UpdateClockPeriod(u8 taskId, u8 direction)
+{
+ u8 hours = gTasks[taskId].data[TD_HOURS];
+
+ switch(direction)
+ {
+ case 1:
+ switch(hours)
+ {
+ case 11:
+ gTasks[taskId].data[TD_PERIOD] = AM;
+ break;
+ case 23:
+ gTasks[taskId].data[TD_PERIOD] = PM;
+ break;
+ }
+ break;
+ case 2:
+ switch(hours)
+ {
+ case 0:
+ gTasks[taskId].data[TD_PERIOD] = AM;
+ break;
+ case 12:
+ gTasks[taskId].data[TD_PERIOD] = PM;
+ break;
+ }
+ break;
+ }
+}
+
+static void InitClockWithRtc(u8 taskId)
+{
+ RtcCalcLocalTime();
+ gTasks[taskId].data[TD_HOURS] = gLocalTime.hours;
+ gTasks[taskId].data[TD_MINUTES] = gLocalTime.minutes;
+ gTasks[taskId].data[TD_MHAND_ANGLE] = gTasks[taskId].data[TD_MINUTES] * 6;
+ gTasks[taskId].data[TD_HHAND_ANGLE] = (gTasks[taskId].data[TD_HOURS] % 12) * 30 + (gTasks[taskId].data[TD_MINUTES] / 10) * 5;
+ if(gLocalTime.hours <= 11)
+ gTasks[taskId].data[TD_PERIOD] = AM;
+ else
+ gTasks[taskId].data[TD_PERIOD] = PM;
+}
+
+void sub_810B05C(struct Sprite *sprite)
+{
+ u16 angle;
+ s16 sin;
+ s16 cos;
+ u16 x;
+ u16 y;
+
+ angle = gTasks[sprite->data0].data[TD_MHAND_ANGLE];
+ sin = Sin2(angle) / 16;
+ cos = Cos2(angle) / 16;
+ SetOamMatrix(0, cos, sin, -sin, cos);
+ x = gClockHandCoords[angle][0];
+ y = gClockHandCoords[angle][1];
+
+ //Manual sign extension
+ if(x > 0x80)
+ x |= 0xFF00;
+ if(y > 0x80)
+ y |= 0xFF00;
+
+ sprite->pos2.x = x;
+ sprite->pos2.y = y;
+}
+
+void sub_810B0F4(struct Sprite *sprite)
+{
+ u16 angle;
+ s16 sin;
+ s16 cos;
+ u16 x;
+ u16 y;
+
+ angle = gTasks[sprite->data0].data[TD_HHAND_ANGLE];
+ sin = Sin2(angle) / 16;
+ cos = Cos2(angle) / 16;
+ SetOamMatrix(1, cos, sin, -sin, cos);
+ x = gClockHandCoords[angle][0];
+ y = gClockHandCoords[angle][1];
+
+ //Manual sign extension
+ if(x > 0x80)
+ x |= 0xFF00;
+ if(y > 0x80)
+ y |= 0xFF00;
+
+ sprite->pos2.x = x;
+ sprite->pos2.y = y;
+}
+
+void sub_810B18C(struct Sprite *sprite)
+{
+ s16 sin;
+ s16 cos;
+
+ if(gTasks[sprite->data0].data[TD_PERIOD] != AM)
+ {
+ if((u16)(sprite->data1 - 60) <= 29)
+ sprite->data1 += 5;
+ if(sprite->data1 <= 59)
+ sprite->data1++;
+ }
+ else
+ {
+ if((u16)(sprite->data1 - 46) <= 29)
+ sprite->data1 -= 5;
+ if(sprite->data1 > 75)
+ sprite->data1--;
+ }
+ cos = Cos2((u16)sprite->data1);
+ sprite->pos2.x = cos * 30 / 4096;
+ sin = Sin2((u16)sprite->data1);
+ sprite->pos2.y = sin * 30 / 4096;
+}
+
+void sub_810B230(struct Sprite *sprite)
+{
+ s16 sin;
+ s16 cos;
+
+ if(gTasks[sprite->data0].data[TD_PERIOD] != AM)
+ {
+ if((u16)(sprite->data1 - 105) <= 29)
+ sprite->data1 += 5;
+ if(sprite->data1 <= 104)
+ sprite->data1++;
+ }
+ else
+ {
+ if((u16)(sprite->data1 - 91) <= 29)
+ sprite->data1 -= 5;
+ if(sprite->data1 > 120)
+ sprite->data1--;
+ }
+ cos = Cos2((u16)sprite->data1);
+ sprite->pos2.x = cos * 30 / 4096;
+ sin = Sin2((u16)sprite->data1);
+ sprite->pos2.y = sin * 30 / 4096;
+}