nyacronomicon/js/plugins/KN_Shop.js

341 lines
No EOL
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//=============================================================================
// RPG Maker MZ - KN_Shop.js
//=============================================================================
/*:ja
* @target MZ
* @author kurogoma knights
* @base PluginCommonBase
* @plugindesc ショップ(ランダム商品陳列)
*
* @command MAKE_SHOP
* @desc ショップ作成
* @arg name @default shop1
* @arg buyOnly @type boolean @default false
* @arg discountable @type boolean @default true
* @arg manageStock @type boolean @default true
* @desc 在庫管理するtrueか無限在庫false
*
* @command DELETE_SHOP
* @desc ショップ削除
* @arg name @default shop1
*
* @command ADD_GOODS
* @desc タグ指定で商品追加
* @arg name @default shop1
* @arg goodsType @type select @option アイテム @option スキル @option レア @option ニボシ @default アイテム
*
* @command ADD_GOODS_ID
* @desc ID指定で商品追加
* @arg name @default shop1
* @arg items @type item[] @default []
*
* @command RANDOMIZE
* @desc 商品をランダム選択
* @arg name @default shop1
* @arg minGoods @type number @min 1 @default 6
* @arg maxGoods @type number @max 20 @default 8
* @arg unique @type boolean @default false
*
* @command OPEN_SHOP
* @desc ショップを開く
* @arg name @default shop1
*
* @command RESET_SHOP
* @desc 在庫リセット(全商品再入荷)
* @arg name @default shop1
*/
(() => {
'use strict';
const script = document.currentScript;
//=========================================================================
// 定数
//=========================================================================
const SOLD_OUT_LABEL = 'Sold Out';
const DISCOUNT_ITEM_ID = 244;
const DISCOUNT_RATE = 0.8;
//=========================================================================
// ユーティリティ
//=========================================================================
// 真偽値に変換
function toBool(v, defaultVal = false) {
if (v === true || v === 'true' || v === 1 || v === '1') return true;
if (v === false || v === 'false' || v === 0 || v === '0') return false;
return defaultVal;
}
// 配列をシャッフルして指定数を取得
function pickRandom(array, count, unique = true) {
if (unique) {
const shuffled = [...array].sort(() => Math.random() - 0.5);
return shuffled.slice(0, Math.min(count, array.length))
.map(item => JsonEx.makeDeepCopy(item));
} else {
return Array.from({ length: count }, () => {
const item = array[Math.floor(Math.random() * array.length)];
return JsonEx.makeDeepCopy(item);
});
}
}
// ショップ用商品データ作成
function createGoods(itemId, overridePrice = null) {
return [
0, // 商品種別0:アイテム)
itemId, // アイテムID
overridePrice ? 1 : 0, // 価格種別0:標準 1:指定)
overridePrice, // 価格指定値
true // 在庫あり
];
}
// 割引後の価格を計算
function getDiscountedPrice(basePrice, allowDiscount) {
if (allowDiscount && $gameParty.hasItem($dataItems[DISCOUNT_ITEM_ID], false)) {
return Math.floor(basePrice * DISCOUNT_RATE);
}
return basePrice;
}
// スキルタイプ判定
function isActiveSkill(item) {
return item?.meta?.['スキルタイプ'] === 'アクティブ';
}
// 所持数取得
// パッシブ:そのアイテムのみカウント
// アクティブ:強化前後を合算(どちらか片方しか持てない)
function getOwnedCount(item) {
let count = $gameParty.numItems(item);
// アクティブスキルは強化前後を合算
if (isActiveSkill(item) && item.id % 2 === 0) {
const evolved = $dataItems[item.id + 1];
if (evolved) count += $gameParty.numItems(evolved);
}
return count;
}
//=========================================================================
// ショップデータ管理Game_System拡張
//=========================================================================
const _Game_System_initialize = Game_System.prototype.initialize;
Game_System.prototype.initialize = function() {
_Game_System_initialize.call(this);
this._knShops = [];
};
// ショップ検索
Game_System.prototype.knFindShop = function(args) {
return this._knShops.find(shop => shop.name === args.name);
};
// ショップ作成
Game_System.prototype.knMakeShop = function(args) {
if (this.knFindShop(args)) {
return console.warn(`knMakeShop: already exists: ${args.name}`);
}
this._knShops.push({
name: args.name,
goods: [],
buyOnly: toBool(args.buyOnly, false),
discountable: toBool(args.discountable, true),
manageStock: toBool(args.manageStock, true)
});
};
// ショップ削除
Game_System.prototype.knDeleteShop = function(args) {
const index = this._knShops.findIndex(shop => shop.name === args.name);
if (index === -1) return;
this._knShops.splice(index, 1);
};
// タグ指定で商品追加
Game_System.prototype.knAddGoods = function(args) {
const shop = this.knFindShop(args);
if (!shop) return console.warn(`knAddGoods: shop not found: ${args.name}`);
const tagMap = {
'アイテム': '<アイテムショップグッズ>',
'スキル': '<スキルショップグッズ>',
'レア': '<レアショップグッズ>',
'ニボシ': '<ニボシショップグッズ>'
};
const tag = tagMap[args.goodsType];
if (!tag) return console.warn(`knAddGoods: unknown type: ${args.goodsType}`);
$dataItems.forEach(item => {
if (item?.note?.includes(tag)) {
shop.goods.push(createGoods(item.id, item.meta?.OverridePrice));
}
});
};
// ID指定で商品追加
Game_System.prototype.knAddGoodsId = function(args) {
const shop = this.knFindShop(args);
if (!shop) return console.warn(`knAddGoodsId: shop not found: ${args.name}`);
if (!Array.isArray(args.items)) return;
args.items.forEach(id => {
const item = $dataItems[id];
if (item) {
shop.goods.push(createGoods(item.id, item.meta?.OverridePrice));
}
});
};
// 商品をランダム選択
Game_System.prototype.knRandomize = function(args) {
const shop = this.knFindShop(args);
if (!shop) return console.warn(`knRandomize: shop not found: ${args.name}`);
const count = Kurogoma.Random.range(args.minGoods, args.maxGoods);
shop.goods = pickRandom(shop.goods, count, toBool(args.unique, false));
};
// 在庫リセット
Game_System.prototype.knResetShop = function(args) {
const shop = this.knFindShop(args);
if (!shop) return console.warn(`knResetShop: not found: ${args.name}`);
shop.goods.forEach(g => g[4] = true);
};
// ショップを開く
Game_System.prototype.knOpenShop = function(args) {
const shop = this.knFindShop(args);
if (!shop) return console.warn(`knOpenShop: not found: ${args.name}`);
SceneManager.push(Scene_ShopEx);
SceneManager.prepareNextScene(shop.goods, shop.buyOnly, {
discountable: shop.discountable,
manageStock: shop.manageStock
});
};
//=========================================================================
// プラグインコマンド登録
//=========================================================================
PluginManagerEx.registerCommand(script, "MAKE_SHOP", args => $gameSystem.knMakeShop(args));
PluginManagerEx.registerCommand(script, "DELETE_SHOP", args => $gameSystem.knDeleteShop(args));
PluginManagerEx.registerCommand(script, "ADD_GOODS", args => $gameSystem.knAddGoods(args));
PluginManagerEx.registerCommand(script, "ADD_GOODS_ID", args => $gameSystem.knAddGoodsId(args));
PluginManagerEx.registerCommand(script, "RANDOMIZE", args => $gameSystem.knRandomize(args));
PluginManagerEx.registerCommand(script, "RESET_SHOP", args => $gameSystem.knResetShop(args));
PluginManagerEx.registerCommand(script, "OPEN_SHOP", args => $gameSystem.knOpenShop(args));
//=========================================================================
// Scene_Shop拡張
//=========================================================================
class Scene_ShopEx extends Scene_Shop {
prepare(goods, purchaseOnly, shopMeta) {
super.prepare(goods, purchaseOnly);
this._shopMeta = shopMeta;
}
createBuyWindow() {
const rect = this.buyWindowRect();
this._buyWindow = new Window_ShopBuyEx(rect);
this._buyWindow.setupGoods(this._goods);
this._buyWindow.setHelpWindow(this._helpWindow);
this._buyWindow.setStatusWindow(this._statusWindow);
this._buyWindow.hide();
this._buyWindow.setHandler("ok", this.onBuyOk.bind(this));
this._buyWindow.setHandler("cancel", this.onBuyCancel.bind(this));
this.addWindow(this._buyWindow);
}
// 数量入力をスキップして直接購入
onBuyOk() {
this._item = this._buyWindow.item();
SoundManager.playShop();
this.doBuy(1);
this.activateBuyWindow();
this._goldWindow.refresh();
this._statusWindow.refresh();
}
doBuy(number) {
super.doBuy(number);
if (this._shopMeta?.manageStock) {
this._goods[this._buyWindow.index()][4] = false;
}
}
buyingPrice() {
const base = super.buyingPrice();
return getDiscountedPrice(base, this._shopMeta?.discountable);
}
}
//=========================================================================
// Window_ShopBuy拡張
//=========================================================================
class Window_ShopBuyEx extends Window_ShopBuy {
// 商品描画
drawItem(index) {
const item = this.itemAt(index);
const rect = this.itemLineRect(index);
const priceWidth = this.priceWidth();
const nameWidth = rect.width - priceWidth;
const shopMeta = SceneManager._scene?._shopMeta;
const inStock = shopMeta?.manageStock ? this._shopGoods[index][4] : true;
const price = getDiscountedPrice(this.price(item), shopMeta?.discountable);
const priceText = inStock ? price : SOLD_OUT_LABEL;
this.changePaintOpacity(this.isEnabled(item, inStock));
this.drawItemName(item, rect.x, rect.y, nameWidth);
this.drawText(priceText, rect.x + nameWidth, rect.y, priceWidth, "right");
this.changePaintOpacity(true);
}
// 現在選択中のアイテムが購入可能か
isCurrentItemEnabled() {
const item = this._data[this.index()];
const goods = this._shopGoods[this.index()];
const manageStock = SceneManager._scene?._shopMeta?.manageStock;
const inStock = manageStock ? (goods?.[4] ?? false) : true;
return this.isEnabled(item, inStock);
}
// 購入可能判定
isEnabled(item, inStock) {
if (!super.isEnabled(item)) return false;
if (!inStock) return false;
if (!this.canPurchaseItem(item)) return false;
return true;
}
// 所持制限チェック
canPurchaseItem(item) {
if (!item) return false;
const limit = Number(item.meta?.['所持制限'] ?? 99);
const owned = getOwnedCount(item);
// 所持制限に達している
if (owned >= limit) return false;
// アクティブスキル:強化後を持っているなら強化前は買えない
// getOwnedCountで合算済みだが、明示的に重複禁止のチェックも残す
if (isActiveSkill(item) && item.id % 2 === 0) {
const evolved = $dataItems[item.id + 1];
if (evolved && $gameParty.hasItem(evolved)) return false;
}
return true;
}
}
})();