Adding a Header to Window_Command subclass

Discussion in 'Learning Javascript' started by nathanlink169, Oct 3, 2019.

  1. nathanlink169

    nathanlink169 Veteran Veteran

    Messages:
    49
    Likes Received:
    16
    Location:
    Ottawa
    First Language:
    English
    Primarily Uses:
    RMMV
    Hello all!

    TL-DR - I'm looking to have an unselectable "Header" inside of a Window_Command subclass.

    I'm working on a custom quest system in the game. I've written a script to populate some quest data, and now I'm writing the code to show the quests the player can currently go on. I would like to split this list out into two separate lists. At the top will be the active quests. At the bottom will be the completed quests. Above the active quests, I'm looking to add an "Active Quests" header: I hope to make this text either bigger than the selectable texts, or at least a different colour. Does anyone know how to go about doing something like this? I've attached my code at the bottom

    (please don't judge the formatting too harshly, it's horrible - I'm a c++ developer who's only about 10 development hours into Javascript!)

    Code:
    //-----------------------------------------------------------------------------
    // Window_MenuQuestList
    //
    // The window for selecting a quest and travelling.
    
    function Window_MenuQuestList() {
        this.initialize.apply(this, arguments);
    }
    
    Window_MenuQuestList.prototype = Object.create(Window_Command.prototype);
    Window_MenuQuestList.prototype.constructor = Window_MenuQuestList;
    
    Window_MenuQuestList.prototype.initialize = function (x, y) {
        Window_Command.prototype.initialize.call(this, x, y);
        this.selectLast();
    
        this._previousIndex = -10; // set this to something that index() will never be set to
    };
    
    Window_MenuQuestList._lastCommandSymbol = null;
    
    Window_MenuQuestList.initCommandPosition = function () {
        this._lastCommandSymbol = null;
    };
    
    Window_MenuQuestList.prototype.windowWidth = function () {
        return 240;
    };
    
    Window_MenuQuestList.prototype.windowHeight = function () {
        return Graphics.boxHeight;
    };
    
    Window_MenuQuestList.prototype.numVisibleRows = function () {
        return this.maxItems();
    };
    
    Window_MenuQuestList.prototype.update = function () {
        Window_Command.prototype.update.call(this);
    
        if (this.index() != this._previousIndex) {
            this._previousIndex = this.index();
    
            var key = this._questKeyArray[this.index()];
            this._helpWindow._text = $gamePlayer._questInformation[key]["description"];
            this._helpWindow.refresh();
        }
        console.log(this.index());
    };
    
    
    Window_MenuQuestList.prototype.makeCommandList = function () {
        this.addMainCommands();
    };
    
    Window_MenuQuestList.prototype.addMainCommands = function () {
        var questArray = $gamePlayer._questInformation;
    
        this._questKeyArray = {};
    
        var index = 0;
    
        // TODO: Insert "Active Quests" Header here
    
        for (var key in questArray) {
            var value = questArray[key];
            if (value["unlocked"] === true) {
                this.addCommand(value["title"], undefined, true, undefined);
    
                this._questKeyArray[index] = key;
                index = index + 1;
            }
        }
    
        // TODO: Show Completed Quests Here
    };
    
    Window_MenuQuestList.prototype.processOk = function () {
        Window_MenuQuestList._lastCommandSymbol = this.currentSymbol();
        Window_Command.prototype.processOk.call(this);
    };
    
    Window_MenuQuestList.prototype.selectLast = function () {
        this.selectSymbol(Window_MenuQuestList._lastCommandSymbol);
    };
     
    #1
  2. Ossra

    Ossra Formerly Exhydra Veteran

    Messages:
    861
    Likes Received:
    627
    First Language:
    English
    Primarily Uses:
    RMMV
    @nathanlink169 What you will need to do in order to create a header is add the height of a single line (or perhaps two if you want to use larger text) to the base rect of each item. You can do this by hijacking the 'itemRect' function in your window and adding in the value returned from the 'lineHeight' function (usually 36 pixels). The following is from code I used on a command window I wanted to have a header on :

    Code:
    Window_GameNew.prototype.itemRect = function(index) {
      var rect = Window_Command.prototype.itemRect.call(this, index);
      rect.y  += this.lineHeight();
     
      return rect;
    };
    
    Window_GameNew.prototype.refresh = function() {
      Window_Command.prototype.refresh.call(this);
     
      this.drawText('My Header', 0, 0, this.contentsWidth(), 'center');
    };
    


    EDIT: Here is the rest of the code I used for the window. I added in some code that deals with a larger font size for the header :

    Code:
    function Window_GameNew() {
        this.initialize.apply(this, arguments);
    }
    
    Window_GameNew.prototype = Object.create(Window_Command.prototype);
    Window_GameNew.prototype.constructor = Window_GameNew;
    
    Window_GameNew.prototype.initialize = function() {
        Window_Command.prototype.initialize.call(this, 0, 0);
        this.updatePlacement();
        this.openness = 0;
    };
    
    Window_GameNew.prototype.windowWidth = function() {
        return 250;
    };
    
    Window_GameNew.prototype.itemTextAlign = function() {
      return 'center';
    };
    
    Window_GameNew.prototype.numVisibleRows = function() {
        return 3;
    };
    
    Window_GameNew.prototype.itemRect = function(index) {
      var rect = Window_Command.prototype.itemRect.call(this, index);
      rect.y  += this.lineHeight() + 20;
     
      return rect;
    };
    
    Window_GameNew.prototype.updatePlacement = function() {
        this.x = (Graphics.boxWidth - this.width) / 2;
        this.y = (Graphics.boxHeight - this.height) / 2;
    };
    
    Window_GameNew.prototype.makeCommandList = function() {
        this.addCommand('Yes', 'newGame');
        this.addCommand('No',  'cancel');
    };
    
    Window_GameNew.prototype.refresh = function() {
      Window_Command.prototype.refresh.call(this);
    
      this.contents.fontSize = 56;
      this.drawText('Start the game?', 0, 5, this.contentsWidth(), 'center');
    };
    
    Window_GameNew.prototype.contentsHeight = function() {
      var height = Window_Command.prototype.contentsHeight.call(this);
    
      return height + 20;
    };
    
    Window_GameNew.prototype.fittingHeight = function(numLines) {
      var height = Window_Command.prototype.fittingHeight.call(this, numLines);
    
      return height + 20;
    };
    

    To accommodate the larger text at the top of the window, I changed the 'y' value in drawText to move the text down, then added some more buffer space in itemRect as well as contentsHeight, and fittingHeight. If you keep the text normal sized, then you will not have to fiddle and tinker as much.
     
    Last edited: Oct 3, 2019
    #2
    standardplayer likes this.
  3. nathanlink169

    nathanlink169 Veteran Veteran

    Messages:
    49
    Likes Received:
    16
    Location:
    Ottawa
    First Language:
    English
    Primarily Uses:
    RMMV
    Hey Ossra, thanks for the reply! I got it a good chunk of the way through with your help, so I really appreciate it!

    I'm running into another issue. I lied a little bit - I actually want to add two headers: one for the available quests, and one for the completed quests. I've managed to get everything in place, and it looks fairly good so far!

    upload_2019-10-3_21-56-52.png

    Unfortunately I'm running into a bit of an issue with the placement of the selection box. It starts off in the right place, but when I move the selection down, it jerks down one space, most likely due to the extra space I jammed in the middle of the list.

    upload_2019-10-3_22-1-7.png
    Notice how the text description is describing the fourth quest - the selection box has moved down one, despite the fact the fourth quest is actually the one selected.

    Do you have any idea why this is happening or how to fix it? I'm attaching my code at the bottom of this post once again. Thank you again so much for your help!
    Code:
    //-----------------------------------------------------------------------------
    // Window_MenuQuestList
    //
    // The window for selecting a quest and travelling.
    
    function Window_MenuQuestList() {
        this.initialize.apply(this, arguments);
    }
    
    Window_MenuQuestList.prototype = Object.create(Window_Command.prototype);
    Window_MenuQuestList.prototype.constructor = Window_MenuQuestList;
    
    Window_MenuQuestList.prototype.initialize = function (x, y) {
        Window_Command.prototype.initialize.call(this, x, y);
        this.selectLast();
    
        this._previousIndex = -10;
        this._completedIndex = -10;
    };
    
    Window_MenuQuestList._lastCommandSymbol = null;
    
    Window_MenuQuestList.initCommandPosition = function () {
        this._lastCommandSymbol = null;
    };
    
    Window_MenuQuestList.prototype.windowWidth = function () {
        return 240;
    };
    
    Window_MenuQuestList.prototype.windowHeight = function () {
        return Graphics.boxHeight;
    };
    
    Window_MenuQuestList.prototype.numVisibleRows = function () {
        return this.maxItems();
    };
    
    Window_MenuQuestList.prototype.refresh = function () {
        Window_Command.prototype.refresh.call(this);
    
        this.changePaintOpacity(true);
        this.drawText('Available', 0, 0, this.contentsWidth(), 'center');
        this.drawText('Completed', 0, (this._completedIndex + 1) * this.lineHeight(), this.contentsWidth(), 'center');
    };
    
    Window_MenuQuestList.prototype.update = function () {
        Window_Command.prototype.update.call(this);
    
        if (this.index() != this._previousIndex) {
            this._previousIndex = this.index();
    
            var key = this._questKeyArray[this.index()];
            this._helpWindow._text = $gamePlayer._questInformation[key]["description"];
            this._helpWindow.refresh();
        }
        console.log(this.index());
    };
    
    Window_MenuQuestList.prototype.itemRect = function (index) {
        var rect = Window_Command.prototype.itemRect.call(this, index);
        rect.y += this.lineHeight();
    
        if (index >= this._completedIndex) {
            rect.y += this.lineHeight();
        }
    
        return rect;
    };
    
    
    Window_MenuQuestList.prototype.makeCommandList = function () {
        this.addMainCommands();
    };
    
    Window_MenuQuestList.prototype.addMainCommands = function () {
        var questArray = $gamePlayer._questInformation;
    
        this._questKeyArray = {};
    
        var index = 0;
    
        // Active Quests
    
        for (var key in questArray) {
            var value = questArray[key];
            if (value["unlocked"] === true && value["complete"] === false) {
                this.addCommand(value["title"], undefined, true, undefined);
    
                this._questKeyArray[index] = key;
                index = index + 1;
            }
        }
    
        this._completedIndex = index;
    
        // Completed Quests
    
        for (var key in questArray) {
            var value = questArray[key];
            if (value["unlocked"] === true && value["complete"] === true) {
                this.addCommand(value["title"], undefined, false, undefined);
    
                this._questKeyArray[index] = key;
                index = index + 1;
            }
        }
    };
    
    Window_MenuQuestList.prototype.processOk = function () {
        Window_MenuQuestList._lastCommandSymbol = this.currentSymbol();
        Window_Command.prototype.processOk.call(this);
    };
    
    Window_MenuQuestList.prototype.selectLast = function () {
        this.selectSymbol(Window_MenuQuestList._lastCommandSymbol);
    };
     
    #3
  4. Ossra

    Ossra Formerly Exhydra Veteran

    Messages:
    861
    Likes Received:
    627
    First Language:
    English
    Primarily Uses:
    RMMV
    Code:
    if (index >= this._completedIndex) {
            rect.y += this.lineHeight();
    }
    @nathanlink169 Shouldn't that if statement just be 'index > this._completedIndex' ? I think the extra lineHeight is being added because it equals the index of the last active quest.
     
    #4
  5. nathanlink169

    nathanlink169 Veteran Veteran

    Messages:
    49
    Likes Received:
    16
    Location:
    Ottawa
    First Language:
    English
    Primarily Uses:
    RMMV
    That's what I thought originally too. It's a bit of a weird thing that's going on. It starts on the first item (in this case, "Third Quest")

    As soon as I move either up or down, the entire selection box is permanently one below what it should be at. So, after moving the cursor once, highlighting Fourth Quest will actually select the third quest, highlighting Completed will show the Fourth Quest, and highlighting First Quest will show the first quest. I cannot move the cursor back to highlight Third Quest, even though it was originally there.

    If you would like, I can put together a sample project to send which may show this behaviour a little better.
     
    #5
  6. Ossra

    Ossra Formerly Exhydra Veteran

    Messages:
    861
    Likes Received:
    627
    First Language:
    English
    Primarily Uses:
    RMMV
    @nathanlink169 Hmm, yeah, a sample project would be much easier to troubleshoot.
     
    #6
  7. nathanlink169

    nathanlink169 Veteran Veteran

    Messages:
    49
    Likes Received:
    16
    Location:
    Ottawa
    First Language:
    English
    Primarily Uses:
    RMMV
    I've attached it here - I didn't include the Audio or Img folders, just due to file size - if you just throw the default audio and image folders in there it should work!
     

    Attached Files:

    #7
  8. Ossra

    Ossra Formerly Exhydra Veteran

    Messages:
    861
    Likes Received:
    627
    First Language:
    English
    Primarily Uses:
    RMMV
    @nathanlink169 Ah, I found the issue. When the Window_Command 'initialize' function is being called, the 'addMainCommands' function is run and '_completedIndex' is set to the index of the last active quest. Unfortunately, right after the 'initialize' function is completed, both '_previousIndex' and '_completedIndex' are set to '-10'. Therefore, whenever 'itemRect' checks to see whether the 'index' argument is greater than '_completedIndex', it is always true since no index less than '-10'. The effect is that the cursor is bumped down two lines.

    To correct the issue, place the two variables before the Window_Command 'initialize' is called :

    Code:
    Window_MenuQuestList.prototype.initialize = function (x, y) {
        this._previousIndex = -10;
        this._completedIndex = -10;
     
        Window_Command.prototype.initialize.call(this, x, y);
    
        this.selectLast();
    };

    Also, I created a partial re-write of the current code in order to use an array as the quest container instead of an object to simply several processes :

    Code:
    //-----------------------------------------------------------------------------
    // Scene_GetQuest
    //-----------------------------------------------------------------------------
    
    function Scene_GetQuest() {
      this.initialize.apply(this, arguments);
    }
    
    Scene_GetQuest.prototype = Object.create(Scene_MenuBase.prototype);
    Scene_GetQuest.prototype.constructor = Scene_GetQuest;
    
    Scene_GetQuest.prototype.initialize = function () {
      Scene_MenuBase.prototype.initialize.call(this);
    };
    
    Scene_GetQuest.prototype.create = function () {
      Scene_MenuBase.prototype.create.call(this);
    
      this.createHelpWindow();
      this.createListWindow();
    };
    
    // 'Scene_MenuBase' changed to 'Scene_GetQuest' to keep issues from arising down the road
    Scene_GetQuest.prototype.createHelpWindow = function () {
      this._helpWindow = new Window_QuestHelp();
      this.addWindow(this._helpWindow);
    };
    
    Scene_GetQuest.prototype.createListWindow = function () {
      this._listWindow = new Window_MenuQuestList(0, 0);
      // Removed un-needed handlers
      this._listWindow.setHandler('cancel', this.popScene.bind(this));
      // Added helpWindow to listWindow via built-in helper function
      this._listWindow.setHelpWindow(this._helpWindow);
      this.addWindow(this._listWindow);
    };
    
    //-----------------------------------------------------------------------------
    // Window_MenuQuestList
    //-----------------------------------------------------------------------------
    
    function Window_MenuQuestList() {
      this.initialize.apply(this, arguments);
    }
    
    Window_MenuQuestList.prototype = Object.create(Window_Command.prototype);
    Window_MenuQuestList.prototype.constructor = Window_MenuQuestList;
    
    Window_MenuQuestList.prototype.initialize = function (x, y) {
      Window_Command.prototype.initialize.call(this, x, y);
      this.selectLast();
    
      // No need for 'previousIndex' and 'completedIndex'
    };
    
    // Changed 'lastCommandSymbol' to 'lastCommandId' to return cursor to last selected index
    Window_MenuQuestList._lastCommandId = 0;
    
    Window_MenuQuestList.initCommandPosition = function () {
      this._lastCommandSymbol = 0;
    };
    
    Window_MenuQuestList.prototype.windowWidth = function () {
      return 240;
    };
    
    Window_MenuQuestList.prototype.windowHeight = function () {
      return Graphics.boxHeight;
    };
    
    Window_MenuQuestList.prototype.numVisibleRows = function () {
      return this.maxItems();
    };
    
    Window_MenuQuestList.prototype.refresh = function () {
      Window_Command.prototype.refresh.call(this);
    
      // Get number of completed quests to determine position of 'Completed' header text
      var completed = $gamePlayer._questInformation.filter(function(quest) {
        return !quest.completed;
      });
    
      this.changePaintOpacity(true);
      this.drawText('Available', 0, 0, this.contentsWidth(), 'center');
      this.drawText('Completed', 0, (completed.length - 1) * this.lineHeight(), this.contentsWidth(), 'center');
    };
    
    // Over-write built-in helpWindow update function
    Window_MenuQuestList.prototype.updateHelp = function() {
      // ext of each command contains the index of the quest in the $gamePlayer._questInformation array
      var ext  = this.currentExt();
      var text = $gamePlayer._questInformation[ext].description;
    
      this._helpWindow.setText(text);
      this._helpWindow.refresh();
    };
    
    Window_MenuQuestList.prototype.itemRect = function (index) {
      var rect = Window_Command.prototype.itemRect.call(this, index);
      // Base Y value bump for first header
      rect.y += this.lineHeight();
    
      // If current item is not enabled, item is completed quest
      if (!this.isCommandEnabled(index)) {
        rect.y += this.lineHeight();
      }
    
      return rect;
    };
    
    Window_MenuQuestList.prototype.makeCommandList = function () {
      this.addMainCommands();
    };
    
    // Simplified adding commands
    Window_MenuQuestList.prototype.addMainCommands = function () {
      var questList = $gamePlayer._questInformation;
    
      for (var i = 0; i < questList.length; i++) {
        var quest = questList[i];
    
        if (quest.unlocked) {
          // Title, Symbol, Enabled, Ext (questInformation index)
          this.addCommand(quest.title, 'quest', !quest.complete, i);
        }
      }
    };
    
    Window_MenuQuestList.prototype.processOk = function () {
      // Set lastCommandId to current index on 'Ok'
      Window_MenuQuestList._lastCommandId = this.index();
    
      Window_Command.prototype.processOk.call(this);
    };
    
    Window_MenuQuestList.prototype.selectLast = function () {
      this.select(Window_MenuQuestList._lastCommandId);
    };
    
    //-----------------------------------------------------------------------------
    // Window_QuestHelp
    //-----------------------------------------------------------------------------
    
    function Window_QuestHelp() {
      this.initialize.apply(this, arguments);
    }
    
    Window_QuestHelp.prototype = Object.create(Window_Base.prototype);
    Window_QuestHelp.prototype.constructor = Window_QuestHelp;
    
    Window_QuestHelp.prototype.initialize = function () {
      var width = Graphics.boxWidth - 240;
      var height = this.fittingHeight(2);
    
      Window_Base.prototype.initialize.call(this, 240, 0, width, height);
      this._text = '';
    };
    
    Window_QuestHelp.prototype.setText = function (text) {
      if (this._text !== text) {
        this._text = text;
        this.refresh();
      }
    };
    
    Window_QuestHelp.prototype.clear = function () {
      this.setText('');
    };
    
    Window_QuestHelp.prototype.setItem = function (item) {
      this.setText(item ? item.description : '');
    };
    
    Window_QuestHelp.prototype.refresh = function () {
      this.contents.clear();
    
      this.drawTextEx(this._text, this.textPadding(), 0);
    };
    
    //-----------------------------------------------------------------------------
    // Game_Player
    //-----------------------------------------------------------------------------
    
    // Add a function 'alias'
    var NP_GamePlayer_initMembers = Game_Player.prototype.initMembers;
    
    Game_Player.prototype.initMembers = function() {
      // Call old function to set player variables
      NP_GamePlayer_initMembers.call(this);
    
      // Set 'questInformation' variable so we do not have to set it on the fly
      this._questInformation = [];
    };
    
    Game_Player.prototype.addQuest = function (title, description, unlocked, complete) {
      var quest = {
        "title": title,
        "description": description,
        "unlocked": typeof unlocked !== 'undefined' ? unlocked : true,
        "complete": typeof complete !== 'undefined' ? complete : false
      };
    
      // Add quest object to array
      this._questInformation.push(quest);
    
      // Sort array by complete status
      // Completed quests at bottom of list to simplify printing process in quest list window
      this._questInformation.sort(function(x, y) {
        return (y.complete === x.complete) ? 0 : y.complete ? -1 : 1;
      });
    };
    
    // updateQuest function accepts single property,value pair or an array
    // $gamePlayer.updateQuest('First Quest', 'complete', false);
    // $gamePlayer.updateQuest('First Quest', ['complete', 'unlocked'], [false, true]);
    Game_Player.prototype.updateQuest = function (title, property, value) {
      for (var i = 0; i < this._questInformation; i++) {
        var quest = this._questInformation[i];
    
        if (quest.title === title) {
          if (Array.isArray(property)) {
            if (property.length !== value.length) break;
            property.forEach(function(prop, index) {
              quest[prop] = value[index];
            });
          } else {
            quest[property] = value;
          }
          break;
        }
      }
    };
    
    Adding quests via script calls :

    Code:
    ◆Script:$gamePlayer.addQuest(
    :      :"First Quest",
    :      :"This is the first quest description",
    :      :true,
    :      :true);
    ◆Script:$gamePlayer.addQuest(
    :      :"Second Quest",
    :      :"This is the second quest description",
    :      :false,
    :      :false);
    ◆Script:$gamePlayer.addQuest(
    :      :"Third Quest",
    :      :"This is the third quest description");
    ◆Script:$gamePlayer.addQuest(
    :      :"Fourth Quest",
    :      :"This is the fourth quest description");
    
     
    #8
  9. nathanlink169

    nathanlink169 Veteran Veteran

    Messages:
    49
    Likes Received:
    16
    Location:
    Ottawa
    First Language:
    English
    Primarily Uses:
    RMMV
    Ohhh, this makes sense. Thank you so much for your help! I appreciate it a lot.

    Would you mind if I direct messaged you some questions about your re-write of the code? It's off topic for this forum post, but I have a couple more questions, if you wouldn't mind.
     
    #9
  10. Ossra

    Ossra Formerly Exhydra Veteran

    Messages:
    861
    Likes Received:
    627
    First Language:
    English
    Primarily Uses:
    RMMV
    #10

Share This Page