341 lines
No EOL
13 KiB
JavaScript
341 lines
No EOL
13 KiB
JavaScript
//=============================================================================
|
||
// 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;
|
||
}
|
||
}
|
||
|
||
})(); |