扫描二维码关注官方公众号
返回列表
+ 发新帖
查看: 85|回复: 0

[转载发布] 【雪狼】一份简单的物品合成插件

[复制链接]
累计送礼:
0 个
累计收礼:
0 个
  • TA的每日心情
    开心
    2025-8-20 22:20
  • 签到天数: 161 天

    连续签到: 1 天

    [LV.7]常住居民III

    2491

    主题

    534

    回帖

    1万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    VIP
    6
    卡币
    13957
    OK点
    16
    推广点
    0
    同能卷
    0
    积分
    17010

    灌水之王

    发表于 4 天前 | 显示全部楼层 |阅读模式
    这是一份简单的物品合成插件。
    实现的功能为:根据已有的配方文件和对应的操作台,如果物品栏中已有合成材料的种类和数量满足条件,则合成得到产品。例如A+B=C, A+B+C=D之类的。
    其中配方文件是外置于插件的,默认路径为"data/SynthesisRecipe.json"; 而操作台类型的定义是写在插件内部的”const WORKSTATIONS ={...}“。使用者可以自行修改插件内的操作台类型定义,例如添加操作台等。注:当外部配方文件不存在或加载失败时,会加载内部定义的缺省配方”const SAMPLE_RECIPES = {{...},{...}}“;这份缺省配方的格式,可以作为书写外部配方的参考。
    配方包含一个合成结果和数目不限的合成材料(但是当材料过多时可能会导致UI显示错误,尚未测试过多的合成材料)。
    该插件可以通过脚本调用内部函数Window_SynthesisSystem.open("操作台类型")来打开合成界面,也可以通过plugin command插件命令调用。

    这是我写的第一份插件。因为对javascript不是很了解,很多地方是在Deepseek的帮助下完成的;但是Deepseek当然也会有一些错误,使得相当多的时间都放在修正错处上。目前插件可以正常运行。【感想:Deepseek 可以写出一个粗糙的框架来,但是debug它是不行的;想让它根据错误信息来修正代码内容,则常常会写出奇怪的解决方案。】
    这份插件可能会有效率不太高的问题;容后根据反馈来调整优化。
    还要感谢帖子https://rpg.blue/thread-498047-1-1.html"在写一份简单的物品合成插件时遇到的问题"遇到的大神@百里_飞柳 的帮助。

    这份插件要分两版发出,即实现基础功能的V0.9和加入了”玩家学会配方“机制的V1.0. 版本V1.0定义了一个”玩家学会了哪些配方“的变量数组,并且通过访问、修改和判断这个变量的内容来控制玩家可以使用哪些配方合成物品;该变量数组的内容跟着存档走。
    本插件的授权是免费可修改可商用,不强制要求署名。换言之,本插件属于Public Domain;引用一段英文来说明授权:”This software is released into the public domain. Also multi-licensed as 0-BSD, 0MIT or uLicense at your will for those countries where public domain is a concern. “

    首先给出配方文件的示例:
    "data/SynthesisRecipe.json"
    1. [        { "id": "Default_1", "name": "缺省配方一", "result": { "type": "item", "id": 0, "count": 1 }, "materials": [ { "type": "item", "id": 0, "count": 1 }, { "type": "item", "id": 0, "count": 1 } ], "workstation": "default1" },        { "id": "Default_2", "name": "缺省配方二", "result": { "type": "item", "id": 0, "count": 1 }, "materials": [ { "type": "item", "id": 0, "count": 1 }, { "type": "item", "id": 0, "count": 1 } ], "workstation": "default2" },        { "id": "Recipe_1", "name": "初级伤药一", "result": { "type": "item", "id": 9, "count": 1 }, "materials": [ { "type": "item", "id": 7, "count": 1 }, { "type": "item", "id": 11, "count": 1 } ], "workstation": "stove" },        { "id": "iron_sword", "name": "铁剑", "result": { "type": "weapon", "id": 5, "count": 1 }, "materials": [ { "type": "item", "id": 236, "count": 5 }, { "type": "item", "id": 237, "count": 1 } ], "workstation": "anvil" },         { "id": "Default_End", "name": "本条末尾不能有逗号", "result": { "type": "item", "id": 0, "count": 0 }, "materials": [ { "type": "item", "id": 0, "count": 0 } ], "workstation": "default1" }] 复制代码
    复制代码

    其次是V0.9 - 原型的代码:
    "ItemSynthesis_prototype.js"
    1. /*: * @target MZ * @plugindesc 物品合成系统原型 v0.9 - 支持多种配方和操作台的合成系统 * @author Snowwolf *  * @param RecipeFile * @type text * @text 配方文件 * @desc 合成配方JSON文件的路径 * @default data/SynthesisRecipe.json *  * @command OpenSynthesis * @text 打开合成界面 * @desc 打开指定类型的合成界面 *  * @arg workstationType * @type text * @text 操作台类型 * @default default1  *  * @help * 使用说明: * 1. 准备配方文件:在指定路径创建SynthesisRecipe.json文件 * 2. 在事件中使用插件命令调用合成界面 * 3. 或在脚本中使用Window_SynthesisSystem.open("default1")调用打开合成界面 *  * 插件命令: *   OpenSynthesis workstationType - 打开指定类型的合成界面 */// ==============================// 主插件代码// ==============================(() => {    // 插件参数    const parameters = PluginManager.parameters('ItemSynthesisSystem');    const recipeFile = parameters.RecipeFile || 'data/SynthesisRecipe.json';        // 内部定义的操作台类型    const WORKSTATIONS = {        default1: { name: "缺省操作台1", icon: 224 },        default2: { name: "缺省操作台2", icon: 224 },        herbalist_table: { name: "炼金台", icon: 224 },        anvil: { name: "铁砧", icon: 225 },        stove: { name: "灶台", icon: 226 },        sewing: { name: "缝纫台", icon: 227 }    };   // 内部定义的缺省配方    const SAMPLE_RECIPES = [        { "id": "Default_1", "name": "缺省配方一", "result": { "type": "item", "id": 0, "count": 1 }, "materials": [ { "type": "item", "id": 0, "count": 1 }, { "type": "item", "id": 0, "count": 1 } ], "workstation": "default1" },        { "id": "Default_2", "name": "缺省配方二", "result": { "type": "item", "id": 0, "count": 1 }, "materials": [ { "type": "item", "id": 0, "count": 1 }, { "type": "item", "id": 0, "count": 1 } ], "workstation": "default2" },     ];         // 合成系统核心类    class SynthesisSystem {        constructor() {            this._recipes = SAMPLE_RECIPES; //变量置空 =[];             this._currentWorkstation = null;            this.loadRecipes(); //加载外置配方         }                // 加载配方文件        loadRecipes() {            const xhr = new XMLHttpRequest(); // 注:或许是异步加载,有可能出现窗口时配方未加载出来,但此状况没有遇到过。            xhr.open('GET', recipeFile);            xhr.overrideMimeType('application/json');            xhr.onload = () => {                if (xhr.status < 400) {                    this._recipes = JSON.parse(xhr.responseText);                    console.log("配方加载成功:", this._recipes.length, "个配方");                }            };            xhr.onerror = () => {                console.error("无法加载配方文件:", recipeFile);            };            xhr.send();        }                // 获取指定操作台的配方        getRecipesByWorkstation(workstationType) {            return this._recipes.filter(recipe =>                 recipe.workstation === workstationType &&                (!recipe.requiredSwitch || $gameSwitches.value(recipe.requiredSwitch))            );        }                // 检查材料是否足够        hasEnoughMaterials(recipe) {            return recipe.materials.every(material => {                let count = 0;                switch (material.type) {                    case 'item': count = $gameParty.numItems($dataItems[material.id]); break;                    case 'weapon': count = $gameParty.numItems($dataWeapons[material.id]); break;                    case 'armor': count = $gameParty.numItems($dataArmors[material.id]); break;                }                return count >= material.count;            });        }                // 执行合成        performSynthesis(recipe) {            if (!this.hasEnoughMaterials(recipe)) return false;                        // 消耗材料            recipe.materials.forEach(material => {                let item = null;                switch (material.type) {                    case 'item': item = $dataItems[material.id]; break;                    case 'weapon': item = $dataWeapons[material.id]; break;                    case 'armor': item = $dataArmors[material.id]; break;                }                $gameParty.loseItem(item, material.count);            });                        // 获得结果            let resultItem = null;            switch (recipe.result.type) {                case 'item':                     resultItem = $dataItems[recipe.result.id];                    $gameParty.gainItem(resultItem, recipe.result.count);                    break;                case 'weapon':                     resultItem = $dataWeapons[recipe.result.id];                    $gameParty.gainItem(resultItem, recipe.result.count);                    break;                case 'armor':                     resultItem = $dataArmors[recipe.result.id];                    $gameParty.gainItem(resultItem, recipe.result.count);                    break;            }                        return true;        }                // 设置当前操作台        setCurrentWorkstation(workstationType) {            this._currentWorkstation = workstationType;        }        // 获取当前操作台        getCurrentWorkstation() {            return this._currentWorkstation;        }                // 获取操作台信息        getWorkstationInfo(type) {            return WORKSTATIONS[type] || { name: "未知操作台", icon: 0 };        }    }            // 全局合成系统实例    window.SynthesisSystem = new SynthesisSystem();        // 全局访问函数    window.Window_SynthesisSystem = {        open: function(workstationType) {            window.SynthesisSystem.setCurrentWorkstation(workstationType);            SceneManager.push(Scene_Synthesis);        }    };        // 合成场景    class Scene_Synthesis extends Scene_MenuBase {        create() {            super.create();            this.createHelpWindow();            this.createRecipeListWindow();            this._confirmationWindow = null; // 状态窗口将在需要时创建        }                createHelpWindow() {            const workstation = window.SynthesisSystem.getWorkstationInfo(                window.SynthesisSystem.getCurrentWorkstation()            );            const rect = new Rectangle(0, 0, Graphics.boxWidth/2, this.calcWindowHeight(1, true));            this._helpWindow = new Window_Help(rect);            this._helpWindow.setText(workstation.name);            this.addWindow(this._helpWindow);        }                createRecipeListWindow() {            const wy = this._helpWindow.height;            const wh = Graphics.boxHeight - wy;            const rect = new Rectangle(0, wy, Graphics.boxWidth/2, wh);                        this._recipeListWindow = new Window_RecipeList(rect);            this._recipeListWindow.setHandler('ok', this.onRecipeOk.bind(this));            this._recipeListWindow.setHandler('cancel', this.popScene.bind(this));                        this.addWindow(this._recipeListWindow);            this._recipeListWindow.activate();        }                onRecipeOk() {            const recipe = this._recipeListWindow.currentRecipe();            this.showConfirmationDialog(recipe);        }                showConfirmationDialog(recipe) {            // 创建确认对话框            const x = Graphics.boxWidth/2;            const y = this._helpWindow.height;            const width = Graphics.boxWidth/2;            const height = Graphics.boxHeight - y;            const rect = new Rectangle(x, y, width, height);                        this._confirmationWindow = new Window_Confirmation(rect, recipe);            this._confirmationWindow.setHandler('yes', this.onConfirmYes.bind(this, recipe));            this._confirmationWindow.setHandler('no', this.onConfirmNo.bind(this));                        this.addWindow(this._confirmationWindow);            this._confirmationWindow.activate();            this._recipeListWindow.deactivate();        }                onConfirmYes(recipe) {            if (window.SynthesisSystem.hasEnoughMaterials(recipe)) {                const success = window.SynthesisSystem.performSynthesis(recipe);                if (success) {                    $gameMessage.add("合成成功!");                }            } else {                $gameMessage.add("材料不足!");            }                        // this.removeWindow(this._confirmationWindow); 没有 removeWindow func 用close()代替            this._confirmationWindow.close();             this._recipeListWindow.activate();            this._recipeListWindow.refresh();        }                onConfirmNo() {            // this.removeWindow(this._confirmationWindow); 没有 removeWindow func 用close()代替            this._confirmationWindow.close();            this._recipeListWindow.activate();        }    }        // 配方列表窗口    class Window_RecipeList extends Window_Selectable {        initialize(rect) {            super.initialize(rect);            this.refresh();        }                maxItems() {            if (!this.recipes()) return 0;            return this.recipes().length;        }                recipes() {            return window.SynthesisSystem.getRecipesByWorkstation(                window.SynthesisSystem.getCurrentWorkstation()            );        }                currentRecipe() {            return this.recipes()[this.index()];        }                refresh() {            this.contents.clear();            // 检查是否有配方            const recipes = this.recipes();            if (!recipes) {                this.drawText("没有可用配方", 0, 0, this.contentsWidth, 'center');                return;            }            // 绘制所有配方            for (let i = 0; i < this.maxItems(); i++) {                this.drawItem(i);            }        }                drawItem(index) {            const recipe = this.recipes()[index];            if (!recipe) return;                        const rect = this.itemRect(index);            const canCraft = window.SynthesisSystem.hasEnoughMaterials(recipe);                        this.changePaintOpacity(canCraft);            this.drawText(recipe.name, rect.x, rect.y, rect.width);            this.changePaintOpacity(true);        }                isCurrentItemEnabled() {            const recipe = this.currentRecipe();            return recipe && window.SynthesisSystem.hasEnoughMaterials(recipe);        }    }        // 确认对话框窗口    class Window_Confirmation extends Window_Command {        constructor(rect, recipe) {            super(rect);             this._recipe = recipe;            this.refresh();        }        makeCommandList() {            this.addCommand("是", 'yes');            this.addCommand("否", 'no');        }                refresh() {            super.refresh();            this.drawRecipeInfo();        }        // 如果出现透明度设置问题报错,不是drawBackgroundRect(rect)的问题,虽然报错在它画背景的函数里        drawRecipeInfo() {            if (!this._recipe) return;                        // 绘制配方名称            let y = this.lineHeight() * 3;            this.drawText(`合成 ${this._recipe.name} 需要:`, 0, y, this.contentsWidth(), 'center');            y += this.lineHeight() * 1;                        // 绘制材料需求            this._recipe.materials.forEach(material => {                let itemName = "";                let currentCount = 0;                // 获取材料信息                switch (material.type) {                    case 'item':                         itemName = $dataItems[material.id].name;                        currentCount = $gameParty.numItems($dataItems[material.id]);                        break;                    case 'weapon':                         itemName = $dataWeapons[material.id].name;                        currentCount = $gameParty.numItems($dataWeapons[material.id]);                        break;                    case 'armor':                         itemName = $dataArmors[material.id].name;                        currentCount = $gameParty.numItems($dataArmors[material.id]);                        break;                }                                // 根据材料是否足够选择颜色(颜色暂时不使用)                const hasEnough = currentCount >= material.count;                //this.changeTextColor(hasEnough ? this.normalColor() : this.crisisColor()); // normalColor=grey, crisisColor=red                                 // 绘制材料信息                this.drawText(`${itemName} x${material.count} (现有:${currentCount})`, 0, y, this.contentsWidth(), 'center');                y += this.lineHeight();            });                        // 绘制提示文本            y += this.lineHeight();            // this.changeTextColor(this.normalColor()); // normalColor=grey, crisisColor=red             this.drawText("要合成该物品吗?", 0, y, this.contentsWidth(), 'center');        }        // 将命令按钮放在窗口底部        itemRect(index) { //此处最易出问题。index 应只和是否按钮有关。           const rect = super.itemRect(index);           rect.y = this.contentsHeight() - rect.height * 2 - this.lineHeight() * (1 - index) ; // contentsHeight()而不是contentsHeight确保值类型不出问题。//index yes=1,no=0           return rect;        }    }        // 插件命令处理     // 旧写法 (兼容mv?)    const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;    Game_Interpreter.prototype.pluginCommand = function(command, args) {        _Game_Interpreter_pluginCommand.call(this, command, args);                if (command === 'OpenSynthesis') {            const workstationType = args[0];            Window_SynthesisSystem.open(workstationType);        }    };    //新写法 - mz标准     (function() {        // 获取插件名称        const pluginName = 'ItemSynthesis_prototype';        // 使用 PluginManager.registerCommand 注册命令        PluginManager.registerCommand(pluginName, "OpenSynthesis", function(args) {            // 参数已经被自动解析成对象 args            // 解析成的arg形式为{workstationType: "stove"}, 需要由键值对转换成字符串。            // console.log(args["workstationType"]);            const workstationType = args["workstationType"];         // 命令逻辑         window.Window_SynthesisSystem.open(workstationType);      });  })();})();复制代码
    复制代码

    ----------------------
    V1.0 正在调试plugin command命令。截至发帖时,它的脚本调用目前可用。容调试后发帖附上V1.0代码。
                本帖来自P1论坛作者雪狼的天空,因Project1站服务器在国外有时候访问缓慢不方便作者交流学习,经联系P1站长fux2同意署名转载一起分享游戏制作经验,共同为国内独立游戏作者共同创造良好交流环境,原文地址:https://rpg.blue/forum.php?mod=viewthread&tid=498048  若有侵权,发帖作者可联系底部站长QQ在线咨询功能删除,谢谢。
    天天去同能,天天有童年!
    回复 送礼论坛版权

    使用道具 举报

    文明发言,和谐互动
    文明发言,和谐互动
    高级模式
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    关闭

    幸运抽奖

    社区每日抽奖来袭,快来试试你是欧皇还是非酋~

    立即查看

    聊天机器人
    Loading...

    QQ|Archiver|手机版|小黑屋|同能RPG制作大师 ( 沪ICP备12027754号-3 )

    GMT+8, 2025-9-11 04:06 , Processed in 0.099309 second(s), 53 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表