/* eslint-disable no-unused-expressions */
var MATTIE = MATTIE || {};
MATTIE.multiplayer = MATTIE.multiplayer || {};
/**
* @description This contains all config for the multiplayer mod, configs are split into categories below under the tab titled "name spaces"
* @namespace MATTIE.multiplayer.config
* */
MATTIE.multiplayer.config = MATTIE.multiplayer.config || {};
/**
* @description the number of net packets the client is allowed to send per second,
* turn this up if you are having a lot of lag (to like 50 don't go above 200),
* turn this down if you are getting packet drops. NOTE: if you change this value and hit 'enter'
* and it seems to have little effect, try restarting your game once, then ensure this value remained
* what you set it to. and try to play again, some changes from this require a restart. (f5 is fine)
* @default 50
*/
MATTIE.multiplayer.config.maxPacketsPerSecond = 50;
/**
* @namespace MATTIE.multiplayer.config.scaling
* @description The namespace containing all configurable options for multiplayer scaling / balancing. <br>
* While this is intended to let you customize the difficulty to your taste you can also just crank the values super high / low and do silly things
* */
MATTIE.multiplayer.config.scaling = MATTIE.multiplayer.config.scaling || {};
//---------------------------------------------------------
// Enemy Scaling
//---------------------------------------------------------
/**
* @default 1
* @configurable
* @description The scaler for when enemies will attack in combat. IE: the higher this number is the later in combat enemies will act.
* */
MATTIE.multiplayer.config.scaling.enemyBattleIndexScaler;
Object.defineProperties(MATTIE.multiplayer.config.scaling, {
enemyBattleIndexScaler: {
get: () => MATTIE.configGet('enemyBattleIndexScaler', 1),
set: (value) => { MATTIE.configSet('enemyBattleIndexScaler', value); },
},
});
/**
* @description The formula for where the enemies attack in combat, by default,
* after one party could've attacked it is the enemies turn, then the rest of the players
* !!DANGER!! don't touch this unless you know what you are doing.
* @param {Game_Battler} battler
*/
MATTIE.multiplayer.config.scaling.enemyBattleIndex = (battler) => (
battler.forcedIndex || (
($gameParty.maxBattleMembers() / $gameTroop.totalCombatants())) * (battler.agi / 10) * MATTIE.multiplayer.config.scaling.enemyBattleIndexScaler
);
//---------------------------------------------------------
// Healing Whispers Scaling
//---------------------------------------------------------
/**
* @default true.
* @description whether or not healing whispers gets scaled
*/
MATTIE.multiplayer.config.scaling.shouldScaleHealingWhispers;
/**
* @description this number is divided by the number of players in the current combat when calculating healing whispers scaling.
* IE: when 1 player is in combat 1/1 =1 so healing whispers will be normal
* IE: when 2 players are in combat 1/2 = .5 so healing whispers will deal .5 healing to all 8 players
* @default 1
*/
MATTIE.multiplayer.config.scaling.healingWhispersScaler;
/**
* @description whether party actions, like healing whispers target all parties.
* @default true, there are other scalers that nerf these abilities to make this fair
*/
MATTIE.multiplayer.config.scaling.partyActionsTargetAll;
Object.defineProperties(MATTIE.multiplayer.config.scaling, {
shouldScaleHealingWhispers: {
get: () => MATTIE.configGet('shouldScaleHealingWhispers', true),
set: (value) => { MATTIE.configSet('shouldScaleHealingWhispers', value); },
},
healingWhispersScaler: {
get: () => MATTIE.configGet('healingWhispersScaler', 1),
set: (value) => { MATTIE.configSet('healingWhispersScaler', value); },
},
partyActionsTargetAll: {
get: () => MATTIE.configGet('partyActionsTargetAll', true),
set: (value) => { MATTIE.configSet('partyActionsTargetAll', value); },
},
});
/**
* @description the function to scale healing whispers.
* !!Danger!! don't edit this unless you know what you are doing.
*/
MATTIE.multiplayer.config.scaling.getHealingWhispersScaler = () => (MATTIE.multiplayer.config.scaling.healingWhispersScaler / $gameTroop.totalCombatants());
//---------------------------------------------------------
// Resurrection Cost
//---------------------------------------------------------
/**
* @description whether resurrecting an ally has a cost
* @default true
*/
MATTIE.multiplayer.config.scaling.resurrectionHasCost;
/**
* @description whether resurrecting an allies requires necromancy
* @default false //not implemented yet
*/
MATTIE.multiplayer.config.scaling.resurrectionNeedsNecromancy;
/**
* @description whether resurrecting an ally requires sacrificing an actor/party member
* @default false
*/
MATTIE.multiplayer.config.scaling.resurrectionActorCost;
/**
* @description the number of actors that must be sacrificed.
* @default 1
*/
MATTIE.multiplayer.config.scaling.actorCost;
/**
* @description whether resurrecting an ally has an item cost
* @default true
*/
MATTIE.multiplayer.config.scaling.resurrectionItemCost;
/**
* @description the item id of the item that will be consumed to revive someone
* @default 116, lesser soul
*/
MATTIE.multiplayer.config.scaling.resurrectionItemId;
/**
* @description the number of the resurrection item that is needed to revive a player (if one is needed)
* @default 1
* */
MATTIE.multiplayer.config.scaling.resurrectionItemAmount;
/**
* @description whether enemy hp should be scaled default should be true, but currently false due to bug
* @default true
*/
MATTIE.multiplayer.config.scaling.scaleHp;
/**
* @description this is multiplied by the max hp of all enemies
* @default 1.3
*/
MATTIE.multiplayer.config.scaling.hpScaler;
/**
* @description when there are more than one player in combat with an enemy,
* the number of players is divided by this number then the enemies' health is multiplied by this.
* @default 1.2
*/
MATTIE.multiplayer.config.scaling.hpPlayerDivisor;
Object.defineProperties(MATTIE.multiplayer.config.scaling, {
resurrectionHasCost: {
get: () => MATTIE.configGet('resurrectionHasCost', true),
set: (value) => { MATTIE.configSet('resurrectionHasCost', value); },
},
resurrectionNeedsNecromancy: {
get: () => MATTIE.configGet('resurrectionNeedsNecromancy', false),
set: (value) => { MATTIE.configSet('resurrectionNeedsNecromancy', value); },
},
resurrectionActorCost: {
get: () => MATTIE.configGet('resurrectionActorCost', false),
set: (value) => { MATTIE.configSet('resurrectionActorCost', value); },
},
actorCost: {
get: () => MATTIE.configGet('actorCost', true),
set: (value) => { MATTIE.configSet('actorCost', value); },
},
resurrectionItemCost: {
get: () => MATTIE.configGet('resurrectionItemCost', true),
set: (value) => { MATTIE.configSet('resurrectionItemCost', value); },
},
resurrectionItemId: {
get: () => MATTIE.configGet('resurrectionItemId', 116),
set: (value) => { MATTIE.configSet('resurrectionItemId', value); },
},
resurrectionItemAmount: {
get: () => MATTIE.configGet('resurrectionItemAmount', 1),
set: (value) => { MATTIE.configSet('resurrectionItemAmount', value); },
},
scaleHp: {
get: () => MATTIE.configGet('scaleHp', true),
set: (value) => { MATTIE.configSet('scaleHp', value); },
},
hpScaler: {
get: () => MATTIE.configGet('hpScaler', 1.0),
set: (value) => { MATTIE.configSet('hpScaler', value); },
},
hpPlayerDivisor: {
get: () => MATTIE.configGet('hpPlayerDivisor', 1.0),
set: (value) => { MATTIE.configSet('hpPlayerDivisor', value); },
},
});
/**
* @description the function to handle resurrection cost
* !!Danger!! only edit this if you know what you are doing.
* @returns {boolean}
*/
MATTIE.multiplayer.config.scaling.resurrectionCost = () => {
if (!MATTIE.multiplayer.config.scaling.resurrectionHasCost) return true;
const item = $dataItems[MATTIE.multiplayer.config.scaling.resurrectionItemId];
const itemsNum = $gameParty.numItems(item);
let hasCost = true;
if (MATTIE.multiplayer.config.scaling.resurrectionItemCost) {
if (itemsNum < MATTIE.multiplayer.config.scaling.resurrectionItemAmount) {
hasCost = false;
}
}
if (MATTIE.multiplayer.config.scaling.resurrectionActorCost) {
if ($gameParty.battleMembers().length - 1 < MATTIE.multiplayer.config.scaling.actorCost) {
hasCost = false;
}
}
if (!hasCost) return false;
if (MATTIE.multiplayer.config.scaling.resurrectionActorCost) {
for (let index = 1; index < MATTIE.multiplayer.config.scaling.actorCost + 1; index++) {
const element = $gameParty.battleMembers()[index];
$gameParty.removeActor(element.actorId());
}
}
if (MATTIE.multiplayer.config.scaling.resurrectionItemCost) $gameParty.loseItem(item, MATTIE.multiplayer.config.scaling.resurrectionItemAmount);
return true;
};
/**
* @description the function to scale hp
* @returns {number} the scaler for the enemies' hp
* !!DANGER!! only edit this if you know what you are doing
*/
MATTIE.multiplayer.config.scaling.hpScaling = (forceLocal = false) => {
// If we are a client and have received a strict scaling factor from the Host, use it ALWAYS.
// unless forceLocal is true (used for calculating the "Before" state during transitions)
if (!forceLocal && !MATTIE.multiplayer.isHost && MATTIE.multiplayer.hostScalingFactor) {
return MATTIE.multiplayer.hostScalingFactor;
}
if (!MATTIE.multiplayer.config.scaling.scaleHp) return MATTIE.multiplayer.config.scaling.hpScaler;
const totalCombatants = $gameTroop.totalCombatants();
const playerScaler = totalCombatants > 1 ? totalCombatants / MATTIE.multiplayer.config.scaling.hpPlayerDivisor : 1;
return (playerScaler * MATTIE.multiplayer.config.scaling.hpScaler);
};
//---------------------------------------------------------
// MISC
//---------------------------------------------------------
/**
* @description whether body blocking is enabled or not
* @default false
*/
MATTIE.multiplayer.config.bodyBlocking = false;
/**
* @description whether players can interact with each other
* @default true
*/
MATTIE.multiplayer.config.canInteract = true;
Object.defineProperties(MATTIE.multiplayer.config, {
canInteract: {
get: () => MATTIE.configGet('canInteract', true),
set: (value) => { MATTIE.configSet('canInteract', value); },
},
bodyBlocking: {
get: () => MATTIE.configGet('bodyBlocking', false),
set: (value) => { MATTIE.configSet('bodyBlocking', value); },
},
});
/** @description the base function to check the passage of the game map */
MATTIE.multiplayer.config.scaling.checkPassage = Game_Map.prototype.checkPassage;
/**
* @description override the checkpassage function to turn on body blocking.
* !!DANGER!! Only edit this if you know what you are doing
* @param {number} x the x cord
* @param {number} y the y cord
* @param {number} bit idk
* @returns {boolean} whether the map is passable at the point
*
*/
Game_Map.prototype.checkPassage = function (x, y, bit) {
const val = MATTIE.multiplayer.config.scaling.checkPassage.call(this, x, y, bit);
if (MATTIE.multiplayer.config.scaling.bodyBlocking) {
const netCont = MATTIE.multiplayer.getCurrentNetController();
const playerIds = Object.keys(netCont.netPlayers);
for (let index = 0; index < playerIds.length; index++) {
/** @type {PlayerModel} */
const netPlayer = netCont.netPlayers[playerIds[index]];
if (netPlayer.$gamePlayer.x === x && netPlayer.$gamePlayer.y === y) return false;
}
}
return val;
};
// all of this is deprecated as it doesnt work properly for hpscaling
var _Game_Enemy_param = Game_Enemy.prototype.param;
Game_Enemy.prototype.param = function (paramId) {
let val = _Game_Enemy_param.call(this, paramId);
if (paramId === 0) {
// max HP
// Use cached scaling factor if available (Stable), otherwise dynamic (Volatile)
const scaler = (this._scalingFactor !== undefined) ? this._scalingFactor : MATTIE.multiplayer.config.scaling.hpScaling();
// Initialize _scalingFactor if undefined (first call)
if (this._scalingFactor === undefined) {
this._scalingFactor = scaler;
if (MATTIE.multiplayer.devTools.battleLogger && MATTIE.multiplayer.hostScalingFactor) {
console.log(`[Scaling] Enemy ${this.index()} initialized with factor: ${scaler}`);
}
}
val *= scaler;
}
return val;
};
/**
* @description Helper to update combatants while preserving enemy HP percentages/absolute values relative to scaling changes.
* @param {Function} updateAction The function to execute that modifies the combatant count.
*/
MATTIE.multiplayer.config.scaling.applyTroopScaling = function (updateAction) {
// If no enemies exist yet, just update and return.
// We check members length instead of inBattle() to catch edge cases during initialization.
// if ($gameTroop.members().length 0) {
// updateAction();
// return;
// }
var enemies = $gameTroop.members();
// 1. Lock Initial State
// If enemies lack a cached factor, pin them to the *current* global logic (before update changes the count)
// This fixes the "2000/3000" bug where initialization happens at Factor 1.0, but scaling logic sees Factor 1.5 immediately.
var currentGlobalFactor = MATTIE.multiplayer.config.scaling.hpScaling();
enemies.forEach((e, idx) => {
if (e.isEnemy() && e._scalingFactor === undefined) {
// If undefined, we assume it was using the Local Calculation logic
// But if we are a client connecting to a host, our Local Logic (1.0) is the "Old" state.
// If hpScaling() returns 2.0 (because we set hostScalingFactor), we should lock to 1.0 (local).
// BUT only if we believe we were truly local.
let assumedFactor = currentGlobalFactor;
if (!MATTIE.multiplayer.isHost && MATTIE.multiplayer.hostScalingFactor) {
// We are client, and we have a host factor.
// Did we just get it? Inspect `forceLocal`.
const localCalc = MATTIE.multiplayer.config.scaling.hpScaling(!MATTIE.getCurrentNetController().isHost);
if (localCalc !== currentGlobalFactor) {
assumedFactor = localCalc;
if (MATTIE.multiplayer.devTools.battleLogger) {
console.log(`[Scaling] Enemy ${idx} uninitialized. Assuming transition from Local (${localCalc}) -> Host (${currentGlobalFactor})`);
}
}
}
e._scalingFactor = assumedFactor;
}
});
// 2. Snapshot current MHPs using the locked factor
var oldMhps = enemies.map((e) => e.param(0));
// 3. Perform the update (add/remove combatant)
updateAction();
// 4. Determine NEW Scaling Factor based on new combatant count
var newGlobalFactor = MATTIE.multiplayer.config.scaling.hpScaling();
// 5. Apply Proportional Scaling to Current HP
enemies.forEach((enemy, index) => {
if (!enemy.isEnemy()) return; // safety
// Update the cached scaling factor for this enemy to the new stable value
enemy._scalingFactor = newGlobalFactor;
var oldMhp = oldMhps[index];
// If getting param failed or is weird, fallback to base
if (typeof oldMhp !== 'number' || oldMhp <= 0) oldMhp = enemy.paramBase(0);
// Get new MHP (will use the _scalingFactor we just set)
var newMhp = enemy.param(0);
// Safety check against zero division
if (oldMhp <= 0) oldMhp = 1;
// CRITICAL: optimization to avoid micro-rounding errors triggers "damage" logic
if (Math.abs(oldMhp - newMhp) < 1) return;
var hpPercent = enemy._hp / oldMhp;
var newHp = Math.floor(newMhp * hpPercent);
// Prevent accidental scaling death if HP > 0
if (enemy._hp > 0 && newHp <= 0) newHp = 1;
var diff = newHp - enemy._hp;
if (diff !== 0) {
enemy._hp = newHp;
// Log for debug before changing
if (MATTIE.multiplayer.devTools.battleLogger) {
const percent = (hpPercent * 100).toFixed(1);
const oldHp = enemy._hp + diff;
console.log(`[Scaling] Enemy ${index} HP Scaled: ${oldMhp} -> ${newMhp} (${percent}%). HP: ${oldHp} -> ${newHp}`);
}
// Updates HUD if needed
if (typeof enemy.refresh === 'function') enemy.refresh();
}
});
};
/**
* @description edit the dataEnemies obj of a set enemy to have its scaled amount of health as determined by scaling and config above
* @param {*} enemyId the id of the data enemy
*/
function updateHpOfEnemy(enemyId) {
if (!$dataEnemies[enemyId].scaled) {
$dataEnemies[enemyId].params[0] *= MATTIE.multiplayer.config.scaling.hpScaling();
// // console.log(`#${enemyId} mhp:${$dataEnemies[enemyId].params[0]}`);
$dataEnemies[enemyId].scaled = true;
}
}
/**
* @description adjust the hp of every enemy within memory
* !!DANGER!! don't edit this unless you know what your doing.
*/
// setTimeout(() => {
// $dataEnemies.forEach((enemy) => {
// if (enemy) {
// updateHpOfEnemy(enemy.id);
// }
// });
// }, 5000);
// deprecated runtime solution.
// // MATTIE_RPG.Game_Enemy_Setup = Game_Enemy.prototype.setup;
// // Game_Enemy.prototype.setup = function (enemyId, x, y) {
// // updateHpOfEnemy(enemyId);
// // MATTIE_RPG.Game_Enemy_Setup.call(this, enemyId, x, y);
// // };
// // /** @description the default transform cmd */
// // MATTIE_RPG.Game_Enemy_TRANSFORM = Game_Enemy.prototype.transform;
// // /** @description extend the transform method to do the same as above */
// // Game_Enemy.prototype.transform = function (enemyId) {
// // updateHpOfEnemy(enemyId);
// // // // console.log(`#${enemyId} mhp:${$dataEnemies[enemyId].params[0]}`);
// // MATTIE_RPG.Game_Enemy_TRANSFORM.call(this, enemyId);
// // };1
//---------------------------------------------------------
// Client Side Configs
//---------------------------------------------------------
/**
* @description whether the menu to display what party the player is currently viewing should display
* @default true
*/
MATTIE.multiplayer.config.showViewingMenu;
/**
* @description whether the menu to display number of allies in the current fight should display
* @default true
*/
MATTIE.multiplayer.config.showAlliesMenu;
/**
* @description whether players should be allowed to move during text and cut scenes
* @default true
*/
MATTIE.multiplayer.config.freeMove;
Object.defineProperties(MATTIE.multiplayer.config, {
showViewingMenu: {
get: () => MATTIE.configGet('showViewingMenu', true),
set: (value) => { MATTIE.configSet('showViewingMenu', value); },
},
showAlliesMenu: {
get: () => MATTIE.configGet('showAlliesMenu', true),
set: (value) => { MATTIE.configSet('showAlliesMenu', value); },
},
freeMove: {
get: () => MATTIE.configGet('freeMove', true),
set: (value) => {
MATTIE.unstuckAPI.togglePlayerFreeMove(value);
MATTIE.configSet('freeMove', value);
},
},
});