543 lines
No EOL
20 KiB
JavaScript
543 lines
No EOL
20 KiB
JavaScript
/*:ja
|
||
* @target MZ
|
||
* @base PluginCommonBase
|
||
* @author kurogoma knights
|
||
* @plugindesc KN: ステータスポイント管理プラグイン(単一アクター向け)
|
||
*
|
||
* @param pointsPerLevel
|
||
* @text レベルごとの付与ポイント
|
||
* @type number
|
||
* @default 1
|
||
* @desc レベルが上がったときに付与するポイント数(暫定で1)
|
||
*
|
||
* @help
|
||
* KN_StatusPoint
|
||
* ・目的
|
||
* レベルアップ時にステータスポイントを付与し、変数33〜37の値を
|
||
* ステータスへ反映します(HP/MPは1ポイント=+5、それ以外は1ポイント=+1)。
|
||
*
|
||
* ・仕様(現状)
|
||
* - 変数33: HPに対応(MHPパラメータ。1ポイント=+5)
|
||
* - 変数34: MPに対応(MMPパラメータ。1ポイント=+5)
|
||
* - 変数35: 攻撃(ATK、1ポイント=+1)
|
||
* - 変数36: 防御(DEF、1ポイント=+1)
|
||
* - 変数37: 素早さ(AGI、1ポイント=+1)
|
||
* - レベルアップで付与されるポイントはプラグインパラメータで設定
|
||
* された値(デフォルト1)。付与先は変数番号22に加算されます。UIは別途作成予定です。
|
||
*
|
||
* ・備考
|
||
* - 本プラグインは単一プレイヤー用(対象アクターはID=1に固定)
|
||
* - 変数に値を入れれば即座にステータスに反映されます(メニュー等はパラメータの再取得で自動更新されます)
|
||
*
|
||
*/
|
||
|
||
(function() {
|
||
'use strict';
|
||
const script = document.currentScript;
|
||
const param = PluginManagerEx.createParameter(script);
|
||
|
||
// エリア
|
||
const STAND_AREA_WIDTH = Kurogoma.Map.getStandAreaWidth();
|
||
|
||
// レベルごとの付与ポイント
|
||
const POINTS_PER_LEVEL = Number(param.pointsPerLevel) || 1;
|
||
|
||
// 変数
|
||
const AUTO_TARGET_VAR_ID = Kurogoma.VariableConst.AutoTarget; // 自動割当対象パラメータ保存変数ID 変数の中は("mhp"/"mmp"/"atk"/"def"/"agi"/"")
|
||
const UNASSIGNED_VAR_ID = Kurogoma.VariableConst.UnassignedPoints; // 未割当ポイント保存変数ID
|
||
|
||
// パラメーターID
|
||
const STATUS_PARAM_IDS = {
|
||
mhp: 0,
|
||
mmp: 1,
|
||
atk: 2,
|
||
def: 3,
|
||
agi: 6
|
||
};
|
||
|
||
// パラメーター上限値
|
||
const STATUS_LIMITS = {
|
||
mhp: 999,
|
||
mmp: 999,
|
||
atk: 255,
|
||
def: 255,
|
||
agi: 255
|
||
};
|
||
|
||
// ステータスポイント関連定数
|
||
const STATUS_POINT_SYMBOLS = ["mhp", "mmp", "atk", "def", "agi"];
|
||
|
||
// ステータスポイント表示用ラベル
|
||
const STATUS_POINT_LABELS = {
|
||
mhp: "HP",
|
||
mmp: "MP",
|
||
atk: "ATK",
|
||
def: "DEF",
|
||
agi: "SPD"
|
||
};
|
||
|
||
// ステータスポイント変数ID
|
||
const STATUS_POINT_VARIABLE_IDS = {
|
||
mhp: Kurogoma.VariableConst.StatusHP,
|
||
mmp: Kurogoma.VariableConst.StatusMP,
|
||
atk: Kurogoma.VariableConst.StatusATK,
|
||
def: Kurogoma.VariableConst.StatusDEF,
|
||
agi: Kurogoma.VariableConst.StatusAGI
|
||
};
|
||
|
||
// 上昇量: 1ポイントあたりの増分(例: HP/MPは1ポイント=+5)
|
||
const STATUS_POINT_INCREMENTS = {
|
||
mhp: 5,
|
||
mmp: 5,
|
||
atk: 1,
|
||
def: 1,
|
||
agi: 1
|
||
};
|
||
|
||
const AUTO_TARGET_OPTIONS = [
|
||
{ symbol: "mhp", label: STATUS_POINT_LABELS.mhp, variableValue: "mhp" },
|
||
{ symbol: "mmp", label: STATUS_POINT_LABELS.mmp, variableValue: "mmp" },
|
||
{ symbol: "atk", label: STATUS_POINT_LABELS.atk, variableValue: "atk" },
|
||
{ symbol: "def", label: STATUS_POINT_LABELS.def, variableValue: "def" },
|
||
{ symbol: "agi", label: STATUS_POINT_LABELS.agi, variableValue: "agi" },
|
||
{ symbol: "none", label: "None", variableValue: "" }
|
||
];
|
||
|
||
// --- レベルアップ時の付与 ---
|
||
// 変数20には 'mhp'|'mmp'|'atk'|'def'|'agi' のいずれか(大文字/小文字区別なし)を入れてください。
|
||
const _Game_Actor_prototype_changeExp = Game_Actor.prototype.changeExp;
|
||
Game_Actor.prototype.changeExp = function(exp, show) {
|
||
const oldLevel = this.level;
|
||
_Game_Actor_prototype_changeExp.call(this, exp, show);
|
||
const newLevel = this.level;
|
||
if (newLevel > oldLevel) {
|
||
const autoTargetKey = $gameVariables.value(AUTO_TARGET_VAR_ID);
|
||
const autoTargetVariableId = STATUS_POINT_VARIABLE_IDS[autoTargetKey]; // なくてもundefinedになるだけ
|
||
const gained = (newLevel - oldLevel) * POINTS_PER_LEVEL;
|
||
const canAutoGain = checkAutoGain();
|
||
if (autoTargetVariableId && canAutoGain) { // mhp/mmp/atk/def/agi
|
||
const prev = $gameVariables.value(autoTargetVariableId);
|
||
$gameVariables.setValue(autoTargetVariableId, prev + gained);
|
||
const label = STATUS_POINT_LABELS[autoTargetKey];
|
||
const msg = `${label} has been enhanced!`;
|
||
Torigoya.NotifyMessage.Manager.notify(new Torigoya.NotifyMessage.NotifyItem({ message: msg }));
|
||
} else if (UNASSIGNED_VAR_ID > 0) {
|
||
const prev = Number($gameVariables.value(UNASSIGNED_VAR_ID) || 0);
|
||
$gameVariables.setValue(UNASSIGNED_VAR_ID, prev + gained);
|
||
}
|
||
}
|
||
};
|
||
|
||
// 強化値が最大値に達している場合は自動割当しない機能
|
||
function checkAutoGain() {
|
||
// - AUTO_TARGET_VAR_ID が空または無効な値なら自動割当しない
|
||
// - 対象パラメータが STATUS_LIMITS で定義された最大に達している(>=)なら自動割当しない
|
||
// - 増分値で限界を超える場合でも "部分的に" 効果があるなら実行して良いとする。
|
||
const key = $gameVariables.value(AUTO_TARGET_VAR_ID) || "";
|
||
if (!key) return false;
|
||
if (!STATUS_POINT_SYMBOLS.includes(key)) return false;
|
||
const paramId = STATUS_PARAM_IDS[key];
|
||
if (typeof paramId === "undefined") return false;
|
||
|
||
// 現在の実際のパラメータ値を確認する(通常は base + 加算分)
|
||
const actor = $gameParty.leader();
|
||
const current = actor.param(paramId);
|
||
const limit = STATUS_LIMITS[key] || actor.paramMax(paramId);
|
||
|
||
// 既に上限に達している場合は自動割当を行わない
|
||
return current < limit;
|
||
}
|
||
|
||
// --- 変数に保存した強化値をパラメータに反映 ---
|
||
const _KN_Game_Actor_paramPlus = Game_Actor.prototype.paramPlus;
|
||
Game_Actor.prototype.paramPlus = function(paramId) {
|
||
let value = _KN_Game_Actor_paramPlus.call(this, paramId);
|
||
switch (paramId) {
|
||
case STATUS_PARAM_IDS.mhp: // MHP
|
||
value += Number($gameVariables.value(STATUS_POINT_VARIABLE_IDS.mhp) || 0) * STATUS_POINT_INCREMENTS.mhp;
|
||
break;
|
||
case STATUS_PARAM_IDS.mmp: // MMP
|
||
value += Number($gameVariables.value(STATUS_POINT_VARIABLE_IDS.mmp) || 0) * STATUS_POINT_INCREMENTS.mmp;
|
||
break;
|
||
case STATUS_PARAM_IDS.atk: // ATK
|
||
value += Number($gameVariables.value(STATUS_POINT_VARIABLE_IDS.atk) || 0) * STATUS_POINT_INCREMENTS.atk;
|
||
break;
|
||
case STATUS_PARAM_IDS.def: // DEF
|
||
value += Number($gameVariables.value(STATUS_POINT_VARIABLE_IDS.def) || 0) * STATUS_POINT_INCREMENTS.def;
|
||
break;
|
||
case STATUS_PARAM_IDS.agi: // AGI
|
||
value += Number($gameVariables.value(STATUS_POINT_VARIABLE_IDS.agi) || 0) * STATUS_POINT_INCREMENTS.agi;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
return value;
|
||
};
|
||
|
||
// アクターだけ最大値設定 エネミーも含めるならGame_BattlerBaseで上書き
|
||
const _Game_Actor_paramMax = Game_Actor.prototype.paramMax;
|
||
Game_Actor.prototype.paramMax = function(paramId) {
|
||
switch (paramId) {
|
||
case STATUS_PARAM_IDS.mhp:
|
||
return STATUS_LIMITS.mhp;
|
||
case STATUS_PARAM_IDS.mmp:
|
||
return STATUS_LIMITS.mmp;
|
||
case STATUS_PARAM_IDS.atk:
|
||
return STATUS_LIMITS.atk;
|
||
case STATUS_PARAM_IDS.def:
|
||
return STATUS_LIMITS.def;
|
||
case STATUS_PARAM_IDS.agi:
|
||
return STATUS_LIMITS.agi;
|
||
default:
|
||
return _Game_Actor_paramMax.call(this, paramId);
|
||
}
|
||
};
|
||
|
||
// ステータスポイント手動割り振り
|
||
class Scene_StatusPointAllocate extends Scene_MenuBase {
|
||
create() {
|
||
super.create();
|
||
this._totalAvailable = this.initialAvailablePoints();
|
||
this.createAllWindows();
|
||
}
|
||
|
||
createAllWindows() {
|
||
this.createBoardWindow();
|
||
}
|
||
|
||
start() {
|
||
super.start();
|
||
this._boardWindow.activate();
|
||
this._boardWindow.select(0);
|
||
}
|
||
|
||
initialAvailablePoints() {
|
||
const raw = Number($gameVariables.value(UNASSIGNED_VAR_ID) || 0);
|
||
return Math.max(0, Math.floor(raw));
|
||
}
|
||
|
||
boardWindowRect() {
|
||
const n = STATUS_POINT_SYMBOLS.length + 3;
|
||
const ww = 360;
|
||
const wh = this.calcWindowHeight(n, true);
|
||
const wx = (Graphics.boxWidth - STAND_AREA_WIDTH - ww) / 2;
|
||
const wy = (Graphics.boxHeight - wh) / 2;
|
||
return new Rectangle(wx, wy, ww, wh);
|
||
}
|
||
|
||
createBoardWindow() {
|
||
const rect = this.boardWindowRect();
|
||
const initialAutoSymbol = this.resolveInitialAutoSymbol();
|
||
this._boardWindow = new Window_StatusPointBoard(rect, {
|
||
totalAvailable: this._totalAvailable,
|
||
labels: STATUS_POINT_LABELS,
|
||
autoOptions: AUTO_TARGET_OPTIONS,
|
||
initialAutoSymbol
|
||
});
|
||
this._boardWindow.setHandler("ok", this.onBoardOk.bind(this));
|
||
this._boardWindow.setHandler("cancel", this.onBoardCancel.bind(this));
|
||
this.addWindow(this._boardWindow);
|
||
}
|
||
|
||
onBoardOk() {
|
||
const total = this._boardWindow.totalAllocated();
|
||
const autoDirty = this._boardWindow.isAutoDirty();
|
||
if (total <= 0 && !autoDirty) {
|
||
SoundManager.playBuzzer();
|
||
this._boardWindow.activate();
|
||
return;
|
||
}
|
||
this.applyAutoTargetSelection();
|
||
if (total > 0) {
|
||
this.applyAllocations(this._boardWindow.allocations(), total);
|
||
}
|
||
SoundManager.playOk();
|
||
this.popScene();
|
||
}
|
||
|
||
onBoardCancel() {
|
||
this.popScene();
|
||
}
|
||
|
||
resolveInitialAutoSymbol() {
|
||
const raw = $gameVariables.value(AUTO_TARGET_VAR_ID) || ""; // mhp/mmp/atk/def/agi
|
||
const found = AUTO_TARGET_OPTIONS.find(opt => opt.variableValue === raw);
|
||
return found ? found.symbol : "none";
|
||
}
|
||
|
||
applyAutoTargetSelection() {
|
||
const value = this._boardWindow.currentAutoOption().variableValue;
|
||
$gameVariables.setValue(AUTO_TARGET_VAR_ID, value);
|
||
}
|
||
|
||
applyAllocations(allocations, total) {
|
||
STATUS_POINT_SYMBOLS.forEach(symbol => {
|
||
const add = allocations[symbol];
|
||
if (add <= 0) return;
|
||
const varId = STATUS_POINT_VARIABLE_IDS[symbol];
|
||
if (!varId) return;
|
||
const current = Number($gameVariables.value(varId) || 0);
|
||
$gameVariables.setValue(varId, current + add);
|
||
});
|
||
const remain = Math.max(0, Number($gameVariables.value(UNASSIGNED_VAR_ID) || 0) - total);
|
||
$gameVariables.setValue(UNASSIGNED_VAR_ID, remain);
|
||
$gameParty.leader().refresh();
|
||
}
|
||
}
|
||
|
||
// ステータスポイントウィンドウ
|
||
class Window_StatusPointBoard extends Window_Selectable {
|
||
initialize(rect, options) {
|
||
this._labels = options.labels;
|
||
this._totalAvailable = Math.max(0, Math.floor(options.totalAvailable));
|
||
this._baseValues = this.createBaseValues();
|
||
this.initAllocations();
|
||
this._autoOptions = options.autoOptions;
|
||
this._autoIndex = this.indexOfAutoSymbol(options.initialAutoSymbol);
|
||
if (this._autoIndex < 0) this._autoIndex = 0;
|
||
this._autoInitialIndex = this._autoIndex;
|
||
super.initialize(rect);
|
||
this.refresh();
|
||
this.select(0);
|
||
}
|
||
|
||
initAllocations() {
|
||
this._allocations = {};
|
||
STATUS_POINT_SYMBOLS.forEach(symbol => this._allocations[symbol] = 0);
|
||
}
|
||
|
||
createBaseValues() {
|
||
const actor = $gameParty.leader();
|
||
const values = {};
|
||
STATUS_POINT_SYMBOLS.forEach(symbol => {
|
||
values[symbol] = actor.param(STATUS_PARAM_IDS[symbol]);
|
||
});
|
||
return values;
|
||
}
|
||
|
||
maxCols() {
|
||
return 1;
|
||
}
|
||
|
||
maxItems() {
|
||
return STATUS_POINT_SYMBOLS.length + 2;
|
||
}
|
||
|
||
rowSpacing() {
|
||
return 4;
|
||
}
|
||
|
||
headerHeight() {
|
||
return this.lineHeight();
|
||
}
|
||
|
||
separatorThickness() {
|
||
return 2;
|
||
}
|
||
|
||
headerOffset() {
|
||
return this.headerHeight() + this.separatorThickness() + 8;
|
||
}
|
||
|
||
contentsHeight() {
|
||
const base = super.contentsHeight.call(this);
|
||
return base + this.headerOffset();
|
||
}
|
||
|
||
autoIndex() {
|
||
return STATUS_POINT_SYMBOLS.length;
|
||
}
|
||
|
||
confirmIndex() {
|
||
return STATUS_POINT_SYMBOLS.length + 1;
|
||
}
|
||
|
||
totalAllocated() {
|
||
return STATUS_POINT_SYMBOLS.reduce((sum, key) => sum + this._allocations[key], 0);
|
||
}
|
||
|
||
remainingPoints() {
|
||
return Math.max(0, this._totalAvailable - this.totalAllocated());
|
||
}
|
||
|
||
allocations() {
|
||
return Object.assign({}, this._allocations);
|
||
}
|
||
|
||
isAutoDirty() {
|
||
return this._autoIndex !== this._autoInitialIndex;
|
||
}
|
||
|
||
currentAutoOption() {
|
||
return this._autoOptions[this._autoIndex];
|
||
}
|
||
|
||
itemRect(index) {
|
||
const rect = super.itemRect(index);
|
||
rect.y += this.headerOffset();
|
||
return rect;
|
||
}
|
||
|
||
refresh() {
|
||
this.contents.clear();
|
||
this.drawHeader();
|
||
this.drawItems();
|
||
this.refreshCursor();
|
||
}
|
||
|
||
drawItems() {
|
||
for (let i = 0; i < this.maxItems(); i++) {
|
||
this.drawItemBackground(i);
|
||
this.drawItem(i);
|
||
}
|
||
}
|
||
|
||
drawHeader() {
|
||
const label = "Available Points: ";
|
||
const value = `${this.remainingPoints()}P`;
|
||
const contentWidth = this.contentsWidth();
|
||
const totalText = label + value;
|
||
const totalWidth = this.textWidth(totalText);
|
||
const startX = Math.max(0, (contentWidth - totalWidth) / 2);
|
||
this.drawText(label, startX, 0, totalWidth, "left");
|
||
const labelWidth = this.textWidth(label);
|
||
this.drawText(value, startX + labelWidth, 0, totalWidth - labelWidth, "left");
|
||
this.resetTextColor();
|
||
}
|
||
|
||
drawItem(index) {
|
||
const rect = this.itemLineRect(index);
|
||
this.resetTextColor();
|
||
if (index < STATUS_POINT_SYMBOLS.length) {
|
||
const symbol = STATUS_POINT_SYMBOLS[index];
|
||
const label = this._labels[symbol];
|
||
const value = this.displayStatValue(symbol);
|
||
const increment = this._allocations[symbol];
|
||
const valueWidth = 80;
|
||
const labelWidth = Math.max(0, rect.width - valueWidth);
|
||
this.changeTextColor(ColorManager.systemColor());
|
||
this.drawText(label, rect.x, rect.y, labelWidth, "left");
|
||
this.resetTextColor();
|
||
let displayText = String(value);
|
||
if (increment > 0) {
|
||
displayText += ` (+${increment})`;
|
||
}
|
||
this.drawText(displayText, rect.x, rect.y, rect.width, "right");
|
||
} else if (index === this.autoIndex()) {
|
||
const option = this.currentAutoOption();
|
||
const label = option ? option.label : "";
|
||
this.drawText(`Auto-Assign <${label}>`, rect.x, rect.y, rect.width, "center");
|
||
} else {
|
||
const enabled = this.isConfirmEnabled();
|
||
this.changePaintOpacity(enabled);
|
||
this.drawText("Confirm", rect.x, rect.y, rect.width, "center");
|
||
this.changePaintOpacity(true);
|
||
}
|
||
}
|
||
|
||
isCurrentItemEnabled() {
|
||
if (this.index() === this.confirmIndex()) {
|
||
return this.isConfirmEnabled();
|
||
}
|
||
return true;
|
||
}
|
||
|
||
isConfirmEnabled() {
|
||
return this.totalAllocated() > 0 || this.isAutoDirty();
|
||
}
|
||
|
||
cursorRight(wrap) {
|
||
const index = this.index();
|
||
if (index < STATUS_POINT_SYMBOLS.length) {
|
||
this.adjustStatValue(1);
|
||
} else if (index === this.autoIndex()) {
|
||
this.changeAutoSelection(1);
|
||
} else {
|
||
super.cursorRight(wrap);
|
||
}
|
||
}
|
||
|
||
cursorLeft(wrap) {
|
||
const index = this.index();
|
||
if (index < STATUS_POINT_SYMBOLS.length) {
|
||
this.adjustStatValue(-1);
|
||
} else if (index === this.autoIndex()) {
|
||
this.changeAutoSelection(-1);
|
||
} else {
|
||
super.cursorLeft(wrap);
|
||
}
|
||
}
|
||
|
||
processOk() {
|
||
const idx = this.index();
|
||
if (idx < STATUS_POINT_SYMBOLS.length) {
|
||
this.adjustStatValue(1);
|
||
return;
|
||
}
|
||
if (idx === this.autoIndex()) {
|
||
this.changeAutoSelection(1);
|
||
return;
|
||
}
|
||
if (idx === this.confirmIndex()) {
|
||
if (this.isConfirmEnabled()) {
|
||
if (this.totalAllocated() > 0) {
|
||
AudioManager.playSe({ name: "Absorb2", pan: 0, pitch: 100, volume: 90 });
|
||
}
|
||
super.processOk();
|
||
} else {
|
||
SoundManager.playBuzzer();
|
||
}
|
||
} else {
|
||
SoundManager.playBuzzer();
|
||
}
|
||
}
|
||
|
||
adjustStatValue(step) {
|
||
const symbol = STATUS_POINT_SYMBOLS[this.index()];
|
||
const current = this._allocations[symbol];
|
||
if (step > 0) {
|
||
const pointValue = STATUS_POINT_INCREMENTS[symbol];
|
||
const limit = STATUS_LIMITS[symbol];
|
||
const base = this._baseValues[symbol];
|
||
const statAfter = base + (current + 1) * pointValue;
|
||
if (this.remainingPoints() <= 0 || statAfter > limit) {
|
||
SoundManager.playBuzzer();
|
||
return;
|
||
}
|
||
this._allocations[symbol] = current + 1;
|
||
SoundManager.playCursor();
|
||
} else if (step < 0) {
|
||
if (current <= 0) {
|
||
SoundManager.playBuzzer();
|
||
return;
|
||
}
|
||
this._allocations[symbol] = current - 1;
|
||
SoundManager.playCursor();
|
||
}
|
||
this.refresh();
|
||
}
|
||
|
||
changeAutoSelection(delta) {
|
||
if (!this._autoOptions.length) return;
|
||
const max = this._autoOptions.length;
|
||
this._autoIndex = (this._autoIndex + delta + max) % max;
|
||
SoundManager.playCursor();
|
||
this.refresh();
|
||
}
|
||
|
||
displayStatValue(symbol) {
|
||
const base = this._baseValues[symbol];
|
||
const allocation = this._allocations[symbol];
|
||
const pointValue = STATUS_POINT_INCREMENTS[symbol];
|
||
return base + allocation * pointValue;
|
||
}
|
||
|
||
indexOfAutoSymbol(symbol) {
|
||
if (!symbol) return -1;
|
||
return this._autoOptions.findIndex(opt => opt.symbol === symbol);
|
||
}
|
||
}
|
||
window.Kurogoma = window.Kurogoma || {};
|
||
window.Kurogoma.Scene_StatusPointAllocate = Scene_StatusPointAllocate;
|
||
|
||
})(); |