Do you write plugin Y as compatibility patch of plugin X?

Discussion in 'Learning Javascript' started by DoubleX, May 29, 2016.

?

Do you write plugin Y as compatibility patch of plugin X?

  1. Always for all plugins

    0 vote(s)
    0.0%
  2. Never for any plugin

    0 vote(s)
    0.0%
  3. Always for addressing compatibility with plugins written by yourselves

    0 vote(s)
    0.0%
  4. Never for addressing compatibility with plugins written by yourselves

    0 vote(s)
    0.0%
  5. Always for addressing compatibility with plugins written by others

    0 vote(s)
    0.0%
  6. Never for addressing compatibility with plugins written by others

    0 vote(s)
    0.0%
  7. Depends on actual situations

    0 vote(s)
    0.0%
Multiple votes are allowed.
  1. DoubleX

    DoubleX Just a nameless weakling Veteran

    Messages:
    1,462
    Likes Received:
    542
    First Language:
    Chinese
    Primarily Uses:
    N/A
    For example, I've written DoubleX RMMV Popularized ATB Compatibility to address compatibility issues with DoubleX RMMV Popularized ATB Core.


    To me, this approach can somehow separate compatibility issue resolutions from plugin functionality implementations, possibly bring some advantages in some situations:


    1. The plugin functionality implementations easier, simpler and smaller to be thoroughly comprehended, as it's not mixed with compatibility issue resolutions.


    2. In cases where plugin Z must be placed below plugin X to solve their compatibility issues, the plugin Y meant to solve compatibility issues with plugin X can simply be placed below plugin Z.


    3. In cases where users don't face any compatibility issues with plugin X, they won't have to activate the compatibility issue resolutions, which is redundant for them.


    4. The chances for changing compatibility issue resolutions without changing plugin functionality implementations can be significantly increased, thus noticeably reducing the cost of maintaining version control.


    5. The size of the plugin implementing its functionality will be less likely to be out of control, due to not having to include compatibility issue resolutions, which can sometimes address dozens of plugins.


    Nevertheless, this approach might also has some potential drawbacks:


    1. It can bloat the plugin manager rather quickly if there are lots of plugins each having their corresponding separate compatibility plugin


    2. Users have to remember to check the status of both the plugins themselves and their corresponding separate compatibility plugins


    3. Writing a separate plugin to resolve compatibility issues of another plugin generally means at least slightly more effort needed by plugin developers


    4. Plugin developers have to check those plugins themselves as well as their corresponding compatibility plugins in order to thoroughly comprehend those compatibility issues and how they're fixed


    5. Changing those plugins themselves sometimes mean needing to change their corresponding separate compatibility plugins as well


    Therefore, sometimes I feel/think this approach's not called for. For instance, the compatibility issues between DoubleX RMMV Popularized ATB Charge and  DoubleX RMMV Popularized ATB Cooldown is instead handled in the former plugin.


    It's because I feel/think that  DoubleX RMMV Popularized ATB Compatibility should address compatibility issues with plugins outside the PATB system, but not addons inside that system.
     


    Right now my setup using this approach is something like this(DoubleX RMMV Action Cost Compatibility v1.00b):

    if (DoubleX_RMMV['Act Cost']) {

    DoubleX_RMMV.Act_Cost_Compatibility = {};

    /*----------------------------------------------------------------------------*/

    if (DoubleX_RMMV['MATB'] || DoubleX_RMMV["PATB Core"]) { // v1.00b+

    var compatibilityContainer;

    if (DoubleX_RMMV['MATB']) {

    compatibilityContainer = DoubleX_RMMV.Act_Cost_Compatibility['MATB'] = {};

    } else if (DoubleX_RMMV["PATB Core"]) {

    compatibilityContainer = DoubleX_RMMV.Act_Cost_Compatibility["PATB Core"] = {};

    }

    (function(AC, ACCATB) {

    'use strict';

    ACCATB.BattleManager = {};
    var BM = ACCATB.BattleManager;

    BM.processTurn = BattleManager.processTurn;
    BattleManager.processTurn = function() {
    // Added to ensures battlers can use valid actions upon executing them
    this._subject.actCostInputs = -999;
    // MATB Act Validity
    // PATB Act Validity
    BM.processTurn.apply(this, arguments);
    }; // BattleManager.processTurn

    })(DoubleX_RMMV.Act_Cost, compatibilityContainer);

    } // if (DoubleX_RMMV['MATB'] || DoubleX_RMMV["PATB Core"])

    /*----------------------------------------------------------------------------*/

    if (DoubleX_RMMV["Unison Item Default"]) {

    DoubleX_RMMV.Act_Cost_Compatibility["Unison Item Default"] = {};

    (function(AC, UI, ACCUI) {

    'use strict';

    var DM = UI.DataManager;
    ACCUI.DataManager = {};
    var DMACCUI = ACCUI.DataManager;

    // data: The data to have its notetags read
    DMACCUI.loadItemNotes = DM.loadItemNotes;
    DM.loadItemNotes = function(data) {
    DMACCUI.loadItemNotes(data);
    // Added to read and store values of <unison item actor act cost: costs>
    var uIAACs = data.meta.unisonItemActorActCosts = [];
    var ACs = /< *unison +item +actor +act +cost *: *(\d+(?: *, *\d+)*) *>/i;
    data.note.split(/[\r\n]+/).forEach(function(line) {
    if (line.match(ACs)) { DM.storeItemNotes(uIAACs); }
    });
    // Unison Item Config Act Cost
    }; // DM.loadItemNotes

    var BM = AC.BattleManager;
    ACCUI.BattleManager = {};
    var BMACCUI = ACCUI.BattleManager;

    BMACCUI.reserveActs = BM.reserveActs;
    BM.reserveActs = function() {
    // Rewritten to not using this function for unison skills/items
    var actor = this.actor();
    if (!actor) { return; }
    if (actor.inputtingAction().item().meta.unisonItemActors.length <= 1) {
    BMACCUI.reserveActs.apply(this, arguments);
    }
    // Unison Item Config Act Cost
    }; // BM.reserveActs

    BMACCUI.releaseReservedActs = BM.releaseReservedActs;
    BM.releaseReservedActs = function() {
    // Added to not using this function for unison skills/items
    var actor = this.actor();
    if (!actor) { return; }
    var act = actor.inputtingAction();
    if (act.item().meta.unisonItemActors.length > 1) { return act.clear(); }
    // Unison Item Config Act Cost
    BMACCUI.releaseReservedActs.apply(this, arguments);
    }; // BM.releaseReservedActs

    BM = UI.BattleManager;

    BM.addUnisonActors = function() { // Rewrite
    var actor = this.actor(), act, item;
    if (actor) { act = actor.inputtingAction(); }
    if (act) { item = act.item(); }
    if (!item || item.meta.unisonItemActors.length <= 1) { return; }
    var actorIds = item.meta.unisonItemActors;
    // Rewritten to store the action cost for each unison invokee as well
    var actSlot = [actor.index(), actor.actionInputIndex];
    var actCosts = item.meta.unisonItemActorActCosts.slice(0);
    var actorId = actor.actorId(), actCost = item.meta.actCost;
    BM.unisonActors[actSlot] = [actorIds, actCosts];
    for (var index = 0, length = actorIds.length; index < length; index++) {
    actCosts[index] = actCosts[index] || actCost;
    if (actorId === actorIds[index]) { actCosts[index] -= 1; }
    if (!actCosts[index]) { actCosts.push(actCosts[index]); }
    $gameActors.actor(actorIds[index]).actCostInputs += actCosts[index];
    }
    // Unison Item Config Act Cost
    // Unison Item Default Invokee Act Slot Num
    }; // BM.addUnisonActors

    // actor: The currently selected actor
    BM.eraseUnisonActors = function(actor) { // Rewrite
    var actors = BM.unisonActors[[actor.index(), actor.actionInputIndex]];
    if (!actors) { return; }
    BM.unisonActors[[actor.index(), actor.actionInputIndex]] = null;
    // Rewritten to address the action cost for each unison invokee as well
    var actorIds = actors[0], actCosts = actors[1];
    for (var i = 0, length = actorIds.length; i < length; i++) {
    $gameActors.actor(actorIds).actCostInputs -= actCosts;
    }
    // Unison Item Config Act Cost
    // Unison Item Default Invokee Act Slot Num
    }; // BM.eraseUnisonActors

    BMACCUI.clearUnisonActors = BM.clearUnisonActors;
    BM.clearUnisonActors = function() {
    BMACCUI.clearUnisonActors.apply(this, arguments);
    // Added to actCostInputs for reserving action slots from invokees too
    $gameParty.movableMembers().forEach(function(mem) {
    mem.actCostInputs = -999;
    });
    // Unison Item Config Act Cost
    // Unison Item Default Invokee Act Slot Num
    }; // BM.clearUnisonActors

    var GBB = AC.Game_BattlerBase;
    ACCUI.Game_BattlerBase = {};
    var GBBACCUI = ACCUI.Game_BattlerBase;
    GBBACCUI.canReserveActs = GBB.canReserveActs;
    GBB.canReserveActs = function(item) {
    // Added to check for unison acion cost for the unison invoker as well
    if (!item) { return false; }
    if (this.isActor()) {
    var emptyActs = this._actions.filter(function(act) {
    return !act.item();
    }).length;
    var actorId = this.actorId(), actorIds = item.meta.unisonItemActors;
    var actCosts = item.meta.unisonItemActorActCosts;
    if (actorIds.length > 1) {
    for (var i = 0, length = actorIds.length; i < length; i++) {
    if (actorId !== actorIds) { continue; }
    if (!actCosts) { break; }
    return actCosts <= emptyActs - this._actCostInputs;
    }
    }
    }
    // Unison Item Config Act Cost
    return GBBACCUI.canReserveActs.apply(this, arguments);
    }; // GBB.canReserveActs

    GBB = UI.Game_BattlerBase;

    GBBACCUI.canInput = Game_BattlerBase.prototype.canInput;
    Game_BattlerBase.prototype.canInput = function() {
    // Rewritten to check if at least 1 action slot isn't reserved
    if (!GBBACCUI.canInput.apply(this, arguments)) { return false; }
    if (!this.isActor() || !$gameParty.inBattle()) { return true; }
    return this._actCostInputs < this._actions.length;
    // Unison Item Default Invokee Act Slot Num
    }; // Game_BattlerBase.prototype.canInput

    GBB.canUseUnisonSkill = function(skill) { // Rewrite
    var actor, actorIds = skill.meta.unisonItemActors;
    var inBattle = $gameParty.inBattle(), mems = $gameParty.aliveMembers();
    // Added
    var actCosts = skill.meta.unisonItemActorActCosts;
    var actCost = skill.meta.actCost;
    // Unison Item Default Invokee Act Slot Num
    var learnFlags = skill.meta.unisonItemActorLearn;
    for (var i = 0, length = actorIds.length; i < length; i++) {
    if (this.actorId() === actorIds) { continue; }
    actor = mems.filter(function(m) {
    return m.index() > this.index() && m.actorId() === actorIds;
    }, this)[0];
    if (!actor || inBattle && !actor.canInput()) { return false; }
    if (!actor.meetsSkillConditions(skill)) { return false; }
    // Added to check if the unison invokee can pay the action cost
    if (!inBattle) { continue; }
    if (!GBB.hasEnoughActCost.call(actor, actCosts || actCost)) {
    return false;
    }
    // Unison Item Default Invokee Act Slot Num
    if (learnFlags && actor.skills().every(function(s) {
    return s !== skill;
    })) { return false; }
    }
    return true;
    }; // GBB.canUseUnisonSkill

    GBB.canUseUnisonItem = function(item) { // Rewrite
    if (!this.meetsItemConditions(item)) { return false; }
    var actor, actorIds = item.meta.unisonItemActors;
    var inBattle = $gameParty.inBattle(), mems = $gameParty.aliveMembers();
    // Added
    var actCosts = item.meta.unisonItemActorActCosts;
    var actCost = item.meta.actCost;
    // Unison Item Default Invokee Act Slot Num
    for (var i = 0, length = actorIds.length; i < length; i++) {
    if (this.actorId() === actorIds) { continue; }
    actor = mems.filter(function(m) {
    return m.index() > this.index() && m.actorId() === actorIds;
    }, this)[0];
    if (!actor || inBattle && !actor.canInput()) { return false; }
    // Added to check if the unison invokee can pay the action cost
    if (!inBattle) { continue; }
    if (!GBB.hasEnoughActCost.call(actor, actCosts || actCost)) {
    return false;
    }
    // Unison Item Default Invokee Act Slot Num
    }
    return true;
    }; // GBB.canUseUnisonItem

    // actCost: The number of action slots needed for the action
    GBB.hasEnoughActCost = function(actCost) { // New
    // Checks if the unison invokee has enough empty action slots
    return this._actions.length - this._actCostInputs >= actCost;
    // Unison Item Default Invokee Act Slot Num
    }; // GBB.hasEnoughActCost

    var WIL = AC.Window_ItemList;
    ACCUI.Window_ItemList = {};
    var WILACCUI = ACCUI.Window_ItemList;

    WILACCUI.drawActCost = WIL.drawActCost;
    WIL.drawActCost = function(item, x, y, width) {
    // Added to draw the act cost in battle for each unison actor
    if ($gameParty.inBattle()) {
    var actorIds = item.meta.unisonItemActors;
    if (actorIds.length > 1) {
    var actCosts = item.meta.unisonItemActorActCosts;
    var actorId = BattleManager.actor().actorId();
    for (var i = 0, length = actorIds.length; i < length; i++) {
    if (!actCosts || actorId !== actorIds) { continue; }
    return this.drawText(actCosts, x, y, width, 'right');
    }
    }
    }
    // Unison Item Config Act Cost
    WILACCUI.drawActCost.apply(this, arguments);
    }; // WIL.drawActCost

    var WSL = AC.Window_SkillList;
    ACCUI.Window_SkillList = {};
    var WSLACCUI = ACCUI.Window_SkillList;

    WSLACCUI.drawActCost = WSL.drawActCost;
    WSL.drawActCost = function(skill, x, y, width) {
    // Added to draw the act cost for each unison actor
    this.changeTextColor(this.normalColor());
    var actorIds = skill.meta.unisonItemActors;
    if (actorIds.length > 1) {
    var actCosts = skill.meta.unisonItemActorActCosts;
    var actorId = this._actor.actorId();
    for (var i = 0, length = actorIds.length; i < length; i++) {
    if (!actCosts || actorId !== actorIds) { continue; }
    return this.drawText(actCosts, x, y, width, 'right');
    }
    }
    // Unison Item Config Act Cost
    WSLACCUI.drawActCost.apply(this, arguments);
    }; // WSL.drawActCost

    })(DoubleX_RMMV.Act_Cost, DoubleX_RMMV.Unison_Item,
    DoubleX_RMMV.Act_Cost_Compatibility["Unison Item Default"]);

    } // if (DoubleX_RMMV["Unison Item Default"])

    /*----------------------------------------------------------------------------*/

    } else {
    alert('Place Action Cost Compatibility below Action Cost.');
    } // if (DoubleX_RMMV['Act Cost'])






    Besides, some plugin developers don't write plugin Y as comaptibility patch of plugin X even when plugin Y's only addressing compatibility issues with plugins that are completely foreign to plugin X.


    For instance, consider the below section of code in Enemy State Overlays written by DreamX:

    if (!Imported.YEP_X_AnimatedSVEnemies) {
    Sprite_Enemy_prototype_initMembers = Sprite_Enemy.prototype.initMembers;
    Sprite_Enemy.prototype.initMembers = function () {
    Sprite_Enemy_prototype_initMembers.call(this);
    this.createStateSprite();
    };
    } else {
    Yanfly_SVE_Sprite_Enemy_setBattler = Yanfly.SVE.Sprite_Enemy_setBattler;
    Yanfly.SVE.Sprite_Enemy_setBattler = function (battler) {
    Yanfly_SVE_Sprite_Enemy_setBattler.call(this, battler);
    this.createStateSprite();
    };
    }

    While it's as simple as extending different functions based on whether another plugin's activated(although in this case this plugin must be placed below YEP_X_AnimatedSVEnemies in order for the compatibility issue resolution to work), this approach makes sense to me, because both the plugin functionality implementation and compatibility issue resolution are so easy, simple and small that writing a separate plugin to resolve that compatibility issue would be overkill to me.


    So my vote is "Depends on actual situations".


    What's your approach on resolving compatibility issues? What do you think about the aforementioned ones? Let's share your views here :)
     
    Last edited by a moderator: May 29, 2016
    #1
  2. Victor Sant

    Victor Sant Veteran Veteran

    Messages:
    1,694
    Likes Received:
    1,434
    Location:
    Brazil
    First Language:
    Portuguese
    I did make scripts only for compatibility patches on Ace once and the result was pretty bad. So I would never make a whole plugin exclusively to address a compatibility issue. Adding some code to existing plugins for compatibility is something I do one time or another, although another sour experience is making me think twice before doing it again.
     
    #2
    DoubleX likes this.
  3. Kino

    Kino EIS Game Dev Veteran

    Messages:
    515
    Likes Received:
    672
    Location:
    United States
    First Language:
    English
    Primarily Uses:
    RMMV
     If it suits the needs for what people want, I'll make the change if it's highly requested. However, I wouldn't go out of my way to make a plugin with compatibility in mind. It really depends on the situation.
     
    #3
    DoubleX likes this.

Share This Page