// NAME: Multi-Star Variable Purchase (MP3)
// GAMES: MP3_USA
// EXECUTION: Direct
// PARAM: +Number|MAX_STARS
// PARAM: Number|STAR_COST

//Special Thanks to Airsola for finding everything initially,
//and PartyPlanner64 for the translated versions to C

//If using the Variable Pricing, set STAR_COST to 0 or a negative value
//MAX_STARS indicates how many stars a player can purchase at once.

#include "ultra64.h"

struct player {
    s8 unk0;
    s8 cpu_difficulty;
    s8 controller;
    u8 character;
    /**
     * Miscellaneous flags.
     * 1: Is CPU player
     */
    u8 flags;
    s8 pad0[5];
    /**
     * Current coin count.
     */
    s16 coins; // 10
    /**
     * Coins obtained during a Mini-Game.
     */
    s16 minigame_coins; // 12
    s8 stars; // 14

    u8 cur_chain_index; // 15
    u8 cur_space_index; // 16
    u8 next_chain_index; // 17
    u8 next_space_index; // 18
    u8 unk1_chain_index; // 19
    u8 unk1_space_index; // 20
    u8 reverse_chain_index; // 21
    u8 reverse_space_index; // 22

    u8 status_flags; // 23
    u8 items[3]; // 24
    u8 bowser_suit_flag; // 27
    u8 turn_color_status; // 28

    s8 pad1[7]; // 29 - 35

    void *obj; // 36 // struct object *
    s16 minigame_star; // 40
    s16 coin_star; // 42
    s8 happening_space_count; // 44
    s8 red_space_count;
    s8 blue_space_count;
    s8 chance_space_count;
    s8 bowser_space_count; // 48
    s8 battle_space_count;
    s8 item_space_count;
    s8 bank_space_count;
    s8 game_guy_space_count; // 52

    // s8 pad2[3];
}; // sizeof == 56

// Structure declarations for the structs used by the AI logic.
union ai_next {
    struct ai_node *ptr;
    unsigned int decision;
};
struct ai_node {
    unsigned char type;
    unsigned int data;
    union ai_next next;
};

// Example AI logic that makes a random choice.
struct ai_node ai_random_choice[] = {
    { 0, 0x00000000, 0x064C9932 },
};

#define PICTURE 0x16 //set as Millenium Star

extern struct player *GetPlayerStruct(s32 player_index);

// This is one of a few available addresses that get copied into EEPROM.
extern u8 D_800CD0A1; //change if using a different address

char cost[16];
char stars[16];
char coins[16];
char desc[16];

char *NotEnoughCoins_Message =
    "\x1A\x1A\x1A\x1A" // Standard padding for picture
    "Hey"
    "\x82" // Comma (,)
    " \x12" // String #2 (Character)
    "\xC2" // !
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A"
    "You need at least "
    "\x05" // Green Font
    "\x11" // String #1 (STAR_COST)
    "coins"
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A"
    "\x08" // White 
    "to purchase"
    "\x07" // Yellow Font
    "stars"
    "\x08" // White Font
    " right now"
    "\x85" // .
    "\x0A" // New line
    "Come back when you have enough"
    "\xC2" // !
    "\xFF";

char *Prompt =
    "\x0B" // Start the message
    "\x1A\x1A\x1A\x1A" // Standard padding for picture
    "Hey"
    "\x82" // Comma (,)
    " \x12" // String #2 (Character)
    "\xC2" // !
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A"
    "\x07" // Yellow Font
    "Stars"
    "\x08" // White Font
    " costs "
    "\x05" // Green Font
    "\x11" // String #1 (STAR_COST)
    "coins"
    "\x08" // White Font
    " each"
    "\x85" // .
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A"
    "Would you like to purchase "
    "\x13" // String #3 ("some" or "one", depending on the Max Stars they offer)
    "\xC3" // "?"
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A\x1A\x1A" // Picture + Option Indent
    "\x0C" // Start option
    "Yes"
    "\x0D" // End option
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A\x1A\x1A"
    "\x0C" // Start option
    "No"
    "\x0D" // End option
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A\x1A\x1A"
    "\x0C" // Start option
    "View Map"
    "\x0D";

char *Yes_Message =
    "\x1A\x1A\x1A\x1A" // Standard padding for picture
    "Alright"
    "\xC2" // !
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A"
    "Enjoy your "
    "\x07" // Yellow Font
    "Star"
    "\x08" // White Font
    "\xC2" // !
    "\xFF";

char *No_Message =
    "\x1A\x1A\x1A\x1A" // Standard padding for picture
    "Really"
    "\xC3" // "?"
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A" // Standard padding for picture
    "Guess I\x5Cll see"
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A" // Standard padding for picture
    "you later then"
    "\x85" // .
    "\xFF";

char *Stars_Message =
    "\x0B" // Start the message
    "\x1A\x1A\x1A\x1A" // Standard padding for picture
    "How many"
    "\x07" // Yellow Font
    "stars"
    "\x08" // White Font
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A"
    "will you purchase"
    "\xC3" // "?"
    "\x0A" // New line
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A\x1A" // Picture + Option Indent
    "Buy"
    "\x10\x10" // Space 2x (  )
    "\x2A" // Star icon
    "\x20" // ???
    "\x3E" // x (multiply)
    "\x20" // ???
    "\x05" // Green Font
    "\x0F" // ???
    "\x11" // String #1 (Stars)
    "\x19" // ???
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A\x1A" // Picture + Option Indent
    "For"
    "\x10\x10" // Space 2x (  )
    "\x29" // Coin icon
    "\x20" // ???
    "\x3E" // x (multiply)
    "\x20" // ???
    "\x05" // Green Font
    "\x0F" // ???
    "\x12" // String #2 (Price)
    "\x19" // ???
    "\x0A" // New line
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A\x1A\x1A"
    "\x28" // Control Stick Icon
    "\x85\x85\x85" // ...
    "Choose"
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A\x1A\x1A"
    "\x21" // (A) Button Icon
    "\x85\x85\x85"
    "Confirm"
    "\x0A" // New line
    "\x1A\x1A\x1A\x1A\x1A\x1A"
    "\x22" // (B) Button Icon
    "\x85\x85\x85"
    "Back";

void main() {
    SetBoardPlayerAnimation(-1, -1, 2);

    s32 player_index = GetCurrentPlayerIndex();
    struct player *player = GetPlayerStruct(-1);

    PlaySound(345); // Millenium Star's "Ho ho ho".

    s32 price = STAR_COST;
    if(price <= 0){
        price = D_800CD0A1; //if STAR_COST is 0 or less, it loads the variable price.
    } //Change D_800CD0A1 to another usable address if it conflicts with another event
    
    s32 purchaseNo = 1;
    s32 Max = player->coins / price;
    sprintf(cost, "%d", price);
    sprintf(stars, "%d", purchaseNo);
    sprintf(coins, "%d", (purchaseNo * price));
    
    if(Max > MAX_STARS){
        Max = MAX_STARS;
    }
    
    if(MAX_STARS != 1){
    	sprintf(desc, "some");
    }
    else {
    	sprintf(desc, "one");
    }
    
    s32 character_str = 0x1C00 + player->character;

    if (PlayerHasCoins(player_index, price) == 0) {
        ShowMessage(PICTURE, NotEnoughCoins_Message, cost, character_str, 0, 0, 0);
        func_800EC9DC();
        CloseMessage();
        func_800EC6EC();
        return;
    }

    while (1) {
        ShowMessage(PICTURE, Prompt, cost, character_str, desc, 0, 0);
        // Get the selection, either from the player or CPU.
        // If A0 is a pointer to AI data, AI logic is ran to pick for CPUs.
        s32 choice = GetBasicPromptSelection(2, 0); 
        
        CloseMessage();
        func_800EC6EC();
        
        switch (choice) {
            case 0:
                if(MAX_STARS == 1){ //gives a star if only 1 can be purchased
                    PlaySound(583); // Cash register sound.
                    SleepProcess(10);

                    // Take coins
                    AdjustPlayerCoinsGradual(player_index, -(purchaseNo * price));
                    ShowPlayerCoinChange(player_index, -(purchaseNo * price));
                    SleepProcess(30);

                    ShowMessage(PICTURE, Yes_Message, 0, 0, 0, 0, 0);
                    func_800EC9DC();
                    CloseMessage();
                    func_800EC6EC();
                    
					// Celebration
                	func_8004A520(111); // Play star jingle
                	player->stars += purchaseNo;
                	func_800F2304(-1, 6, 0);
                	func_8004ACE0(610, player_index);
                	SleepProcess(60);
                	SleepProcess(50);

                	// Restore board music
                	func_8004A520(GetBoardAudioIndex());
                    return;
                }
                s32 isCPU = PlayerIsCPU(-1);
                s32 aPressed = 0;
                s32 bPressed = 0;
                
                //this version doesn't have CPU menu appear since it appears to
                //not work when working with multiple variables in 1 message like this.
                if (!isCPU) {
                    // Alternative ShowMessage?
                	func_800EC92C(PICTURE, Stars_Message, stars, coins, 0, 0, 0);
                    
                    // Give real players opportunity to buy more stars.
                    while (!aPressed && !bPressed) {
                        aPressed = ButtonsPressed(player, A_BUTTON);
                        bPressed = ButtonsPressed(player, B_BUTTON);

                        s32 yState = GetJoystickVerticalState(player);
                        if (yState > 0 && purchaseNo < Max) {
                            purchaseNo++;
                        }
                        else if (yState < 0 && purchaseNo > 1) {
                            purchaseNo--;
                        }
                        else {
                            SleepVProcess();
                            continue;
                        }

                        PlaySound(582); // Coin sound

                        // Refresh the amount
    					sprintf(stars, "%d", purchaseNo);
    					sprintf(coins, "%d", (purchaseNo * price));
                        func_8005B6BC(2, stars, 0);
                        func_8005B43C(2, Stars_Message, -1, -1);
                        SleepProcess(2); // Reduce speed of loop further
                    }
                    
                    CloseMessage();
                	func_800EC6EC(); //an error occurred here?
                }

                if(isCPU){ //ensures computer buys as many as possible
                    SleepProcess(10); //Initial CPU wait
                    purchaseNo = Max;
                }
                
                if (isCPU || aPressed) {
                    PlaySound(583); // Cash register sound.
                    SleepProcess(10);

                    // Take coins
                    AdjustPlayerCoinsGradual(player_index, -(purchaseNo * price));
                    ShowPlayerCoinChange(player_index, -(purchaseNo * price));
                    SleepProcess(30);

                    ShowMessage(PICTURE, Yes_Message, 0, 0, 0, 0, 0);
                    func_800EC9DC();
                    CloseMessage();
                    func_800EC6EC();
                    
					// Celebration
                	func_8004A520(111); // Play star jingle
                	player->stars += purchaseNo;
                	func_800F2304(-1, 6, 0);
                	func_8004ACE0(610, player_index, -1, -1);
                	SleepProcess(60);
                	SleepProcess(50);

                	// Restore board music
                	func_8004A520(GetBoardAudioIndex());
                    return;
                }
                break; // Go back.

            case 1:
                // Show decline message and exit
                ShowMessage(PICTURE, No_Message, 0, 0, 0, 0, 0);
                func_800EC9DC();
                CloseMessage();
                func_800EC6EC();
                return;

            case 2:
                // Let player view the map, then repeat the loop to pick again.
                ViewBoardMap();
                break;
        }
    }
}

// OSContPad is defined in ultra64.h.
// The array of structs in the game is sizeof 8, so we need
// to pad out a bit to access it like an array.
struct OSContPadPadded {
    OSContPad cont;
    u16 padding;
};

// This is the location of the ultra64 controller struct array.
extern struct OSContPadPadded D_800CC3F4[4];

// Returns 1 if the button mask matches the player's controller, 0 otherwise.
s32 ButtonsPressed(struct player *player, u32 buttons) {
    return (D_800CC3F4[player->controller].cont.button & buttons) == buttons;
}

// Returns 1 if Y axis of the joystick has been pushed up enough to be considered "up"
// Returns -1 if it is pushed "down"
// 0 if in a more or less neutral vertical state.
s32 GetJoystickVerticalState(struct player *player) {
    s8 stick_y = D_800CC3F4[player->controller].cont.stick_y;

    // According to docs, -80 <= stick_y <= 80
    if (stick_y < -50) {
        return -1;
    }
    if (stick_y > 60) {
        return 1;
    }
    return 0;
}

void SendPlayerBack(struct player *player) {
    // Send the player back
    if (player->status_flags & 0x80) {
        SetNextChainAndSpace(
            -1,
            player->reverse_chain_index,
            player->reverse_space_index
        );
    }
    else {
        SetNextChainAndSpace(
            -1,
            player->next_chain_index,
            player->next_space_index
        );
    }
}