External Achievements (Full Script)

Discussion in 'RGSS3 Scripts (RMVX Ace)' started by boldpaste2, Jul 4, 2015.

  1. tomkomaster

    tomkomaster Veteran Veteran

    Messages:
    151
    Likes Received:
    8
    First Language:
    English
    Primarily Uses:
    RMVXA
    Can someone please fix the code?
     
    #21
  2. tomkomaster

    tomkomaster Veteran Veteran

    Messages:
    151
    Likes Received:
    8
    First Language:
    English
    Primarily Uses:
    RMVXA
    Anyone?
     
    #22
  3. Kes

    Kes Global Moderators Global Mod

    Messages:
    20,136
    Likes Received:
    10,301
    First Language:
    English
    Primarily Uses:
    RMVXA
    @tomkomaster Boldpaste2 hasn't been on the site since last July, so it's unlikely that he'll be fixing it. Have you tried sending him a pm, as suggested in the first post? It might be that he is still receiving email notifications of pms.
     
    #23
  4. tomkomaster

    tomkomaster Veteran Veteran

    Messages:
    151
    Likes Received:
    8
    First Language:
    English
    Primarily Uses:
    RMVXA
    Good idea. Ive started a conversation with him. I hope he replies.
     
    #24
  5. Altimos

    Altimos Villager Member

    Messages:
    17
    Likes Received:
    4
    Location:
    USA
    First Language:
    English
    Boldpaste2 has not logged in since January.

    This looks to be an awesome script.
    I have tried on many of occasions to try and align this script to work, but I am not that knowledgeable.
    Has nobody fixed it, that can share it for others?
     
    #25
  6. GaryTheGreat

    GaryTheGreat Veteran Veteran

    Messages:
    118
    Likes Received:
    3
    First Language:
    Russian
    Primarily Uses:
    RMVXA
    Is there a way to make spoiler-safe achievements? (hidden or replaced with ??? in the menu until unlocked)
     
    #26
  7. tale

    tale Volunteer Veteran

    Messages:
    379
    Likes Received:
    577
    Location:
    USA
    First Language:
    English
    Primarily Uses:
    N/A
    Fixed script format, credit Adiktuzmiko. For full details- External Achievements
    Code:
    =begin
     ==============================================================================
    
     External Achievements v1.01
     by AdiktuzMiko
     maintained and commisioned by: boldpaste2
     --- Date Created: 10/17/2014
     --- Last Date Updated: 10/17/2014
     --- Level: Easy
     --- Do not use this script if you did not obtain it from the official forum.
         Each game needs to have their own unique code so they don't overwrite
         other projects achievement data or prevent other projects from running.
     ==============================================================================
     Overview
     ==============================================================================
    
     This script adds the ability to place achievements into games and stores
     the data outside of saves so in the event if the save or game is deleted,
     the achievement data will be stored.
    
     Changelog
     ---------
     7/3/2015 - Rewrote Configuration description and preconfigured settings.
    
     ==============================================================================
     Features
     ==============================================================================
    
     --- Creates a new option in the option menu to view achievement data
     --- Allows banners to display when an achievement is earned
     --- Stores achievements externally so other api's may access the achievement data
     --- Customizable achievement window
     --- Luna Engine Friendly
    
     ==============================================================================
     Set-Up
     ==============================================================================
    
     Just put this script into a blank slot above main but below materials
     (see compatibility section) and modify the settings below
    
     By default a command is added to the main menu using the symbol
    
     :achievements
    
     using the handler method
    
     command_achievements
    
     To call it from somewhere else
    
     SceneManager.call(Scene_Achievements)  <--- Script call that opens the achievements window
    
     To unlock a new achievement:
    
     ADIK::ACHIEVEMENTS.add_achievement(id)  <--- Script call to show that an achievement has been earned
    
     where id => index of the achievement that you want to unlock
    
     ==============================================================================
     Compatibility
     ==============================================================================
    
     This script aliases Scene_Title's start method,
    
     Might alias DataManager's save_without_rescue and load_game, Scene_Menu's
     add_main_commands and create_command_window, Scene_Base's terminate
    
     ==============================================================================
     Terms of Use
     ==============================================================================
    
    This script is free to use in both non-commercial and commercial games so long as the following terms are met.
    
    -You may not post this script elsewhere or pass it on to anybody. If you have received this script from a source
     outside of the main post, do not use it! Each release is specifically configured per game! Using identical configs
     will cause other peoples achievements from running or prevents them from running all together. If you would like
     to receive a copy, please send a PM to http://forums.rpgmakerweb.com/index.php?/user/23718-boldpaste2/
    -You may not use this script if obtained from an external source.
    -You CAN modify the script so long as you do not redistribute the modifications.
    -Give credit to AdiktuzMiko for the creation of the script.
    
    
     ==============================================================================
    =end
    
    module ADIK
      module ACHIEVEMENTS
        
    
    #==============================================================================
    #Banner Configuration
    #==============================================================================
    
        #Background image for the achievement window. The image should be a .png stored in Graphics\System
        ACHIEVEMENT_BACKGROUND = ""
        
        #Position of the banner. You know, the title thing in the achievement window.
        ACHIEVEMENT_BANNER_X = 50
        ACHIEVEMENT_BANNER_Y = 0
        ACHIEVEMENT_BANNER_WIDTH = Graphics.width
        
    #A padding option to allow you to budge your achievement list down or up (just in case)
        ACHIEVEMENT_BANNER_HEIGHT = 64
        
    #If you want to give the banner a window skin, leave it as "" to not use a window skin.
    #You can also change the font, font color, size, and actual title.
        ACHIEVEMENT_BANNER_WINDOWSKIN = ""
        ACHIEVEMENT_BANNER_FONT_NAME = Font.default_name
        ACHIEVEMENT_BANNER_FONT_COLOR = Color.new(255,0,255)
        ACHIEVEMENT_BANNER_FONT_SIZE = 32
        ACHIEVEMENT_BANNER_TEXT = "ACHIEVEMENTS"
    
        #Fill color for Achievement Banner
        ACHIEVEMENT_BANNER_FILL_COLOR = Color.new(100,100,100,255)
        #Width and height of fill for Achievement Banner
        ACHIEVEMENT_BANNER_FILL_WIDTH = Graphics.width
        ACHIEVEMENT_BANNER_FILL_HEIGHT = ACHIEVEMENT_BANNER_HEIGHT
        
    #==============================================================================
    #Achievement Window Configuration
    #==============================================================================
    
        #Size of the achievement menu itself. This is practaclly done for you.
        ACHIEVEMENT_WINDOW_X = 0
        ACHIEVEMENT_WINDOW_Y = ACHIEVEMENT_BANNER_HEIGHT + 12
        ACHIEVEMENT_WINDOW_WIDTH = Graphics.width
        ACHIEVEMENT_WINDOW_HEIGHT = Graphics.height - ACHIEVEMENT_WINDOW_Y
    
        #Configuration of achievement grid. You made need math on your side for this.
    #example, if using 4 columns with no spacing, and using a screen size of 640
        #your achievement icons must not be bigger then 160 width.
        ACHIEVEMENT_COLUMNS = 4
        ACHIEVEMENT_ROWS_VISIBLE = 5
        ACHIEVEMENT_SPACING_X = 4
        ACHIEVEMENT_SPACING_Y = 4
    
        #Width and height for each achievement
        ACHIEVEMENT_WIDTH = (ACHIEVEMENT_WINDOW_WIDTH / 4) - ACHIEVEMENT_SPACING_X
        ACHIEVEMENT_HEIGHT = (ACHIEVEMENT_WINDOW_HEIGHT / 5) - 4*ACHIEVEMENT_SPACING_Y
    
    #==============================================================================
    #Achievement Notification Configuration
    #==============================================================================
        
        #The position on the screen where the icon "pops up" when the script call is activated.
        NEW_ACHIEVEMENT_X = 200
        NEW_ACHIEVEMENT_Y = 380
    
        #Width of the pop-up (generally width of image)
        NEW_ACHIEVEMENT_WIDTH = 128
        NEW_ACHIEVEMENT_HEIGHT = Graphics.height - NEW_ACHIEVEMENT_Y
    
        #How fast the pop-up fades in and out and how long it stays on screen.
        NEW_ACHIEVEMENT_FADE_IN = 60
        NEW_ACHIEVEMENT_FADE_OUT = 60
        NEW_ACHIEVEMENT_WAIT = 60
    
        #When changing scenes, does the pop-up go away if the above settings have not been completed.
        NEW_ACHIEVEMENT_PERSIST = true
        
    #==============================================================================
    #Achievement Configuration
    #------------------------------------------------------------------------------
    #The heart of the script. this is where you store your achievements for your project.
    #The images used are stored in    Graphics\Pictures    as .png files.
    #
    # Format is: LIST[index] = image_file_name
    # Where index is achievement ID
    #
    # Example LIST[1] = "Achievment_1"
    #==============================================================================
    
        LIST = [] #Do not delete this.
    
        LIST[1] = "Achievment_1"
        LIST[2] = "Achievment_2"
    
        #Use a different image when the achievement has not been earned?
    #WARNING! if false, it will greyscale the image which causes a huge performance drop
        DIF_IMAGE = true
        
        #List of disabled icon images if DIF-IMAGE is TRUE
        LIST_DIS = []
    
    LIST[1] = "Achievment_1D"
        LIST[2] = "Achievment_2D"
        
        #Add a command to open achievements scene from the main menu
        ADD_TO_MAIN_MENU = true
        COMMAND_NAME = "Achievements"
        
    #==============================================================================
    #DATA
    #------------------------------------------------------------------------------
    #
    #
    #~ Here is a list of parameters you can set the DATA_PATH into
    #
    #~ AppData = Contains the full path to the Application Data directory of the logged-in user
    #~ LOCALAPPDATA = This variable is the temporary files of Applications.
    #~  CURRENT = Folder where the game is run from
    #
    #~ !!!The following might require your game to run in admin mode!!!
    #
    #~ ProgramFiles = This variable points to Program Files directory
    #~ CommonProgramFiles = This variable points to Common Files directory
    #==============================================================================
    
        #Autosave the achievements file when a new achievement
        #is achieved
        AUTOSAVE = true
    
       # Primary folder where the achievements rgss file will be placed
        # based on the default paths in your computer
        # set to nil if you don't want to use this
        DATA_PATH = 'AppData'
    
        #Enter each subfolder paths after the primary folder here
        #The script automatically creates the folder path specified
        
        #FOLDER = [FOLDER1,FOLDER2,and so on]
    
    
        FOLDER = ["MyRMGames","GAME NAME"]
    
        
        #Filename for the achievements file
    
    #USE A NAME NOBODY WILL COME UP WITH!
        NAME = "Defualt.rgss3"
    
        
        # Primary folder where the achievements text file will be placed
        # based on the default paths in your computer
        # set to nil if you don't want to use this
        TEXT_PATH = 'AppData'
        
        #Enter each sub folder paths after the primary folder here
        #The script automatically creates the folder path specified
        
        #FOLDER = [FOLDER1,FOLDER2,and so on]
    
    
        TEXT_FOLDER = ["MyRMGames","GAME NAME"]
    
        
        #Filename for the achievements text file
    
    #USE A NAME NOBODY WILL COME UP WITH!
        TEXT_NAME = "Defualt.ini"
    
        
        #==================================================================
        #DO NOT EDIT BEYOND THIS POINT
        #==================================================================
    
        #==================================================================
        #I SAID DO NOT EDIT BEYOND THIS POINT
        #==================================================================
        
        def self.check_achievements
          @path = ""
          if not DATA_PATH == 'CURRENT'
            @path = ENV[DATA_PATH] + "\\"
          end
          FOLDER.each do |path|
            @path += path + "\\"
            Dir.mkdir(@path) if !FileTest.exist?(@path)
          end
          @path += NAME
          @text_path = ""
          if not TEXT_PATH == 'CURRENT'
            @text_path = ENV[TEXT_PATH] + "\\"
          end
          TEXT_FOLDER.each do |path|
            @text_path += path + "\\"
            Dir.mkdir(@text_path) if !FileTest.exist?(@text_path)
          end
          @text_path += TEXT_NAME
          begin
            load_achievements
          rescue
            $achievements = []
            save_achievements
          end
        end
        
        def self.has_achievement(id)
          return $achievements.include?(id)
        end
        
        def self.save_achievements
          File.open(@path, "wb") do |file|
            Marshal.dump($achievements, file)
          end
          File.open(@text_path,"w+") do |file|
            ADIK::ACHIEVEMENTS::LIST.each_index do |index|
              if has_achievement(index)
                file.write("Achievement"+index.to_s+"=1"+"\n")
              else
                file.write("Achievement"+index.to_s+"=0"+"\n")
              end
            end
          end
        end
        
        def self.load_achievements
          File.open(@path, "rb") do |file|
            $achievements = Marshal.load(file)
          end
          File.open(@text_path,"w+") do |file|
            ADIK::ACHIEVEMENTS::LIST.each_index do |index|
              if has_achievement(index)
                file.write("Achievement"+index.to_s+"=1"+"\n")
              else
                file.write("Achievement"+index.to_s+"=0"+"\n")
              end
            end
          end
        end
        
        check_achievements
        
        def self.add_achievement(id)
          return if $achievements.include?(id)
          $achievements.push(id)
          SceneManager.scene.show_new_achievement(id)
          save_achievements if AUTOSAVE
        end
        
        
        
      end
    end
    
    class Window_Achievements < Window_Base
     
      def initialize(x, y, width, height)
        super
        self.windowskin = Cache.system("")
        @row = 1
        @row_max = 1 + ((ADIK::ACHIEVEMENTS::LIST.size)/ADIK::ACHIEVEMENTS::ACHIEVEMENT_COLUMNS).truncate
        create_achievements
      end
     
      def add_row(value)
        @row = [@row+value,@row_max].min
        refresh_achivements
      end
     
      def red_row(value)
        @row = [@row-value,1].max
        refresh_achivements
      end
     
      def create_achievements
        @gallery = []
        refresh_achivements
      end
     
      def refresh_achivements
        @gallery.each do |spr|
          spr.dispose unless spr.nil?
        end
        @gallery = []
        columns = ADIK::ACHIEVEMENTS::ACHIEVEMENT_COLUMNS
        column = 1
        row = 1
        dx = ADIK::ACHIEVEMENTS::ACHIEVEMENT_SPACING_X
        dy = ADIK::ACHIEVEMENTS::ACHIEVEMENT_SPACING_Y
        w = ADIK::ACHIEVEMENTS::ACHIEVEMENT_WIDTH
        h = ADIK::ACHIEVEMENTS::ACHIEVEMENT_HEIGHT
        min = (@row-1)*columns + 1
        max = (@row+ADIK::ACHIEVEMENTS::ACHIEVEMENT_ROWS_VISIBLE-1)*columns
        (min..max).each do |index|
          return if ADIK::ACHIEVEMENTS::LIST[index].nil?
          @gallery[index] = Sprite.new
          @gallery[index].bitmap = Bitmap.new(ADIK::ACHIEVEMENTS::ACHIEVEMENT_WINDOW_WIDTH,ADIK::ACHIEVEMENTS::ACHIEVEMENT_WINDOW_HEIGHT)
          
          rect = Rect.new(x+(dx+w)*(column-1),y+(dy+h)*(row-1),w,h)
          if ADIK::ACHIEVEMENTS::DIF_IMAGE
            if ADIK::ACHIEVEMENTS.has_achievement(index)
              bit = Cache.picture(ADIK::ACHIEVEMENTS::LIST[index])
            else
              bit = Cache.picture(ADIK::ACHIEVEMENTS::LIST_DIS[index])
            end
            @gallery[index].bitmap.stretch_blt(rect,bit,Rect.new(0,0,bit.width,bit.height))
          else
            bit = Cache.picture(ADIK::ACHIEVEMENTS::LIST[index])
            @gallery[index].bitmap.stretch_blt(rect,bit,Rect.new(0,0,bit.width,bit.height))
            @gallery[index].tone.set(0,0,0,255) unless ADIK::ACHIEVEMENTS.has_achievement(index)
          end
          column += 1
          if column > 4
            column = 1
            row += 1
            return if row > ADIK::ACHIEVEMENTS::ACHIEVEMENT_ROWS_VISIBLE
          end
        end
      end
     
      def dispose
        @gallery.each do |spr|
          spr.dispose unless spr.nil?
        end
        super
      end
     
    end
    
    class Window_AchievementBanner < Window_Base
     
      def initialize(x, y, width, height)
        super
        self.windowskin = Cache.system(ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_WINDOWSKIN)
        contents.font.name = ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_FONT_NAME
        contents.font.color = ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_FONT_COLOR
        contents.font.size = ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_FONT_SIZE
        contents.draw_text(0,0,width,height/2,ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_TEXT,1)
      end
     
    end
    
    class Scene_Achievements < Scene_Base
     
      def start
        super
        create_background
        create_banner_window
        create_achievement_window
      end
     
      def update
        super
        if Input.trigger?(:B)
          SceneManager.return
        elsif Input.trigger?(:DOWN)
          @achievement_window.add_row(1)
        elsif Input.trigger?(:UP)
          @achievement_window.red_row(1)
        end
      end
     
      def create_background
        @background = Sprite.new
        @background.bitmap = Bitmap.new(Graphics.width,Graphics.height)
        x = ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_X
        y = ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_Y
        w = ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_WIDTH
        h = ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_HEIGHT
        bit = Cache.system(ADIK::ACHIEVEMENTS::ACHIEVEMENT_BACKGROUND)
        @background.bitmap.blt(0,0,bit,Rect.new(0,0,bit.width,bit.height))
        color = ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_FILL_COLOR
        @background.bitmap.fill_rect(x,y,w,h,color)
        
      end
     
      def create_achievement_window
        @achievement_window = Window_Achievements.new(ADIK::ACHIEVEMENTS::ACHIEVEMENT_WINDOW_X,ADIK::ACHIEVEMENTS::ACHIEVEMENT_WINDOW_Y,ADIK::ACHIEVEMENTS::ACHIEVEMENT_WINDOW_WIDTH,ADIK::ACHIEVEMENTS::ACHIEVEMENT_WINDOW_HEIGHT)
      end
     
      def create_banner_window
        @banner_window = Window_AchievementBanner.new(ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_X,ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_Y,ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_WIDTH,ADIK::ACHIEVEMENTS::ACHIEVEMENT_BANNER_HEIGHT)
      end
     
      def terminate
        @achievement_window.dispose
        @background.dispose
        @banner_window.dispose
        super
      end
     
    end
    
    
    #For the new achievement pop-up
    #If persist is true
    if ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_PERSIST
      class Scene_Base
     
        @@new_achievement = nil
     
        alias update_achievements update
        def update
          update_achievements
          return if @@new_achievement.nil?
          @@new_achievement.z = 99999
          if @@na_fadein_time > 0
            @@na_fadein_time -= 1
            @@new_achievement.opacity += @@ops_in
          elsif @@na_wait_time > 0
            @@na_wait_time -= 1
          elsif @@na_fadeout_time > 0
            @@na_fadeout_time -= 1
            @@new_achievement.opacity -= @@ops_out
          else
            @@new_achievement.dispose
            @@new_achievement = nil
          end
        end
     
        def show_new_achievement(id)
          @@new_achievement = Sprite.new
          @@new_achievement.z = 99999
          @@new_achievement.bitmap = Bitmap.new(Graphics.width,Graphics.height)
          @@na_fadein_time = ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_FADE_IN
          @@na_fadeout_time = ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_FADE_OUT
          @@na_wait_time = ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_WAIT
          @@ops_in = 255/(@@na_fadein_time)
          @@ops_out = 255/(@@na_fadeout_time)
          bit = Cache.picture(ADIK::ACHIEVEMENTS::LIST[id])
          rect = Rect.new(ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_X,ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_Y,ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_WIDTH,ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_HEIGHT)
          @@new_achievement.opacity = 0
          @@new_achievement.bitmap.stretch_blt(rect,bit,Rect.new(0,0,bit.width,bit.height))
        end
     
      end
     
    else #If don't persist
     
      class Scene_Base
     
        @new_achievement = nil
     
        alias update_achievements update
        def update
          update_achievements
          return if @new_achievement.nil?
          @new_achievement.z = 99999
          if @na_fadein_time > 0
            @na_fadein_time -= 1
            @new_achievement.opacity += @ops_in
          elsif @na_wait_time > 0
            @na_wait_time -= 1
          elsif @na_fadeout_time > 0
            @na_fadeout_time -= 1
            @new_achievement.opacity -= @ops_out
          else
            @new_achievement.dispose
            @new_achievement = nil
          end
        end
     
        def show_new_achievement(id)
          @new_achievement = Sprite.new
          @new_achievement.z = 99999
          @new_achievement.bitmap = Bitmap.new(Graphics.width,Graphics.height)
          @na_fadein_time = ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_FADE_IN
          @na_fadeout_time = ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_FADE_OUT
          @na_wait_time = ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_WAIT
          @ops_in = 255/(@na_fadein_time)
          @ops_out = 255/(@na_fadeout_time)
          bit = Cache.picture(ADIK::ACHIEVEMENTS::LIST[id])
          rect = Rect.new(ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_X,ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_Y,ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_WIDTH,ADIK::ACHIEVEMENTS::NEW_ACHIEVEMENT_HEIGHT)
          @new_achievement.opacity = 0
          @new_achievement.bitmap.stretch_blt(rect,bit,Rect.new(0,0,bit.width,bit.height))
        end
        
        alias terminate_achievements terminate
        def terminate
          @new_achievement.dispose unless @new_achievement.nil?
          @new_achievement = nil
          terminate_achievements
        end
     
      end
     
    end
    
    #Saves/Loads achievements upon saving and loading
    #Only if autosave is false, since there's no point in doing this
    #if it's on autosave
    
    if not ADIK::ACHIEVEMENTS::AUTOSAVE
      module DataManager
     
        class <<self; alias load_game_achievements load_game; end
        def self.load_game(index)
          load_game_achievements(index)
          ADIK::ACHIEVEMENTS.load_achievements
        end
     
        class <<self; alias save_game_without_rescue_achievements save_game_without_rescue; end
        def self.save_game_without_rescue(index)
          save_game_without_rescue_achievements(index)
          ADIK::ACHIEVEMENTS.save_achievements
          return true
        end
     
      end
    end
    
    #So that the menu is only edited if ADD_TO_MAIN_MENU is true
    if ADIK::ACHIEVEMENTS::ADD_TO_MAIN_MENU
    
      class Window_MenuCommand
     
        alias add_main_commands_add_achievement add_main_commands
        def add_main_commands
          add_main_commands_add_achievement
          add_command(ADIK::ACHIEVEMENTS::COMMAND_NAME, :achievements, true)
        end
        
      end
    
      class Scene_Menu
     
        alias create_command_window_achievements create_command_window
        def create_command_window
          create_command_window_achievements
          @command_window.set_handler(:achievements, method(:command_achievements))
        end
        
        def command_achievements
          SceneManager.call(Scene_Achievements)
        end
     
      end
    end
     
    #27
    ekhartpl likes this.

Share This Page